Summary
LibUsdOracle returns extremely wrong price when price fetched from 0x02 type of oracles, i.e uniswap as no price normalization is done for 0x02 type oracles
Vulnerability Details
To understand the workings of getUsdPrice() we need to understand
getTokenPriceFromExternal() which returns value of 1e24/price in case of chainlink oracle
function getTokenPriceFromExternal(
address token,
uint256 lookback
) internal view returns (uint256 tokenPrice) {
....
return
uint256(1e24).div(
LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
lookback
)
);
} else if (oracleImpl.encodeType == bytes1(0x02)) {
....
uint256 chainlinkTokenPrice = LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
lookback
);
return tokenPrice.mul(chainlinkTokenPrice).div(1e6);
}
but in case of uni oracle it directly returns the price
function getUsdPrice(address token, uint256 lookback) internal view returns (uint256) {
if (token == C.WETH) {
uint256 ethUsdPrice = LibEthUsdOracle.getEthUsdPrice(lookback);
if (ethUsdPrice == 0) return 0;
return uint256(1e24).div(ethUsdPrice);
}
if (token == C.WSTETH) {
uint256 wstethUsdPrice = LibWstethUsdOracle.getWstethUsdPrice(lookback);
if (wstethUsdPrice == 0) return 0;
return uint256(1e24).div(wstethUsdPrice);
}
uint256 tokenPrice = getTokenPriceFromExternal(token, lookback);
if (tokenPrice == 0) return 0;
return uint256(1e24).div(tokenPrice);
}
but in get usdPrice it gets divided by 1e24 which leads to totally bogus price, but the chainlink price works fine cause 1e24 / (1e24 / price) = price but
1e24/ (price) != price in case of 0x02(uniswap) oracles
Code snippets-
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/4e0ad0b964f74a1b4880114f4dd5b339bc69cd3e/protocol/contracts/libraries/Oracle/LibUsdOracle.sol#L128-L160
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/4e0ad0b964f74a1b4880114f4dd5b339bc69cd3e/protocol/contracts/libraries/Oracle/LibUsdOracle.sol#L49-L65
Impact
Wrong price
Tools Used
Manual review
Recommendations
uint256 chainlinkTokenPrice = LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
lookback
);
-- return tokenPrice.mul(chainlinkTokenPrice).div(1e6);
++ return 1e24.div(tokenPrice.mul(chainlinkTokenPrice).div(1e6));