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

Withdrawals Can Force Unnecessary Losses on Withdrawers

Summary

The _freeFunds() function in @src/StrategyMainnet.sol (the same for all strategies) incorrectly handles cases where the requested withdrawal amount exceeds the available unexchanged balance, forcing Withdrawers to take losses when they could be avoided. This violates the specs outlined in the Yearn V3 specifications.

When a user requests to withdraw an amount larger than what's currently unexchanged in the transmuter, the function simply withdraws whatever is available (totalAvailable) and returns. The difference between the requested amount and what was actually withdrawn (_amount - totalAvailable) is counted as a loss and passed on to the withdrawer.

However, the strategy has additional ways to free funds that aren't being utilized:

  1. The strategy can claim WETH from the transmuter via transmuter.claim()

  2. The claimed WETH can be swapped back to alETH using the Curve router

By not attempting these additional steps before forcing a loss, the strategy unnecessarily penalizes users during withdrawals. This is particularly problematic because:

  1. The strategy lacks an _emergencyWithdraw() function, leaving users no alternative withdrawal path

  2. The Yearn V3 specifications explicitly recommend reverting in illiquidity scenarios rather than forcing losses

Proof of Concept

  1. Alice deposits 100 alETH into the strategy

  2. The strategy deposits these into the transmuter, converting them to WETH over time

  3. After some time:

    • 30 alETH remains unexchanged

    • 70 WETH is claimable from the transmuter

  4. Alice attempts to withdraw 100 alETH

  5. _freeFunds() only withdraws 30 alETH from unexchanged balance

  6. The remaining 70 alETH is counted as a loss to Alice

  7. Meanwhile, 70 WETH remains claimable in the transmuter that could have been claimed and swapped back to alETH

Recommended mitigation steps

Either:

1 Revert when there are insufficient unexchanged funds:

function _freeFunds(uint256 _amount) internal override {
uint256 totalAvailable = transmuter.getUnexchangedBalance(address(this));
require(_amount <= totalAvailable, "Insufficient liquidity");
transmuter.withdraw(_amount, address(this));
}

2 Or implement the full withdrawal path using claiming and swaping funds from transmutter

Updates

Lead Judging Commences

inallhonesty Lead Judge
6 months ago

Appeal created

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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