DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Valid

Obtaining the twap price from uniswap v3 oracle for stablecoins will result in a frequent DoS

Summary

When computing the price of a stablecoin by using a uniswap v3 twap implementation, it will result in a DoS for most of the time

Relevant GitHub Links:

https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/main/protocol/contracts/libraries/Oracle/LibUsdOracle.sol#L155-L159

Vulnerability Details

If we look at the getTokenPriceFromExternal function we can see there are 3 different scenarios according to the encodeType we pass in:

function getTokenPriceFromExternal(
address token,
uint256 lookback
) internal view returns (uint256 tokenPrice) {
AppStorage storage s = LibAppStorage.diamondStorage();
Implementation memory oracleImpl = s.sys.oracleImplementation[token];
// If the encode type is type 1, use the default chainlink implementation instead.
// `target` refers to the address of the price aggergator implmenation
if (oracleImpl.encodeType == bytes1(0x01)) {
...
fetches chainlink price feed
...
} else if (oracleImpl.encodeType == bytes1(0x02)) {
// assumes a dollar stablecoin is passed in
// if the encodeType is type 2, use a uniswap oracle implementation.
address chainlinkToken = IUniswapV3PoolImmutables(oracleImpl.target).token0();
chainlinkToken = chainlinkToken == token
? IUniswapV3PoolImmutables(oracleImpl.target).token1()
: token;
tokenPrice = LibUniswapOracle.getTwap(
lookback == 0 ? LibUniswapOracle.FIFTEEN_MINUTES : uint32(lookback),
oracleImpl.target,
chainlinkToken,
token,
uint128(10) ** uint128(IERC20Decimals(token).decimals())
);
// USDC/USD
// call chainlink oracle from the OracleImplmentation contract
Implementation memory chainlinkOracleImpl = s.sys.oracleImplementation[chainlinkToken];
address chainlinkOraclePriceAddress = chainlinkOracleImpl.target;
if (chainlinkOraclePriceAddress == address(0)) {
// use the chainlink registry
chainlinkOraclePriceAddress = ChainlinkPriceFeedRegistry(chainlinkRegistry).getFeed(
chainlinkToken,
0x0000000000000000000000000000000000000348
); // 0x0348 is the address for USD
}
uint256 chainlinkTokenPrice = LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
lookback
);
return tokenPrice.mul(chainlinkTokenPrice).div(1e6);
}
// If the oracle implementation address is not set, use the current contract.
address target = oracleImpl.target;
...
third implementation
...
}

If the encodeType is equal to bytes1(0x01), it fetches the price of the token using a chainlink price feed.
If the encodeType is equal to bytes1(0x02), it is assumed that a stablecoin is passed (according to the comments) and it fetches the twap from uniswap v3. Afterwards, it fetches the price of USDC/USD from a chainlink price feed. However, to do that it passes a 4-hour timeout that will make the result to be most of the time 0 because the heartbeat of the USDC/USD chainlink price feed is 24 hours, so it will exceed the 4 hour timeout and will understand that the price is stale. In this case, the timeout passed should be the one that is compatible with the 24-hour heartbeat price feeds that is stored in the LibChainlinkOracle file:

// timeout for Oracles with a 1 day heartbeat.
uint256 constant FOUR_DAY_TIMEOUT = 345600;

Impact

High
Every price of any token that would be configured to be extracted from a uniswap v3 oracle, will not work for 20 hours each day due to the timeout.

Tools Used

Manual review

Recommendations

Change the timeout to be 4 days instead of 4 hours:

...
uint256 chainlinkTokenPrice = LibChainlinkOracle.getTokenPrice(
chainlinkOraclePriceAddress,
- LibChainlinkOracle.FOUR_HOUR_TIMEOUT,
+ LibChainlinkOracle.FOUR_DAY_TIMEOUT,
lookback
);
...
Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Using 4 hour heartbeat for USDC/USD chainlink price feed which has a 24 h heartbeat

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.