Platform: Code4rena
Start Date: 01/07/2022
Pot Size: $75,000 USDC
Total HM: 17
Participants: 105
Period: 7 days
Judge: Jack the Pug
Total Solo HM: 5
Id: 143
League: ETH
Rank: 23/105
Findings: 2
Award: $577.55
π Selected for report: 1
π Solo Findings: 0
π Selected for report: 0xNineDec
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xdanial, 0xf15ers, Cheeezzyyyy, Chom, Franfran, GalloDaSballo, Green, IllIllI, Meera, Ruhum, bardamu, cccz, codexploder, defsec, hake, hansfriese, horsefacts, hubble, hyh, jonatascm, kebabsec, oyc_109, pashov, rbserver, simon135, tabish, tintin, zzzitron
14.8726 USDC - $14.87
Price data returned by latestRoundData
could be stale. This would create a situation where any contract consuming price information from the configured Chainlink aggregator would rely on an incorrect asset price.
As can be seen in function currentPrice
the answer from feed.latestRoundData()
is assumed to be valid if it is non-zero and returned to the caller. However, aggregator data could be stale and therefore be outdated, which would propagate an incorrect price.
vim
The caller should always check that latestRoundData
is returning sane and up to date values.
Properties updatedAt
and answeredInRound
are of particular interest here. For example, answeredInRound
could be checked against the current roundId
to verify price information has been updated within a certain threshold.
function latestRoundData() public override view checkAccess() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.latestRoundData(); }
#0 - mejango
2022-07-12T18:25:33Z
dup #138
π Selected for report: bardamu
Also found by: GalloDaSballo, berndartmueller, codexploder, horsefacts
562.6794 USDC - $562.68
Call to latestRoundData
could potentially revert and make it impossible to query any prices. Feeds cannot be changed after they are configured (https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L115) so this would result in a permanent denial of service.
Chainlink's multisigs can immediately block access to price feeds at will. Therefore, to prevent denial of service scenarios, it is recommended to query Chainlink price feeds using a defensive approach with Solidityβs try/catch structure. In this way, if the call to the price feed fails, the caller contract is still in control and can handle any errors safely and explicitly.
if (_feed != IJBPriceFeed(address(0))) return _feed.currentPrice(_decimals);
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. Only need the price is needed. (, int256 _price, , , ) = feed.latestRoundData();
Refer to https://blog.openzeppelin.com/secure-smart-contract-guidelines-the-dangers-of-price-oracles/ for more information regarding potential risks to account for when relying on external price feed providers.
vim
Surround the call to latestRoundData()
with try/catch
instead of calling it directly. In a scenario where the call reverts, the catch block can be used to call a fallback oracle or handle the error in any other suitable way.
#0 - jack-the-pug
2022-07-31T12:20:48Z
Good catch! Seems like we should update this function to allow changing the feed contract: