Skip to content

Latest commit

 

History

History
83 lines (54 loc) · 3.25 KB

043.md

File metadata and controls

83 lines (54 loc) · 3.25 KB

Delightful Walnut Okapi

High

The user deposit transaction was not updated in the _deposit function.

Summary

The _deposit function transfers assets before updating the underlyingBalance of the reserve, leading to an inconsistent state if the steps after the transfer fail. An attacker could exploit this to create a discrepancy between the actual balance and the recorded balance.

Root Cause

In the _deposit function of the Vault contract, the command IERC20(asset).safeTransferFrom(msg.sender, reserve.yTokenAddress, amount) is executed before updating reserve.underlyingBalance += amount. If the steps after the transfer fail (e.g., minting yTokens is reverted), underlyingBalance will not be updated, while the assets have already been transferred to reserve.yTokenAddress. This creates an inconsistent state where the reserve holds the assets but does not record the corresponding balance.

https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/LendingPool.sol#L104-L132

    function _deposit(address asset, uint256 amount, address onBehalfOf) internal returns (uint256 yTokenAmount) {
        DataTypes.ReserveData storage reserve = getReserve(asset);
        require(!reserve.getFrozen(), "reserve frozen");
        // update states
        reserve.updateState();

        // validate
        reserve.checkCapacity(amount);

        uint256 exchangeRate = reserve.reserveToYTokenExchangeRate();

@>      IERC20(asset).safeTransferFrom(msg.sender, reserve.yTokenAddress, amount);

        // Mint yTokens for the user
        yTokenAmount = amount * exchangeRate / (PRECISION);

        require(yTokenAmount > MINIMUM_YTOKEN_AMOUNT, "deposit is dust amount");
        if (IyToken(reserve.yTokenAddress).totalSupply() == 0) {
            // Burn the first 1000 yToken, to defend against lp inflation attacks
            IyToken(reserve.yTokenAddress).mint(DEAD_ADDRESS, MINIMUM_YTOKEN_AMOUNT);

            yTokenAmount -= MINIMUM_YTOKEN_AMOUNT;
        }

        IyToken(reserve.yTokenAddress).mint(onBehalfOf, yTokenAmount);
@>      reserve.underlyingBalance += amount;
        // update the interest rate after the deposit
        reserve.updateInterestRates();
    }

Internal Pre-conditions

  • The _deposit function is called with valid parameters.
  • The reserve has a valid yTokenAddress.
  • The amount is greater than 0.

External Pre-conditions

  • The user has approved IERC20(asset) to perform transferFrom.
  • msg.sender has the amount of asset in their wallet.

Attack Path

  • The attacker submits a deposit transaction with a large amount.
  • safeTransferFrom successfully transfers the amount of assets from msg.sender to reserve.yTokenAddress.
  • The next step (e.g., minting yTokens) fails due to a logic error (e.g., totalSupply exceeding the limit).
  • The _deposit function reverts, but underlyingBalance is not updated, while reserve.yTokenAddress has already received the amount of assets.

Impact

The reserve holds assets but does not record the corresponding balance

PoC

No response

Mitigation

Update underlyingBalance before executing transferFrom.

reserve.underlyingBalance += amount;
IERC20(asset).safeTransferFrom(msg.sender, reserve.yTokenAddress, amount);