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: }
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-roundsTools Used
Manual analysis
Recommended Mitigation Steps
Consider adding checks for stale data. e.g
Assessed type
Oracle