Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $149,725 USDC
Total HM: 7
Participants: 52
Period: 21 days
Judge: ronnyx2017
Total Solo HM: 2
Id: 300
League: ETH
Rank: 34/52
Findings: 2
Award: $40.73
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SpicyMeatball
Also found by: 0xBeirao, 7ashraf, LokiThe5th, OMEN, TrungOre, alexzoid, alpha, bdmcbri, ether_sky, fatherOfBlocks, ge6a, hihen, hunter_w3b, jasonxiale, ladboy233, lsaudit, niroh, nobody2018, nonseodion, peanuts, prapandey031, shaka, twcctop, twicek, wangxx2026
19.712 USDC - $19.71
// keccak256("permitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)"); bytes32 private constant _PERMIT_POSITION_MANAGER_TYPEHASH = keccak256( "PermitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)" );
The comment for the typehash of the position manager and the actual code are different. The keccack256 hash in the comment and the hash in the code act on different arguments which will cause the output to be different. The difference in the arguments is in the capitalisation of the first letter.
Change the hash argument to "PermitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)".
Based on a conversation with ChainlinkGod, a prominent advocate of Chainlink, on Twitter, the timeout (heartbeat) of price feeds can be changed. If this occurs it means the PriceFeed contract will be using invalid timeout values and it'll remain in that state since they are immutable. Thus it won't be able to know the right time to put the Chainlink price feeds in frozen states.
Allow the heartbeats to be set by Governance. Since they aren't values enforced at the contract level.
The status values include:
usingFallbackChainlinkUntrusted, bothOraclesUntrusted, usingFallbackChainlinkFrozen, usingChainlinkFallbackUntrusted
When the public status
variable of PriceFeed is called, and it returns any status starting with "using", it should mean that the price returned by the PriceFeed was from the oracle mentioned after using
while the other is untrusted or frozen. E.g if it returns usingFallbackChainlinkFrozen
, it should mean the price returned was from Fallback while Chainlink is frozen. But sometimes it returns the lastGoodPrice instead of the latest price from that oracle.
E.g
if (_fallbackIsFrozen(fallbackResponse)) { _changeStatus(Status.usingFallbackChainlinkUntrusted); return lastGoodPrice; }
From the snippet above the lastGoodPrice is returned but the status is updated to usingFallbackChainlinkUntrusted
. To anyone that calls status
after fetching a price to know the oracle used, he'll be told it was from fallback oracle but it's actually the lastGoodPrice
. There's also no guarantee that when the status is usingFallbackChainlinkUntrusted
, the price will be returned from the Fallback oracle either.
#0 - c4-pre-sort
2023-11-17T14:51:56Z
bytes032 marked the issue as sufficient quality report
#1 - c4-judge
2023-11-28T02:16:07Z
jhsagd76 marked the issue as grade-a
#2 - jhsagd76
2023-11-28T02:19:39Z
Although it includes oos findings, this is a valuable report that is not from a bot.
#3 - jhsagd76
2023-12-08T06:50:19Z
2L
#4 - c4-judge
2023-12-08T06:50:33Z
jhsagd76 marked the issue as grade-b
🌟 Selected for report: DavidGiladi
Also found by: 0xhex, 0xta, Collinsoden, JCK, Madalad, SAQ, SY_S, Sathish9098, cheatc0d3, codeslide, hihen, hunter_w3b, jamshed, lsaudit, mgf15, nonseodion, oualidpro, petrichor, sivanesh_808, tala7985, unique, ybansal2403, zabihullahazadzoi
21.0214 USDC - $21.02
G-1 _formatClAggregateAnswer
can be completely replaced to save gas
The function _formatClAggregateAnswer
can be completely replaced with:
function _formatClAggregateAnswer( int256 _ethBtcAnswer, int256 _stEthEthAnswer, uint8 _ethBtcDecimals, uint8 _stEthEthDecimals ) internal view returns (uint256) { return ( uint256(_ethBtcAnswer) * uint256(_stEthEthAnswer) * EbtcMath.DECIMAL_PRECISION) / 10 ** (_ethBtcDecimals + _stEthEthDecimals); }
The function above simply scales the price first with EbtcMath.DECIMAL_PRECISION
and eliminates the decimals of the stEthEth and ethBtc prices with 10 ** (_ethBtcDecimals + _stEthEthDecimals)
leaving the price to have only EbtcMath.DECIMAL_PRECISION
decimal points.
Gas usage of old implementation: 4519 Gas usage of suggested implementation: 3910
G-2 Price Feed decimal calls can be reduced to 2 from 4 The Price Feed checks the decimals of the two Chainlink price feeds when it tries to get the current and previous round values from the price feeds. This amount to four calls altogether.
decimals()
called twice when previous round data is fetched
function _getPrevChainlinkResponse( uint80 _currentRoundEthBtcId, uint80 _currentRoundStEthEthId ) internal view returns (ChainlinkResponse memory prevChainlinkResponse) { // If first round, early return // Handles revert from underflow in _currentRoundEthBtcId - 1 // and _currentRoundStEthEthId - 1 // Behavior should be indentical to following block if this revert was caught if (_currentRoundEthBtcId == 0 || _currentRoundStEthEthId == 0) { return prevChainlinkResponse; } // Fetch decimals for both feeds: uint8 ethBtcDecimals; uint8 stEthEthDecimals; try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) { // If call to Chainlink succeeds, record the current decimal precision ethBtcDecimals = decimals; } catch { // If call to Chainlink aggregator reverts, return a zero response with success = false return prevChainlinkResponse; } try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) { // If call to Chainlink succeeds, record the current decimal precision stEthEthDecimals = decimals; } catch { // If call to Chainlink aggregator reverts, return a zero response with success = false return prevChainlinkResponse; }
decimals()
called twice when current round data is fetched
function _getCurrentChainlinkResponse() internal view returns (ChainlinkResponse memory chainlinkResponse) { // Fetch decimals for both feeds: uint8 ethBtcDecimals; uint8 stEthEthDecimals; try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) { // If call to Chainlink succeeds, record the current decimal precision ethBtcDecimals = decimals; } catch { // If call to Chainlink aggregator reverts, return a zero response with success = false return chainlinkResponse; } try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) { // If call to Chainlink succeeds, record the current decimal precision stEthEthDecimals = decimals; } catch { // If call to Chainlink aggregator reverts, return a zero response with success = false return chainlinkResponse; }
Since two of the calls will certainly return the same values the call can be done once and cached to save gas spent on the unnecessary static calls
#0 - c4-pre-sort
2023-11-17T14:42:32Z
bytes032 marked the issue as insufficient quality report
#1 - c4-judge
2023-11-28T03:07:12Z
jhsagd76 marked the issue as grade-a
#2 - jhsagd76
2023-12-06T17:01:46Z
2+2+2
6
#3 - c4-judge
2023-12-06T20:54:43Z
jhsagd76 marked the issue as grade-b