Assessment reports>Concrete>Medium findings>Unclaimed withdrawal requests may be incorrectly finalized
Category: Business Logic

Unclaimed withdrawal requests may be incorrectly finalized

Medium Impact
Medium Severity
Medium Likelihood

Description

When the owner of the vault batch claims withdrawal requests, the function batchClaim determines whether to terminate the current batch claim based on the remaining available assets and the value of maxRequests set by the owner. For available assets, the function prepareWithdrawal is expected to return the newAvaliableAssets as the difference between the avaliableAssets and the withdrawal amount when the withdrawal amount does not exceed the avaliableAssets; otherwise, if the withdrawal amount if greater than avaliableAssets, the returned newAvaliableAssets should be equal to avaliableAssets.

function batchClaim(
    IWithdrawalQueue withdrawalQueue,
    uint256 maxRequests,
    uint256 availableAssets,
    // [...]
) external {
    // [...]

    uint256 max = lastCreatedId < lastFinalizedId + maxRequests ? lastCreatedId : lastFinalizedId + maxRequests;

    for (uint256 i = lastFinalizedId + 1; i <= max;) {
        uint256 newAvailiableAssets =
            claimWithdrawal(i, availableAssets, withdrawalQueue, asset, strategies, parkingLot);
        // slither-disable-next-line incorrect-equality
        if (newAvailiableAssets == availableAssets) break;

        availableAssets = newAvailiableAssets;
        newLastFinalized = i;
        unchecked {
            i++;
        }
    }
    // [...]
}

function claimWithdrawal(
    uint256 _requestId,
    uint256 avaliableAssets,
    IWithdrawalQueue withdrawalQueue,
    // [...]
) public returns (uint256) {
    (address recipient, uint256 amount, uint256 newAvaliableAssets) =
        withdrawalQueue.prepareWithdrawal(_requestId, avaliableAssets);

    if (avaliableAssets != newAvaliableAssets) {
        _withdrawStrategyFunds(amount, recipient, asset, strategies, parkingLot);
    }
    return newAvaliableAssets;
}

However, the function prepareWithdrawal will not initialize the returned avaliableAssets when _avaliableAssets is less than amount.

function prepareWithdrawal(uint256 _requestId, uint256 _avaliableAssets)
    external
    onlyOwner
    returns (address recipient, uint256 amount, uint256 avaliableAssets)
{
    // [...]    

    recipient = request.recipient;

    WithdrawalRequest storage prevRequest = _requests[_requestId - 1];

    amount = request.cumulativeAmount - prevRequest.cumulativeAmount;

    if (_avaliableAssets >= amount) {
        assert(_requestsByOwner[recipient].remove(_requestId));
        avaliableAssets = _avaliableAssets - amount;
        request.claimed = true;
        // [...]
    }
}

Impact

When the withdrawal amount is greater than the remaining available assets, the value of the newAvaliableAssets returned by the function prepareWithdrawal will be zero, which does not equal the avaliableAssets before the call.

With Finding ref, because the transaction will not be reverted when totalWithdrawn < amount_ and amount_ - totalWithdrawn > float, the function _withdrawStrategyFunds may revert the transaction or may be successfully executed when avaliableAssets is insufficient. If the execution succeeds, the receiver can only receive part of the assets they are supposed to receive, and the request that has not been marked as claimed in the WithdrawalQueue contract may be incorrectly recorded as the last finalized ID. As a result, the next batch claim will start processing from this request's following request. This request, which has not been fully claimed, will not be executed again.

Recommendations

Consider initializing the avaliableAssets to the value of _avaliableAssets in the function prepareWithdrawal.

Remediation

This issue has been acknowledged by Blueprint Finance, and a fix was implemented in commit d66613bb.

Zellic © 2025Back to top ↑