Skip to content

Commit 0b80857

Browse files
committed
refactor: Enhance PriorityWithdrawalQueue for improved withdrawal request handling
- Update WithdrawRequest structure to include amountLocked for better tracking of finalized requests - Modify fulfillRequests function to handle locking amounts and finalize requests correctly - Adjust invalidateRequests to support both pending and finalized requests - Update tests to reflect changes in request handling and ensure proper functionality
1 parent f199f40 commit 0b80857

3 files changed

Lines changed: 224 additions & 149 deletions

File tree

src/PriorityWithdrawalQueue.sol

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,16 @@ contract PriorityWithdrawalQueue is
9999
uint32 creationTime
100100
);
101101
event WithdrawRequestCancelled(bytes32 indexed requestId, address indexed user, uint96 amountOfEEth, uint96 sharesOfEEth, uint32 nonce, uint32 timestamp);
102-
event WithdrawRequestFinalized(bytes32 indexed requestId, address indexed user, uint96 amountOfEEth, uint96 sharesOfEEth, uint32 nonce, uint32 timestamp);
102+
event WithdrawRequestFinalized(
103+
bytes32 indexed pendingRequestId,
104+
bytes32 indexed finalizedRequestId,
105+
address indexed user,
106+
uint96 amountOfEEth,
107+
uint96 shareOfEEth,
108+
uint32 nonce,
109+
uint32 creationTime,
110+
uint96 amountLocked
111+
);
103112
event WithdrawRequestClaimed(bytes32 indexed requestId, address indexed user, uint96 amountOfEEth, uint96 sharesOfEEth, uint32 nonce, uint32 timestamp);
104113
event WithdrawRequestInvalidated(bytes32 indexed requestId, uint96 amountOfEEth, uint96 sharesOfEEth, uint32 nonce, uint32 timestamp);
105114
event WhitelistUpdated(address indexed user, bool status);
@@ -275,31 +284,62 @@ contract PriorityWithdrawalQueue is
275284

276285
/// @notice Request manager finalizes withdrawal requests after maturity
277286
/// @dev Checks maturity and deadline, marks requests as finalized
278-
/// @param requests Array of requests to finalize
287+
/// Pending requests have amountLocked=0, must pass such requests to this function
288+
/// @param requests Array of pending requests to finalize (with amountLocked=0)
279289
function fulfillRequests(WithdrawRequest[] calldata requests) external onlyRequestManager whenNotPaused {
280-
uint256 totalSharesToFinalize = 0;
290+
uint256 totalAmountToLock = 0;
281291

282292
for (uint256 i = 0; i < requests.length; ++i) {
283293
WithdrawRequest calldata request = requests[i];
284294
bytes32 requestId = keccak256(abi.encode(request));
285295

296+
// Pending requests must have amountLocked = 0
297+
if (request.amountLocked != 0) revert BadInput();
298+
286299
// Verify request exists in pending set
287300
if (!_withdrawRequests.contains(requestId)) revert RequestNotFound();
288-
if (_finalizedRequests.contains(requestId)) revert RequestAlreadyFinalized();
289301

290302
// Check MIN_DELAY has passed (request must wait at least MIN_DELAY seconds)
291303
uint256 earliestFulfillTime = request.creationTime + MIN_DELAY;
292304
if (block.timestamp < earliestFulfillTime) revert NotMatured();
293305

294-
// Add to finalized set
295-
_finalizedRequests.add(requestId);
296-
totalSharesToFinalize += request.shareOfEEth;
297-
298-
emit WithdrawRequestFinalized(requestId, request.user, request.amountOfEEth, request.shareOfEEth, request.nonce, uint32(block.timestamp));
306+
// Calculate and store the locked amount for this request
307+
// Cap at original amount to prevent locking more than user requested
308+
uint256 amountForShares = liquidityPool.amountForShare(request.shareOfEEth);
309+
uint96 amountToLock = request.amountOfEEth < amountForShares
310+
? request.amountOfEEth
311+
: uint96(amountForShares);
312+
totalAmountToLock += amountToLock;
313+
314+
// Create finalized request with amountLocked set
315+
bytes32 finalizedRequestId = keccak256(abi.encode(WithdrawRequest({
316+
user: request.user,
317+
amountOfEEth: request.amountOfEEth,
318+
shareOfEEth: request.shareOfEEth,
319+
nonce: request.nonce,
320+
creationTime: request.creationTime,
321+
amountLocked: amountToLock
322+
})));
323+
324+
// Remove from pending set, add finalized request to finalized set
325+
_withdrawRequests.remove(requestId);
326+
327+
bool addedToFinalized = _finalizedRequests.add(finalizedRequestId);
328+
if (!addedToFinalized) revert RequestAlreadyFinalized();
329+
330+
emit WithdrawRequestFinalized(
331+
requestId,
332+
finalizedRequestId,
333+
request.user,
334+
request.amountOfEEth,
335+
request.shareOfEEth,
336+
request.nonce,
337+
request.creationTime,
338+
amountToLock
339+
);
299340
}
300341

301342
// Lock ETH in LiquidityPool for priority withdrawals
302-
uint256 totalAmountToLock = liquidityPool.amountForShare(totalSharesToFinalize);
303343
liquidityPool.addEthAmountLockedForPriorityWithdrawal(uint128(totalAmountToLock));
304344
}
305345

@@ -337,14 +377,18 @@ contract PriorityWithdrawalQueue is
337377
}
338378
}
339379

340-
/// @notice Invalidate a withdrawal request (prevents finalization)
380+
/// @notice Invalidate withdrawal requests (can be pending or finalized)
341381
/// @param requests Array of requests to invalidate
342382
/// @return invalidatedRequestIds Array of request IDs that were invalidated
343383
function invalidateRequests(WithdrawRequest[] calldata requests) external onlyRequestManager returns (bytes32[] memory invalidatedRequestIds) {
344384
invalidatedRequestIds = new bytes32[](requests.length);
345385
for (uint256 i = 0; i < requests.length; ++i) {
346386
bytes32 requestId = keccak256(abi.encode(requests[i]));
347-
if (!_withdrawRequests.contains(requestId)) revert RequestNotFound();
387+
388+
// Verify request exists (either in pending or finalized set based on amountLocked)
389+
bool isPending = requests[i].amountLocked == 0 && _withdrawRequests.contains(requestId);
390+
bool isFinalized = requests[i].amountLocked > 0 && _finalizedRequests.contains(requestId);
391+
if (!isPending && !isFinalized) revert RequestNotFound();
348392

349393
_cancelWithdrawRequest(requests[i]);
350394
invalidatedRequestIds[i] = requestId;
@@ -376,7 +420,7 @@ contract PriorityWithdrawalQueue is
376420

377421
if (beforeEEthShares - eEthSharesMoved != eETH.shares(address(this))) revert InvalidEEthSharesAfterRemainderHandling();
378422

379-
emit RemainderHandled(uint96(eEthAmountToTreasury), uint96(liquidityPool.amountForShare(eEthSharesToBurn)));
423+
emit RemainderHandled(uint96(eEthAmountToTreasury), uint96(eEthSharesToBurn));
380424
}
381425

382426
/// @notice Update the share remainder split to treasury
@@ -492,7 +536,8 @@ contract PriorityWithdrawalQueue is
492536
amountOfEEth: amountOfEEth,
493537
shareOfEEth: shareOfEEth,
494538
nonce: requestNonce,
495-
creationTime: timeNow
539+
creationTime: timeNow,
540+
amountLocked: 0 // Set to 0 for pending requests
496541
});
497542

498543
requestId = keccak256(abi.encode(req));
@@ -510,21 +555,17 @@ contract PriorityWithdrawalQueue is
510555
);
511556
}
512557

513-
function _dequeueWithdrawRequest(WithdrawRequest calldata request) internal returns (bytes32 requestId) {
514-
requestId = keccak256(abi.encode(request));
515-
bool removedFromSet = _withdrawRequests.remove(requestId);
516-
if (!removedFromSet) revert RequestNotFound();
517-
518-
_finalizedRequests.remove(requestId);
519-
}
520-
521558
function _cancelWithdrawRequest(WithdrawRequest calldata request) internal returns (bytes32 requestId) {
522559
requestId = keccak256(abi.encode(request));
523560

524-
// Check if finalized BEFORE dequeue (dequeue removes from finalized set)
525-
bool wasFinalized = _finalizedRequests.contains(requestId);
561+
// Check if this is a finalized request (amountLocked > 0) or pending request (amountLocked = 0)
562+
bool isFinalized = request.amountLocked > 0;
526563

527-
_dequeueWithdrawRequest(request);
564+
if (isFinalized) {
565+
if (!_finalizedRequests.remove(requestId)) revert RequestNotFound();
566+
} else {
567+
if (!_withdrawRequests.remove(requestId)) revert RequestNotFound();
568+
}
528569

529570
// Calculate current value of shares
530571
uint256 amountForShares = liquidityPool.amountForShare(request.shareOfEEth);
@@ -542,8 +583,9 @@ contract PriorityWithdrawalQueue is
542583
: 0;
543584
totalRemainderShares += uint96(remainder);
544585

545-
if (wasFinalized) {
546-
liquidityPool.reduceEthAmountLockedForPriorityWithdrawal(uint128(amountToReturn));
586+
if (isFinalized) {
587+
// Use the amountLocked from the request struct
588+
liquidityPool.reduceEthAmountLockedForPriorityWithdrawal(request.amountLocked);
547589
}
548590

549591
IERC20(address(eETH)).safeTransfer(request.user, amountToReturn);
@@ -554,19 +596,18 @@ contract PriorityWithdrawalQueue is
554596
function _claimWithdraw(WithdrawRequest calldata request) internal {
555597
if (request.user != msg.sender) revert NotRequestOwner();
556598

599+
// Finalized requests must have amountLocked > 0
600+
if (request.amountLocked == 0) revert RequestNotFinalized();
601+
557602
bytes32 requestId = keccak256(abi.encode(request));
558603

559-
if (!_withdrawRequests.contains(requestId)) revert RequestNotFound();
604+
// Finalized requests are only in _finalizedRequests (removed from _withdrawRequests during fulfillment)
560605
if (!_finalizedRequests.contains(requestId)) revert RequestNotFinalized();
561606

562-
uint256 amountForShares = liquidityPool.amountForShare(request.shareOfEEth);
563-
uint256 amountToWithdraw = request.amountOfEEth < amountForShares
564-
? request.amountOfEEth
565-
: amountForShares;
607+
uint256 amountToWithdraw = request.amountLocked;
566608

567609
uint256 sharesToBurn = liquidityPool.sharesForWithdrawalAmount(amountToWithdraw);
568610

569-
_withdrawRequests.remove(requestId);
570611
_finalizedRequests.remove(requestId);
571612

572613
// Track remainder (difference between original shares and burned shares)
@@ -595,20 +636,23 @@ contract PriorityWithdrawalQueue is
595636
/// @param _shareOfEEth The share of eETH
596637
/// @param _nonce The request nonce
597638
/// @param _creationTime The creation timestamp
639+
/// @param _amountLocked The amount locked (0 for pending, >0 for finalized)
598640
/// @return requestId The keccak256 hash of the request
599641
function generateWithdrawRequestId(
600642
address _user,
601643
uint96 _amountOfEEth,
602644
uint96 _shareOfEEth,
603645
uint32 _nonce,
604-
uint32 _creationTime
646+
uint32 _creationTime,
647+
uint96 _amountLocked
605648
) public pure returns (bytes32 requestId) {
606649
WithdrawRequest memory req = WithdrawRequest({
607650
user: _user,
608651
amountOfEEth: _amountOfEEth,
609652
shareOfEEth: _shareOfEEth,
610653
nonce: _nonce,
611-
creationTime: _creationTime
654+
creationTime: _creationTime,
655+
amountLocked: _amountLocked
612656
});
613657
requestId = keccak256(abi.encode(req));
614658
}
@@ -617,13 +661,7 @@ contract PriorityWithdrawalQueue is
617661
/// @param request The withdrawal request
618662
/// @return requestId The keccak256 hash of the request
619663
function getRequestId(WithdrawRequest calldata request) external pure returns (bytes32) {
620-
return generateWithdrawRequestId(
621-
request.user,
622-
request.amountOfEEth,
623-
request.shareOfEEth,
624-
request.nonce,
625-
request.creationTime
626-
);
664+
return keccak256(abi.encode(request));
627665
}
628666

629667
/// @notice Get all active request IDs
@@ -654,13 +692,12 @@ contract PriorityWithdrawalQueue is
654692

655693
/// @notice Get the claimable amount for a request
656694
/// @param request The withdrawal request
657-
/// @return The claimable ETH amount
695+
/// @return The claimable ETH amount (locked at fulfillment time)
658696
function getClaimableAmount(WithdrawRequest calldata request) external view returns (uint256) {
659697
bytes32 requestId = keccak256(abi.encode(request));
660698
if (!_finalizedRequests.contains(requestId)) revert RequestNotFinalized();
661699

662-
uint256 amountForShares = liquidityPool.amountForShare(request.shareOfEEth);
663-
return request.amountOfEEth < amountForShares ? request.amountOfEEth : amountForShares;
700+
return request.amountLocked;
664701
}
665702

666703
/// @notice Get the total number of active requests

src/interfaces/IPriorityWithdrawalQueue.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ interface IPriorityWithdrawalQueue {
88
/// @param shareOfEEth eETH shares at time of request
99
/// @param nonce Unique nonce to prevent hash collisions
1010
/// @param creationTime Timestamp when request was created
11+
/// @param amountLocked ETH amount locked at fulfillment
1112
struct WithdrawRequest {
1213
address user; // 20 bytes
1314
uint96 amountOfEEth; // 12 bytes | Slot 1 = 32 bytes
1415
uint96 shareOfEEth; // 12 bytes
1516
uint32 nonce; // 4 bytes
16-
uint32 creationTime; // 4 bytes | Slot 2 = 20 bytes
17+
uint32 creationTime; // 4 bytes
18+
uint96 amountLocked; // 12 bytes | Slot 2 = 32 bytes
1719
}
1820

1921
struct PermitInput {

0 commit comments

Comments
 (0)