DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: high
Invalid

Implementation of `availableWithdrawLimit` Can Lock User Funds When Needed

Summary

The TokenizedStrategy:withdraw and redeem functions allow users to specify an acceptable loss percentage when withdrawing funds. However, the current implementation of availableWithdrawLimit prevents users from withdrawing funds even when they are willing to accept losses.

Vulnerability Details

Users may need to withdraw their alETH during emergencies or any other case, even at a loss. While users can specify a maxLoss parameter in the withdraw and redeem functions, the StrategyMainnet:availableWithdrawLimit implementation prevents withdrawals beyond the available balance.

function availableWithdrawLimit(
address /*_owner*/
) public view override returns (uint256) {
// NOTE: Withdraw limitations such as liquidity constraints should be accounted for HERE
// rather than _freeFunds in order to not count them as losses on withdraws.
// TODO: If desired implement withdraw limit logic and any needed state variables.
// EX:
// if(yieldSource.notShutdown()) {
// return asset.balanceOf(address(this)) + asset.balanceOf(yieldSource);
// }
// NOTE : claimable balance can only be included if we are actually allowing swaps to happen on withdrawals
//uint256 claimable = transmuter.getClaimableBalance(address(this));
return
asset.balanceOf(address(this)) +
transmuter.getUnexchangedBalance(address(this));
}

The Vulnerability Flow

  1. User deposits 100 alETH to strategy, which is then deposited to transmuter.

  2. After some time, 10 alETH is converted to WETH in transmuter.

  3. User needs to urgently withdraw their 100 alETH and accepts a 10% loss.

  4. Since strategy:unexchangedBalance returns 90 alETH, availableWithdrawLimit also returns 90 alETH.

  5. When availableWithdrawLimit is called in TokenizedStrategy:withdraw, the transaction reverts because 100 (assets) exceeds 90 (available).

POC

The following test demonstrates how users cannot withdraw funds even when willing to accept losses.

Paste this test in Operation.t.sol

//test withdraw
function test_withdraw() public {
uint256 _amount = 100e18;
mintAndDepositIntoStrategy(strategy, user, _amount);
//check the user share token balance is 100e18
console.log("user share token balance", strategy.balanceOf(user));
assertEq(strategy.balanceOf(user), _amount);
// Provide yield to transmuter so that it can be converted to WETH
deployMockYieldToken();
addMockYieldToken();
depositToAlchemist(_amount);
airdropToMockYield(_amount / 2);
vm.roll(1);
harvestMockYield();
vm.prank(address(transmuterKeeper));
transmuterBuffer.exchange(address(underlying));
// Some days passed
skip(7 days);
vm.roll(5);
//Now if the withdraw the amount then i will get 10e18 asset back
vm.prank(user);
// User is willing to take 20% loss, bu he still can't withdraw
// THIS FUNCTION SHOULD PASS
strategy.withdraw(strategy.balanceOf(user), user, user, 2000);
}

Console

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 9.16ms (5.63ms CPU time)
Ran 1 test suite in 1.01s (9.16ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in src/test/Operation.t.sol:OperationTest
[FAIL: revert: ERC4626: withdraw more than max] test_withdraw() (gas: 3636749)
Encountered a total of 1 failing tests, 0 tests succeeded

Impact

While the claimAndSwap function could resolve this issue by freeing up alETH, it only executes when the alETH/WETH exchange rate is favorable enough for keepers to trigger it—an unlikely scenario given the presence of arbitrage bots.

Users cannot withdraw their funds when needed, even if they are willing to accept losses. This is a high-severity issue since user funds remain locked until market conditions favor the keeper calling claimAndSwap to make alETH available for withdrawal.

Tools Used

Manual review and cursor ai

Recommendations

There should be a way for users to withdraw their assets even when the strategy is at a loss.

Updates

Appeal created

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
oxanmol Submitter
7 months ago
inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.