Closed c4-submissions closed 11 months ago
raymondfam marked the issue as sufficient quality report
raymondfam marked the issue as duplicate of #32
raymondfam marked the issue as not a duplicate
raymondfam marked the issue as duplicate of #843
fatherGoose1 marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L38 https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L46
Vulnerability details
ChainlinkPriceOracle
should use theupdatedAt
value from thelatestRoundData()
function to make sure that the latest answer is recent enough to be used.LRTOracle
should implement price freshness validation for any oracle used in the protocol.Impact
A stale price can cause the malfunction of the
LRTDepositPool.sol#depositAsset()
function:When a user calls the
LRTDepositPool.sol#depositAsset()
function, if the price of the asset has dropped, and the oracle price feed is stale, an outdated price will be used and the user will get more rsETH tokens than he should.Proof of Concept
In the current implementation of
ChainlinkPriceOracle.sol#getAssetPrice()
, there is no price staleness check. This could lead to stale prices being used.If the market price of the asset drops very quickly ("flash crashes"), and Chainlink's feed does not get updated in time, the smart contract will continue to believe the asset is worth more than the market value.
A user can take advantage of the oracle price feed staleness in the following scenario:
The user calls the
LRTDepositPool.sol#depositAsset()
function. The asset to stake price has dropped, but the oracle price feed has not updated the answer yet._mintRsETH()
is called inside thedepositAsset()
functionhttps://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L141
uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);
LRTDepositPool.sol#_mintRsETH()
function calculates the rsETH amount to mint based on asset amount and asset exchange rates from the oracle.https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L152
(rsethAmountToMint) = getRsETHAmountToMint(_asset, _amount);
_mintRsETH()
callsgetRsETHAmountToMint()
to view the amount of rsETH to minthttps://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L109
rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
LRTOracle.sol
lacks price freshness validation in thegetAssetPrice()
functionhttps://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L45-L47
ChainlinkPriceOracle.sol#getAssetPrice()
also lacks price freshness validationhttps://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L37-L39
Due to the oracle price feed staleness, the protocol gets an outdated answer (price higher than the actual price)
Tools Used
Manual Review
Recommended Mitigation Steps
ChainlinkPriceOracle.sol#getAssetPrice()
MAX_STALE_INTERVAL can be based on the
heartbeat
of the feed. Theheartbeat
refers to the maximum expected time interval between updates to the price data.Update
ChainlinkPriceOracle.sol#AggregatorInterface
to support thelatestRoundData()
method.LTROracle.sol
Assessed type
Oracle