Delightful Walnut Okapi
High
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.
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();
}
- The
_deposit
function is called with valid parameters. - The reserve has a valid
yTokenAddress
. - The
amount
is greater than 0.
- The user has approved
IERC20(asset)
to performtransferFrom
. msg.sender
has the amount of asset in their wallet.
- The attacker submits a deposit transaction with a large amount.
safeTransferFrom
successfully transfers the amount of assets frommsg.sender
toreserve.yTokenAddress
.- The next step (e.g., minting yTokens) fails due to a logic error (e.g., totalSupply exceeding the limit).
- The
_deposit
function reverts, butunderlyingBalance
is not updated, whilereserve.yTokenAddress
has already received the amount of assets.
The reserve holds assets but does not record the corresponding balance
No response
Update underlyingBalance
before executing transferFrom
.
reserve.underlyingBalance += amount;
IERC20(asset).safeTransferFrom(msg.sender, reserve.yTokenAddress, amount);