Perfect Opal Boar
Medium
The _deposit
function in LendingPool
contains a critical logic flaw in handling first-time deposits. The function performs validation before deducting the MINIMUM_YTOKEN_AMOUNT
but fails to revalidate after the deduction, leading to silent failures and locked user funds.
https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/LendingPool.sol#L76 https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/LendingPool.sol#L104
The issue stems from three key issues in the deposit logic.
require(yTokenAmount > MINIMUM_YTOKEN_AMOUNT, "deposit is dust amount");
The check is performed before the MINIMUM_YTOKEN_AMOUNT
deduction for first deposits.
if (IyToken(reserve.yTokenAddress).totalSupply() == 0) {
IyToken(reserve.yTokenAddress).mint(DEAD_ADDRESS, MINIMUM_YTOKEN_AMOUNT);
yTokenAmount -= MINIMUM_YTOKEN_AMOUNT;
}
The deduction occurs without subsequent validation.
uint256 exchangeRate = reserve.reserveToYTokenExchangeRate();
yTokenAmount = amount * exchangeRate / (PRECISION);
Exchange rate fluctuations could affect the final yTokenAmount
.
This may cause, if the deposit is too small after the MINIMUM_YTOKEN_AMOUNT
reduction, the transaction will fail without a clear error message.
- Initial State:
•
MINIMUM_YTOKEN_AMOUNT
= 1000 • Exchange rate = 1:1 (PRECISION
) - User Attempts First Deposit:
depositAmount = 1500 tokens
yTokenAmount = 1500 * PRECISION / PRECISION = 1500
- Sequence of Events:
• Initial check passes: 1500 > 1000 (
MINIMUM_YTOKEN_AMOUNT
) • System identifies first deposit (totalSupply = 0
) • Mints 1000 tokens toDEAD_ADDRESS
• Deducts 1000 fromyTokenAmount
• FinalyTokenAmount
= 500 (below minimum) • Transaction fails silently or produces unusable tokens - Result: • User's transaction fails or results in unusable amounts
DOS on first deposits within certain ranges
Implement proper validation flow.
function _deposit(address asset, uint256 amount, address onBehalfOf) internal returns (uint256 yTokenAmount) {
DataTypes.ReserveData storage reserve = getReserve(asset);
// Calculate initial amounts
uint256 exchangeRate = reserve.reserveToYTokenExchangeRate();
yTokenAmount = amount * exchangeRate / PRECISION;
// Check if first deposit
bool isFirstDeposit = IyToken(reserve.yTokenAddress).totalSupply() == 0;
// Calculate minimum required amount
uint256 requiredAmount = isFirstDeposit
? MINIMUM_YTOKEN_AMOUNT * 2
: MINIMUM_YTOKEN_AMOUNT;
// Validate initial amount
require(yTokenAmount >= requiredAmount,
"Deposit too small. Required minimum: {requiredAmount}");
// Process first deposit logic
if (isFirstDeposit) {
IyToken(reserve.yTokenAddress).mint(DEAD_ADDRESS, MINIMUM_YTOKEN_AMOUNT);
yTokenAmount -= MINIMUM_YTOKEN_AMOUNT;
// Revalidate after deduction
require(yTokenAmount >= MINIMUM_YTOKEN_AMOUNT,
"Remaining amount after initial mint too small");
}
// Complete deposit
IERC20(asset).safeTransferFrom(msg.sender, reserve.yTokenAddress, amount);
IyToken(reserve.yTokenAddress).mint(onBehalfOf, yTokenAmount);
emit Deposit(asset, msg.sender, onBehalfOf, amount, yTokenAmount);
return yTokenAmount;
}