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#L37-L39 https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L11-L13
Vulnerability details
Impact
High impact There are some security concerns that can lead a user to get more shares than it would
Security concerns
There are some security concerns that have not been taken in account with this chainlink oracle implementation.
Usage of a deprecated chainlink price feed implementation
This is an old implementation of the chainlink price feeds. If this implementation is finally used, chainlink can at some point stop feeding prices into this deprecated contracts and leave the protocol without any data price. It is highly recommended to use the AggregatorV3 implementation.
There is no check for stale prices Chainlink data feeds can return stale pricing data for some reasons. You can check out some of them here: https://ethereum.stackexchange.com/questions/133242/how-future-resilient-is-a-chainlink-price-feed/133843#133843 To solve this problem, it is recommended to store the heartbeat of the specific price feed and check the latest update of it. Notice that each price feed can have different heartbeats, for example stETH/ETH has 12 hour heartbeat whereas rETH/ETH has 18 hour. Example:
Price changes can be frontrunned A user can monitor when a price feed is about to get updated and frontrun it by depositing assets. Imagine the following situations:
Situation with frontrunning:
Timestamp 1 Protocol has: 100 stETH at price 100 ETH 100 rETH at price 100 ETH 100 cbETH at price 100 ETH
User has: 10 rETH at price 100 ETH => 1000 worth of ETH
Now the price of stETH is about to get updated and user knows the next price because is monitoring the change. If the price is going to drop, the user will obviously not going to deposit anything because he will lose value. However, if he sees that the value of the reserves of the protocol are gonna increase, if he deposit assets before hand, he will get the benefit from this increase. Imagine that the price of stETH will increase to 200 ETH The user deposits this amount before and will get 33 shares of the pool Now the price gets updated
Timestamp 2 Protocol has: 100 stETH at price 200 ETH 110 rETH at price 100 ETH 100 cbETH at price 100 ETH
User's shares are worth approximately 1309 ETH. User obtained 309 ETH of profit without taking any risk
Situation without frontrunning: If the user would decided to deposit his rETH when the price of stETH has already been changed this would be the situation:
Timestamp 2 Protocol has: 100 stETH at price 200 ETH 100 rETH at price 100 ETH 100 cbETH at price 100 ETH
User has: 10 rETH at price 100 ETH => 1000 worth of ETH
User will receive only 25 shares of the pool. Hence, he will not get any benefit from the price increase. This issue can be mitigated by locking
depositAsset
function to some amount of timestamp before the heartbeat.decimals
function in the chainlink price feed and adjust the result of the specific asset to 18 decimals. Example:Tools Used
Manual Review
Recommended Mitigation Steps
For the function to obtain the asset price:
In this function, the decimals concern and the stale price check is solved. To solve frontrunning, we need to add the following function and variables in the oracle:
This function checks if any of the prices of the assets is going to be updated in the following 30 seconds. If any of the price feeds is going to be updated within this period, it reverts. Notice that the locking time can be any amount of time, so it's up to you. I would recommend to make if a number greater than 15 seconds because the current average Ethereum Mainnet block time is about 12 seconds. Now it is only needed to add this check inside the
depositAsset
function in LRTDepositPool.sol.Assessed type
Oracle