Platform: Code4rena
Start Date: 08/06/2022
Pot Size: $115,000 USDC
Total HM: 26
Participants: 72
Period: 11 days
Judge: leastwood
Total Solo HM: 14
Id: 132
League: ETH
Rank: 57/72
Findings: 1
Award: $142.27
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: BowTiedWardens
Also found by: 0x1f8b, 0x29A, 0x52, 0xNazgul, 0xNineDec, 0xf15ers, 0xkatana, 0xmint, Chom, ElKu, Funen, IllIllI, JMukesh, Jujic, Kaiziron, Lambda, MiloTruck, Ruhum, SmartSek, SooYa, TerrierLover, TomJ, WatchPug, Waze, _Adam, asutorufos, auditor0517, bardamu, c3phas, catchup, cccz, ch13fd357r0y3r, cloudjunky, cmichel, cryptphi, csanuragjain, defsec, fatherOfBlocks, hansfriese, hyh, jayjonah8, joestakey, k, kenta, obtarian, oyc_109, robee, sach1r0, shenwilly, simon135, slywaters, sorrynotsorry, tintin, unforgiven, xiaoming90, zzzitron
142.2658 USDC - $142.27
ConnextPriceOracle.sol
The function getPriceFromChainlink()
in ConnextPriceOracle.sol
fetches the latestRoundData()
from a registered aggregator (Chainlink oracle feed) for a specified token. However, neither round completeness or the quoted timestamp are checked to ensure that the reported price is not stale.
Since Connext is creating bridged representations of tokens from other chains, it is vital for the reported prices of tokens to be accurate. While Connext also consults the DEX reported price of the tokens, some pairs with thin liquidity could also return inaccurate prices.
function getPriceFromChainlink(address _tokenAddress) public view returns (uint256) { AggregatorV3Interface aggregator = aggregators[_tokenAddress]; if (address(aggregator) != address(0)) { (, int256 answer, , , ) = aggregator.latestRoundData(); // It's fine for price to be 0. We have two price feeds. if (answer == 0) { return 0; } // Extend the decimals to 1e18. uint256 retVal = uint256(answer); uint256 price = retVal.mul(10**(18 - uint256(aggregator.decimals()))); return price; } return 0; }
Note that the other returned variables roundId
, startedAt
, updatedAt
, and answeredInRound
are omitted from the return result of aggregator.latestRoundData()
.
Add additional validation:
... (uint80 roundID, int256 price, , uint256 updatedAt, uint80 answeredInRound) = aggregator.latestRoundData(); require(answeredInRound >= roundID, "ChainLink: Stale price"); require(updatedAt != 0, "ChainLink: Round not complete"); ...
Manual review
#0 - jakekidd
2022-06-24T21:45:08Z
best description of issue with mitigation step here :)
#1 - 0xleastwood
2022-08-12T04:46:59Z
While the issue is valid, this particular part of the codebase is not in active use, however, it may be used in the future. It would make sense to downgrade this to QA
.
#2 - IllIllI000
2022-08-20T10:24:17Z
@0xleastwood what makes you say that it's not in active use? The readme shows how it's being used https://github.com/code-423n4/2022-06-connext/tree/main/contracts#how-to-set-price-information-in-connextpriceoracle
#3 - 0xleastwood
2022-08-24T01:18:12Z
Can you find me an example where this is being used anywhere in the bridge transfer logic?
#4 - IllIllI000
2022-08-24T01:25:23Z
Can you find me an example where this is being used anywhere in the bridge transfer logic?
It's deployed along with everything else https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/deploy/02_deployConnext.ts#L159 are you really trying to claim that if two pieces don't directly interact, that you can arbitrarily downgrade any finding in one of them?
#5 - 0xleastwood
2022-08-24T01:36:02Z
It's being deployed but not used anywhere. I've spoken to the team about this and they may potentially use the contracts in the future but for now that's not the case. Context matters when determining the severity of an issue.