DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: medium
Valid

Unrecoverable WETH Donations will open multiple attack vectors

Summary

The strategy's total asset calculation includes WETH balances without providing any mechanism to handle WETH deposits or withdrawals, enabling a permanent share price inflation attack through direct WETH transfers.

Vulnerability Details

In _harvestAndReport():

_totalAssets = unexchanged + asset.balanceOf(address(this)) + underlyingBalance;

The strategy counts underlyingBalance (WETH) in total assets but:

  1. Has no mechanism to handle direct WETH deposits

  2. Cannot process WETH withdrawals

  3. Only handles WETH through transmuter claims and swaps

Attack Scenario :

This is a simplified example of the idea of the attack, but better ROI of the loss numbers will be provided

  1. There is one or two people in the strategy each of them deposited 1 alETH

  2. Attacker sees a txn of the keeper calling report()

  3. Attacker frontruns it with calling deposit of 4 alETH and direct donation of 2 wETH

  4. the attacker now owns 67% percent of the value of the pool

  5. report() txn go through and reflect a _totalAssets of 8e18 (2 wETH and 6 alETH).

  6. attacker backruns the report() with withdraw() of all his shares.

  7. attacker gets back 5.36 alETH and remains in the strategy 0.64 alETH withdrawable and 2 wETH unwithdrawable (since wETH can't be deployed in _deployFunds() or swapped in any way)

  8. First two users end with 0.82e18 each stuck unwithdrawable funds

Here is an example in which the attacker has no scarified funds
  1. Pre-report state:

Attacker deposits: 10 alETH = 10e18 shares
Attacker donates: 20 WETH
Total asset balance: 40 e18 equivalent (20 alETH + 20 WETH)
Total asset variable: 20 e18
Total shares: 20e18 (2 users depositing 5 alETH each before the attack)
  1. Share distribution:

Attacker: 10e18 shares (50%)
User1: 5e18 shares (25%)
User2: 5e18 shares (25%)
  1. After report():

Total asset variable: 40 e18 (20 alETH + 20 WETH)
New PPS = Total Assets / Total Shares
PPS = 40e18 / 20e18 = 2
  1. Attacker withdrawal (10e18 shares):

Withdrawal value = 10e18 * 2 = 20e18 alETH
  1. Final state:

Remaining in vault:
- 0 alETH (withdrawable)
- 20 WETH (locked)
Total "value": 20e18
Remaining shares (10e18) owned by User1 & User2
Their theoretical value: 10e18 * 2 = 20e18
Actually withdrawable: 0

Attacker's P&L:

Input: 10 alETH + 20 WETH
Output: 20 alETH
Net loss: 20 WETH

Users' Losses:

Input per user: 5 alETH
Recoverable per user: 0 alETH
Locked per user: 10 WETH equivalent

This scenario is more devastating because:

  1. Users have 100% of their alETH locked (10 alETH total)

  2. Attacker doubles their alETH (+10 alETH profit)

  3. Cost to attacker is only 20 WETH to lock 10 alETH

  4. Users cannot withdraw anything (0 alETH remaining)

The above example will only work if Fees are (0), if not, then attacker will grief users a loss with the same amount he loses

Impact

Medium: Low impact, High severity

  • Permanent share price manipulation

  • Creation of unredeemable "dead shares"

  • Affects all subsequent depositors who receive fewer shares

  • WETH becomes permanently locked in contract

  • Breaks core ERC4626 vault functionality

Tools Used

  • Manual code review

  • Understanding of vault inflation attacks

  • Analysis of TokenizedStrategy share mechanics

Recommendations

  1. Remove underlyingBalance from total assets calculation:

function _harvestAndReport() internal override returns (uint256 _totalAssets) {
uint256 claimable = transmuter.getClaimableBalance(address(this));
uint256 unexchanged = transmuter.getUnexchangedBalance(address(this));
_totalAssets = unexchanged + asset.balanceOf(address(this)) + claimable;
}
  1. Add emergency function to handle accidental WETH transfers:

function rescueTokens(address token, address recipient) external onlyManagement {
require(token != address(asset), "Cannot rescue asset");
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(recipient, balance);
}
Updates

Appeal created

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

_harvestAndReport should not contain the underlying balance to prevent donations having an impact.

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Dormant WETH is not properly treated

Support

FAQs

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