When user calls gm
and the call for the oracle fails, it will return 0 for the deltaB value and this will impact the following state variables:
s.season.abovePeg
- will be set to false on stepSun
because deltaB == 0
s.f.soil
- will be set to zero also on stepSun
because deltaB == 0
sowWithMin
utilizes the s.f.soil
as a slippage mechanism, but previously when oracle failed on gm
call, the following happened on stepSun:
As this value is zero, the sow
function will prevent any call to sow beans as the value to be minted cannot surpass zero:
Transaction will revert due to Field: Soil Slippage
The peg mechanism will suffer DoS until the next successful call of sunrise + oracle returns the correct data.
Users will not be able to sow their beans when in fact they should because the system will think that there is no soil available.
Storage variables will impact the next season peg as their value was incorrectly updated(soil being set to zero, protocol being set to below peg when it could be above the peg, the temperature changed to an incorrect value due to invalid calcId impacted by deltaB == 0).
Prepare the environment to work with Foundry + Updated Mocks
https://gist.github.com/h0lydev/fcdb00c797adfdf8e4816031e095fd6c
Make sure to have the mainnet forked through Anvil: anvil --fork-url https://rpc.ankr.com/eth
Create the Season.t.sol
file under the folder foundry
and paste the code below. Then run forge test --match-contract SeasonTest -vv
.
Output:
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished
Users are blocked from sowing their beans.
Here you can have a more detailed output with several logs across the contracts showing the system is affected by it:
https://gist.github.com/h0lydev/4ca0f742839f588d213da399b7ced17f
Manual Review & Foundry
It is noticed that the developers have the intention of never reverting the sunrise function to decrease the risk of depeg and breaking the incentive for users calling it. But at the same time, those state variables shouldn't be updated as if the system is working correctly because they will impact the next season as stated in this finding.
It is tricky to propose a simple fix to the problem without impacting the system as a whole. Here are a few ideas that could be used:
(Recommended) An effective solution could be to store the latest response from Chainlink and in case it fails and the timeout(a limit that you can be added to accept a previous response from the oracle) is not reached yet, the protocol could use the previous response. Liquity Protocol uses this approach, an example here: https://github.com/liquity/dev/blob/main/packages/contracts/contracts/PriceFeed.sol
This solution will be effective for the protocol because the oracle is also called in different places like when minting fertilizers(getMintFertilizerOut
), getting the well price(getRatiosAndBeanIndex
), and getConstantProductWell
. As the oracle is used along the protocol in many places, the latest successful price
would be often up-to-date and within the limit time defined to use the previous price when the chainlink oracle fails.
Alternatives:
Considering revert the function as chainlink failing doesn't happen quite often. And if it does, it is because chainlink is broken, so that could be taken into consideration.(A keeper could also be taken in place to ensure gm
is always called)
Rethink how to handle/recover from chainlink oracle failing.(like preventing storage variables from being updated/adding checks in place that only updated them when necessary)
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.