Platform: Code4rena
Start Date: 25/10/2022
Pot Size: $50,000 USDC
Total HM: 18
Participants: 127
Period: 5 days
Judge: 0xean
Total Solo HM: 9
Id: 175
League: ETH
Rank: 88/127
Findings: 2
Award: $24.60
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 8olidity, BClabs, CertoraInc, Chom, Franfran, Lambda, RaoulSchaffranek, Ruhum, codexploder, cryptphi, eierina, joestakey, kaden, neumo, pashov, rvierdiiev, sorrynotsorry
24.2165 USDC - $24.22
https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L78-L105 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L112-L144
Oracle contract has 2 functions - viewPrice
& getPrice
- to get the price through the Chainlink price feed in DOLA. Both functions check the decimals of the feedDecimals
answer by calling feed.decimals()
and calculate the price by 36 - feedDecimals - tokenDecimals;
. DOLA has 18 decimals, hence it can be seen that the feedDecimals
is considered not to be higher than 18. If any tokens with decimals higher than 18 are decided to be added to the system and accepted as collateral tokens, both viewPrice
& getPrice
functions will revert.
For viewPrice
;
function viewPrice(address token, uint collateralFactorBps) external view returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; if(feeds[token].feed != IChainlinkFeed(address(0))) { // get price from feed uint price = feeds[token].feed.latestAnswer(); require(price > 0, "Invalid feed price"); // normalize price uint8 feedDecimals = feeds[token].feed.decimals(); uint8 tokenDecimals = feeds[token].tokenDecimals; uint8 decimals = 36 - feedDecimals - tokenDecimals; // @audit-info - The function would revert when feedDecimals GT 18 uint normalizedPrice = price * (10 ** decimals); uint day = block.timestamp / 1 days; // get today's low uint todaysLow = dailyLows[token][day]; // get yesterday's low uint yesterdaysLow = dailyLows[token][day - 1]; // calculate new borrowing power based on collateral factor uint newBorrowingPower = normalizedPrice * collateralFactorBps / 10000; uint twoDayLow = todaysLow > yesterdaysLow && yesterdaysLow > 0 ? yesterdaysLow : todaysLow; if(twoDayLow > 0 && newBorrowingPower > twoDayLow) { uint dampenedPrice = twoDayLow * 10000 / collateralFactorBps; return dampenedPrice < normalizedPrice ? dampenedPrice: normalizedPrice; } return normalizedPrice; } revert("Price not found"); }
For getPrice
;
function getPrice(address token, uint collateralFactorBps) external returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; if(feeds[token].feed != IChainlinkFeed(address(0))) { // get price from feed uint price = feeds[token].feed.latestAnswer(); require(price > 0, "Invalid feed price"); // normalize price uint8 feedDecimals = feeds[token].feed.decimals(); uint8 tokenDecimals = feeds[token].tokenDecimals; uint8 decimals = 36 - feedDecimals - tokenDecimals; // @audit-info - The function would revert when feedDecimals GT 18 uint normalizedPrice = price * (10 ** decimals); // potentially store price as today's low uint day = block.timestamp / 1 days; uint todaysLow = dailyLows[token][day]; if(todaysLow == 0 || normalizedPrice < todaysLow) { dailyLows[token][day] = normalizedPrice; todaysLow = normalizedPrice; emit RecordDailyLow(token, normalizedPrice); } // get yesterday's low uint yesterdaysLow = dailyLows[token][day - 1]; // calculate new borrowing power based on collateral factor uint newBorrowingPower = normalizedPrice * collateralFactorBps / 10000; uint twoDayLow = todaysLow > yesterdaysLow && yesterdaysLow > 0 ? yesterdaysLow : todaysLow; if(twoDayLow > 0 && newBorrowingPower > twoDayLow) { uint dampenedPrice = twoDayLow * 10000 / collateralFactorBps; return dampenedPrice < normalizedPrice ? dampenedPrice: normalizedPrice; } return normalizedPrice; } revert("Price not found"); }
Manual Review
The team can consider this point if any tokens having more than 18 decimals is decided to be added.
#0 - c4-judge
2022-11-05T22:54:52Z
0xean marked the issue as duplicate
#1 - c4-judge
2022-11-28T16:03:04Z
0xean marked the issue as not a duplicate
#2 - c4-judge
2022-11-28T16:03:17Z
0xean marked the issue as duplicate of #540
#3 - Simon-Busch
2022-12-05T15:33:54Z
Issue marked as satisfactory as requested by 0xean
#4 - c4-judge
2022-12-07T08:18:20Z
Simon-Busch marked the issue as duplicate of #533
🌟 Selected for report: rbserver
Also found by: 0x1f8b, 0xNazgul, 0xc0ffEE, 8olidity, Aymen0909, Chom, Franfran, Jeiwan, Jujic, Lambda, M4TZ1P, Olivierdem, Rolezn, Ruhum, TomJ, Wawrdog, __141345__, bin2chen, c7e7eff, carlitox477, catchup, cccz, codexploder, cuteboiz, d3e4, dipp, djxploit, eierina, elprofesor, hansfriese, horsefacts, idkwhatimdoing, imare, immeas, joestakey, ladboy233, leosathya, martin, minhtrng, pashov, peanuts, pedroais, rokinot, rvierdiiev, saneryee, sorrynotsorry, tonisives
0.385 USDC - $0.38
https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L78-L105 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L112-L144
Oracle contract has 2 functions - viewPrice
& getPrice
- to get the price through the Chainlink price feed. However, the received data is not validated/checked for freshness and round completeness. This might cause the price to be stale and it can lead to the wrong return value.
function viewPrice(address token, uint collateralFactorBps) external view returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; if(feeds[token].feed != IChainlinkFeed(address(0))) { // get price from feed uint price = feeds[token].feed.latestAnswer(); require(price > 0, "Invalid feed price"); // normalize price uint8 feedDecimals = feeds[token].feed.decimals(); uint8 tokenDecimals = feeds[token].tokenDecimals; uint8 decimals = 36 - feedDecimals - tokenDecimals; uint normalizedPrice = price * (10 ** decimals); uint day = block.timestamp / 1 days; // get today's low uint todaysLow = dailyLows[token][day]; // get yesterday's low uint yesterdaysLow = dailyLows[token][day - 1]; // calculate new borrowing power based on collateral factor uint newBorrowingPower = normalizedPrice * collateralFactorBps / 10000; uint twoDayLow = todaysLow > yesterdaysLow && yesterdaysLow > 0 ? yesterdaysLow : todaysLow; if(twoDayLow > 0 && newBorrowingPower > twoDayLow) { uint dampenedPrice = twoDayLow * 10000 / collateralFactorBps; return dampenedPrice < normalizedPrice ? dampenedPrice: normalizedPrice; } return normalizedPrice; } revert("Price not found"); }
function getPrice(address token, uint collateralFactorBps) external returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; if(feeds[token].feed != IChainlinkFeed(address(0))) { // get price from feed uint price = feeds[token].feed.latestAnswer(); require(price > 0, "Invalid feed price"); // normalize price uint8 feedDecimals = feeds[token].feed.decimals(); uint8 tokenDecimals = feeds[token].tokenDecimals; uint8 decimals = 36 - feedDecimals - tokenDecimals; uint normalizedPrice = price * (10 ** decimals); // potentially store price as today's low uint day = block.timestamp / 1 days; uint todaysLow = dailyLows[token][day]; if(todaysLow == 0 || normalizedPrice < todaysLow) { dailyLows[token][day] = normalizedPrice; todaysLow = normalizedPrice; emit RecordDailyLow(token, normalizedPrice); } // get yesterday's low uint yesterdaysLow = dailyLows[token][day - 1]; // calculate new borrowing power based on collateral factor uint newBorrowingPower = normalizedPrice * collateralFactorBps / 10000; uint twoDayLow = todaysLow > yesterdaysLow && yesterdaysLow > 0 ? yesterdaysLow : todaysLow; if(twoDayLow > 0 && newBorrowingPower > twoDayLow) { uint dampenedPrice = twoDayLow * 10000 / collateralFactorBps; return dampenedPrice < normalizedPrice ? dampenedPrice: normalizedPrice; } return normalizedPrice; } revert("Price not found"); }
Manual Review
The data feed can be validated as follows;
function viewPrice(address token, uint collateralFactorBps) external view returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; (uint80 roundID, int256 answer, , uint256 timestamp, uint80 answeredInRound) = oracle.latestRoundData(); require(answer > 0, "invalid_oracle_answer"); require(answeredInRound >= roundID, "ChainLink: Stale price"); require(timestamp > 0, "ChainLink: Round not complete"); ...
#0 - c4-judge
2022-11-05T22:54:37Z
0xean marked the issue as duplicate
#1 - Simon-Busch
2022-12-05T15:30:24Z
Issue marked as satisfactory as requested by 0xean
#2 - c4-judge
2022-12-07T08:14:13Z
Simon-Busch marked the issue as duplicate of #584