code-423n4 / 2023-07-moonwell-findings

1 stars 0 forks source link

Chainlink's latestRoundData might return stale or incorrect results #329

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-07-moonwell/blob/main/src/core/Oracles/ChainlinkOracle.sol#L100-L103

Vulnerability details

Impact

Chainlink's latestRoundData() is used but there is no check if the return value indicates stale data. This could return stale price data for the underlying asset.

Proof of Concept

getChainlinkPrice function uses Chainlink's latestRoundData() to get the latest price. However, there is no check if the return value indicates stale data. This could lead to stale prices according to the Chainlink documentation: https://docs.chain.link/docs/historical-price-data/#historical-rounds

File: ChainlinkOracle.sol
097:     function getChainlinkPrice(
098:         AggregatorV3Interface feed
099:     ) internal view returns (uint256) {
100:         (, int256 answer, , uint256 updatedAt, ) = AggregatorV3Interface(feed)
101:             .latestRoundData();
102:         require(answer > 0, "Chainlink price cannot be lower than 0");
103:         require(updatedAt != 0, "Round is in incompleted state");
104:
105:         // Chainlink USD-denominated feeds store answers at 8 decimals
106:         uint256 decimalDelta = uint256(18).sub(feed.decimals());
107:         // Ensure that we don't multiply the result by 0
108:         if (decimalDelta > 0) {
109:             return uint256(answer).mul(10 ** decimalDelta);
110:         } else {
111:             return uint256(answer);
112:         }
113:     }

Tools Used

Manual analysis

Recommended Mitigation Steps

Consider adding checks for stale data. e.g

File: ChainlinkOracle.sol
097:     function getChainlinkPrice(
098:         AggregatorV3Interface feed
099:     ) internal view returns (uint256) {
100:         (, int256 answer, , uint256 updatedAt, ) = AggregatorV3Interface(feed)
101:             .latestRoundData();
102:         require(answer > 0, "Chainlink price cannot be lower than 0");
103:         require(updatedAt != 0, "Round is in incompleted state");
++:          require(block.timestamp - updatedAt < PRICE_ORACLE_STALE_THRESHOLD, "Price round incomplete");
104:
105:         // Chainlink USD-denominated feeds store answers at 8 decimals
106:         uint256 decimalDelta = uint256(18).sub(feed.decimals());
107:         // Ensure that we don't multiply the result by 0
108:         if (decimalDelta > 0) {
109:             return uint256(answer).mul(10 ** decimalDelta);
110:         } else {
111:             return uint256(answer);
112:         }
113:     }

Assessed type

Oracle

0xSorryNotSorry commented 1 year ago

OOS --> [M‑02] Insufficient oracle validation

c4-pre-sort commented 1 year ago

0xSorryNotSorry marked the issue as low quality report

c4-judge commented 1 year ago

alcueca marked the issue as unsatisfactory: Invalid