Summary
The irregularity occurs in the withdrawal batching mechanism of the contract where old WithdrawalBatchIdCutoff
values can be incorrectly parsed or utilized in getBatchIds
call.
Calling this function without updating the withdrawalBatchIdCutoff
variable will lead to false batch id parsing.
Vulnerability Details
The contract manages withdrawals using a batching mechanism, where withdrawals are grouped into batches identified by batch IDs.
These batch IDs are crucial for tracking and processing withdrawals accurately.
However, the issue is in where old WithdrawalBatchIdCutoff
values can be parsed or used if there has been a call to _finalizeWithdrawals
without properly updating the batch IDs. This can result in outdated or incorrect batch IDs being parsed in getBatchIds
call.
Contract: WithdrawalPool.sol
152: function getBatchIds(uint256[] memory _withdrawalIds) public view returns (uint256[] memory) {
153: uint256[] memory batchIds = new uint256[]();
154:
155: for (uint256 i = 0; i < _withdrawalIds.length; ++i) {
156: uint256 batchId;
157: uint256 withdrawalId = _withdrawalIds[i];
158:
159: >>> for (uint256 j = withdrawalBatchIdCutoff; j < withdrawalBatches.length; ++j) {
160: uint256 indexOfLastWithdrawal = withdrawalBatches[j].indexOfLastWithdrawal;
161:
162: if (withdrawalId <= indexOfLastWithdrawal) {
163: batchId = j;
164: break;
165: }
166: }
167:
168: batchIds[i] = batchId;
169: }
170:
171: return batchIds;
172: }
As can be seen on L:159, withdrawalBatchIdCutoff
is used directly and assumed to be up to date which might not be the case since updateWithdrawalBatchIdCutoff
is not triggered anywhere before.
The variable only updated in updateWithdrawalBatchIdCutoff
:
Contract: WithdrawalPool.sol
370: function updateWithdrawalBatchIdCutoff() external {
371: uint256 numWithdrawals = queuedWithdrawals.length;
372: uint256 newWithdrawalIdCutoff = withdrawalIdCutoff;
373:
374:
375: for (uint256 i = newWithdrawalIdCutoff; i < numWithdrawals; ++i) {
376: newWithdrawalIdCutoff = i;
377:
378: Withdrawal memory withdrawal = queuedWithdrawals[i];
379: if (withdrawal.sharesRemaining != 0 || withdrawal.partiallyWithdrawableAmount != 0) {
380: break;
381: }
382: }
383:
384: uint256 numBatches = withdrawalBatches.length;
385: uint256 newWithdrawalBatchIdCutoff = withdrawalBatchIdCutoff;
386:
387:
388: for (uint256 i = newWithdrawalBatchIdCutoff; i < numBatches; ++i) {
389: if (withdrawalBatches[i].indexOfLastWithdrawal >= newWithdrawalIdCutoff) {
390: break;
391: }
392:
393: newWithdrawalBatchIdCutoff = i;
394: }
395:
396: withdrawalIdCutoff = uint128(newWithdrawalIdCutoff);
397: withdrawalBatchIdCutoff = uint128(newWithdrawalBatchIdCutoff);
398: }
and not explicitly called anywhere in the codebase.
Impact
Incorrect batch Id parsing in view functions
Tools Used
Manual Review
Recommendations
_finalizeWithdrawals
could call updateWithdrawalBatchIdCutoff
before syncing the state.