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

Strategy Lose of Profits Due to Immediate Deposit of Premium into Transmuter Without Keeping Records of Profit

Summary

In StrategyMainnet, StrategyArb, and StrategyOp, the claimAndSwap function immediately deposits all received alETH (including the premium/profit) back into the transmuter, preventing the strategy from recording any profit. This leads to inaccurate profit reporting.

Vulnerability Details

The claimAndSwap function claims WETH, swaps it for alETH at a premium, but then deposits all received alETH back into the transmuter:

function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
require(_minOut > _amountClaim, "minOut too low");
// Perform swap
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
// @audit - Immediately deposits all alETH including profit
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

The issue arises because:

  1. The strategy swaps WETH for alETH at a premium (profit)

  2. Instead of keeping the premium as profit, all alETH is deposited into the transmuter

  3. And, during withdrawal the TokenizedStrategy.solaccount for idle by checking the balance of asset which means any directly transferred profit will be seen as idle cash.

    uint256 idle = _asset.balanceOf(address(this));
    uint256 loss;
    // Check if we need to withdraw funds.
    if (idle < assets) {
    // Tell Strategy to free what we need.
    unchecked {
    IBaseStrategy(address(this)).freeFunds(assets - idle);
    }
    // Return the actual amount withdrawn. Adjust for potential under withdraws.
    idle = _asset.balanceOf(address(this));
    // If we didn't get enough out then we have a loss.
    if (idle < assets) {
    unchecked {
    loss = assets - idle;
    }
    // If a non-default max loss parameter was set.
    if (maxLoss < MAX_BPS) {
    // Make sure we are within the acceptable range.
    require(
    loss <= (assets * maxLoss) / MAX_BPS,
    "too much loss"
    );
    }
    // Lower the amount to be withdrawn.
    assets = idle;
    }
    }
  4. When _harvestAndReport() is called, it only sees:

    • Unexchanged balance in transmuter

    • Current alETH balance (which is 0 since all was deposited)

    • Underlying balance

Impact

Loss of Profit for strategy users and protocol

Impact = High

Likelihood = High

Tools Used

Manual

Recommendations

Modify claimAndSwap to retain the premium and exclude totalProfitsfrom availableWithdrawLimit()

uint256 public totalProfits;
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
uint256 profit = balAfter - balBefore - _amountClaim;
totalProfits += profit;
// Only deposit the _amountClaim since this is an arbitrage
transmuter.deposit(_amountClaim, address(this));
}
function _harvestAndReport()
internal
override
returns (uint256 _totalAssets)
{
uint256 unexchanged = transmuter.getUnexchangedBalance(address(this));
_totalAssets = unexchanged + asset.balanceOf(address(this)) + underlying.balanceOf(address(this)) + totalProfits;
totalProfits = 0; // reset total profit
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
11 months ago

Appeal created

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

Support

FAQs

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