Little Malachite Perch
Medium
The OracleReader
contract provides functionality to fetch and process price data from Chainlink oracles via the OracleFeeds
contract. While the implementation offers flexibility and extensibility, there are potential vulnerabilities and design considerations that could expose the system to exploits, especially in volatile market conditions or during oracle manipulation.
This report details identified vulnerabilities, their potential impact, and recommendations to mitigate them. >> OracleReader.sol::Line 78
- Description: The error
NoPriceFound()
is declared in the contract but never used. This introduces potential ambiguity in error handling and omits an opportunity for better reporting of missing price data. - Impact: The lack of explicit error reporting when no valid price is retrieved may result in undefined behavior or missed debugging opportunities.
- Recommendation: Use
NoPriceFound()
in thegetOraclePrice
function when the oracle returns an invalid or zero price. Example:
if (answer == 0) {
revert NoPriceFound();
}
- Description: Chainlink oracles enforce a predefined price range through
minAnswer
andmaxAnswer
. During flash crashes, bridge exploits, or depegging events, if the actual price falls belowminAnswer
or exceedsmaxAnswer
, the oracle may return an incorrect or stale price. - Impact: An attacker could exploit incorrect pricing to borrow more assets than allowed by depositing underpriced tokens. This could lead to bad debt in lending/borrowing platforms or other financial protocols relying on the contract.
- Recommendation: Implement checks to ensure that the retrieved price is within the acceptable bounds:
(, int256 answer, , , ) = AggregatorV3Interface(feed).latestRoundData();
int256 minAnswer = OracleFeeds(oracleFeeds).getMinAnswer(feed);
int256 maxAnswer = OracleFeeds(oracleFeeds).getMaxAnswer(feed);
if (answer < minAnswer || answer > maxAnswer) {
revert NoPriceFound();
}
- Introduce off-chain monitoring to validate Chainlink prices against external sources.
- Description: The
getOraclePrice
function correctly checks if the price is stale by comparing theupdatedTimestamp
to the currentblock.timestamp
. However, this relies entirely on the feedHeartbeats values provided by OracleFeeds. Impact: If thefeedHeartbeats
value is incorrect or misconfigured, stale prices may go undetected, leading to mispriced assets. - Recommendation: Validate
feedHeartbeats
during deployment or initialization. Cross-checkupdatedTimestamp
with a configurable maximum allowed delay in addition to feedHeartbeats.
- Description: The contract relies solely on Chainlink oracles for pricing. In case of oracle manipulation or outages, there is no fallback mechanism or cross-checking with other oracle providers.
- Impact: Oracle manipulation could result in significant financial losses for protocols using this contract.
- Recommendation: Integrate a fallback oracle mechanism (e.g., Band Protocol or Uniswap TWAP). Use a medianizer to aggregate prices from multiple sources.
No response
No response
No response
1. Critical Impact:
- Exploiting incorrect prices during flash crashes could lead to significant financial losses for users and protocols integrating this contract.
- Stale prices could be exploited to borrow funds at outdated rates.
2. Moderate Impact:
- Misrepresentation of constants (e.g., USD and ETH) may lead to integration errors.
- Lack of cross-oracle validation increases dependency on a single source.
No response
- Use
NoPriceFound()
to handle invalid prices. - Validate retrieved prices against
minAnswer
and maxAnswer bounds. - Include cross-oracle validation and fallback mechanisms for improved reliability.
- Enhance documentation to prevent misinterpretation of constants.
- Validate the configuration of
feedHeartbeats
to avoid stale prices.
Example Fix for Flash Crash Protection:
function getOraclePrice(
address quote,
address base
) public view returns (uint256) {
bool isInverted = false;
address feed = OracleFeeds(oracleFeeds).priceFeeds(quote, base);
if (feed == address(0)) {
feed = OracleFeeds(oracleFeeds).priceFeeds(base, quote);
if (feed == address(0)) {
revert NoFeedFound();
}
isInverted = true;
}
(, int256 answer, , uint256 updatedTimestamp, ) = AggregatorV3Interface(
feed
).latestRoundData();
int256 minAnswer = OracleFeeds(oracleFeeds).getMinAnswer(feed);
int256 maxAnswer = OracleFeeds(oracleFeeds).getMaxAnswer(feed);
if (answer < minAnswer || answer > maxAnswer) {
revert NoPriceFound();
}
if (
updatedTimestamp + OracleFeeds(oracleFeeds).feedHeartbeats(feed) <
block.timestamp
) {
revert StalePrice();
}
uint256 decimals = uint256(AggregatorV3Interface(feed).decimals());
return
isInverted
? (10 ** decimals * 10 ** decimals) / uint256(answer)
: uint256(answer);
}
Hi dear Sherlock judge,
This is my first time participating in a competitive audit, and I hope you find my report well-prepared and in accordance with the rules and requirements outlined in the Sherlock audit guidelines.
If there are any areas where I’ve fallen short, I would greatly appreciate detailed feedback to ensure I improve in future reports. Of course, I completely understand if your schedule doesn’t allow for extensive feedback given the number of reports you have to review.
Wishing you a great day! Sending virtual cookies your way 🍪😊