Skip to content

Latest commit

 

History

History
119 lines (71 loc) · 3.05 KB

040.md

File metadata and controls

119 lines (71 loc) · 3.05 KB

Ancient Tangelo Canary

Medium

Manipulable yToken Exchange Rate

Summary

Vulnerable Functions

reserveToYTokenExchangeRate yTokenToReserveExchangeRate

Current Implementation (Vulnerable)

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;

}

function yTokenToReserveExchangeRate(DataTypes.ReserveData storage reserve) external view returns (uint256) { (uint256 totalLiquidity,) = totalLiquidityAndBorrows(reserve); uint256 totalYTokens = IERC20(reserve.yTokenAddress).totalSupply();

if (totalYTokens == 0 || totalLiquidity == 0) {
    return PRECISION;
}
return totalLiquidity * PRECISION / totalYTokens;

}

Problem: yToken Supply Can Be Manipulated

The exchange rate is derived using totalSupply() of yToken, which is not controlled inside this contract.

An attacker can manipulate yToken supply by:

Depositing liquidity when totalYTokens is low. Withdrawing liquidity at a different exchange rate, causing loss to the protocol. Attack Scenario (Minting More Than Deposited) Assume totalLiquidity = 1,000 DAI, and totalYTokens = 1,000.

Exchange Rate: 1 yToken = 1 DAI Attacker deposits 1 DAI. Exchange Rate stays 1 yToken = 1 DAI. Attacker gets 1 yToken.

Attacker manipulates liquidity (e.g., flash loan reduces totalLiquidity).

If totalLiquidity drops to 500 DAI, but totalYTokens = 1,001, the exchange rate changes.

When burning 1 yToken, attacker might receive more than 1 DAI. Repeating this across multiple transactions drains protocol reserves.

Root Cause

https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/libraries/ReserveLogic.sol#L78

Internal Pre-conditions

none

External Pre-conditions

none

Attack Path

none

Impact

Manipulable yToken Exchange Rate

PoC

No response

Mitigation

Instead of relying on IERC20(yTokenAddress).totalSupply(), store totalYTokens in ReserveData and update it internally.

Improved reserveToYTokenExchangeRate

function reserveToYTokenExchangeRate(DataTypes.ReserveData storage reserve) internal view returns (uint256) { (uint256 totalLiquidity,) = totalLiquidityAndBorrows(reserve); uint256 totalYTokens = reserve.totalYTokens; // Use internal tracking

if (totalYTokens == 0 || totalLiquidity == 0) {
    return PRECISION;
}
return totalYTokens * PRECISION / totalLiquidity;

}

Improved yTokenToReserveExchangeRate

function yTokenToReserveExchangeRate(DataTypes.ReserveData storage reserve) external view returns (uint256) { (uint256 totalLiquidity,) = totalLiquidityAndBorrows(reserve); uint256 totalYTokens = reserve.totalYTokens; // Use internal tracking

if (totalYTokens == 0 || totalLiquidity == 0) {
    return PRECISION;
}
return totalLiquidity * PRECISION / totalYTokens;

}