DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Ignored Return Values in _validatePrice() May Lead to Stale or Invalid Price Data Usage

Summary

The function KeeperProxy._validatePrice() (lines 155-180) in contracts/KeeperProxy.sol fails to check important return values from AggregatorV2V3Interface.latestRoundData(). Specifically:

  1. updatedAt is ignored – This means the contract may use stale price data.

  2. answeredInRound is ignored – This could allow using an incomplete or manipulated price feed.

As a result, the function might execute transactions based on outdated or incorrect price data, leading to financial risks, failed transactions, or oracle manipulation attacks.

Vulnerability Details

Problematic Code in _validatePrice()

(
/*uint80 roundID*/,
int256 answer,
uint256 startedAt,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = AggregatorV2V3Interface(sequencerUptimeFeed).latestRoundData();
  • The function only checks answer and startedAt but completely ignores updatedAt and answeredInRound.

  • There is no validation to ensure the retrieved price data is fresh and comes from a valid round.

  • If the Chainlink oracle experiences downtime or returns outdated data, stale prices might be used, leading to incorrect price calculations and financial loss.

Impact

Use of Stale Prices: The contract may use outdated price data, leading to incorrect trades or liquidations.

  • Oracle Exploitation Risk: Attackers could manipulate price feeds by using outdated but favorable price data.

  • Failed Transactions: If the contract executes based on old data, users may experience failed or unfair trades.

Tools Used

Vs code

Recommendations

1. Validate updatedAt Before Using Price Data

Ensure the price data is recent before proceeding with any operations:

require(updatedAt > 0, "Invalid updatedAt timestamp");
require(block.timestamp - updatedAt < MAX_PRICE_AGE, "Price data is stale");

2. Verify answeredInRound to Prevent Invalid Rounds

Ensure that the returned data is from a valid round:

require(answeredInRound >= roundID, "Stale price data detected");

Fixed _validatePrice() Function

function _validatePrice(address perpVault, MarketPrices memory prices) internal view {
// L2 Sequencer check
(uint80 roundID, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
AggregatorV2V3Interface(sequencerUptimeFeed).latestRoundData();
bool isSequencerUp = answer == 0;
require(isSequencerUp, "sequencer is down");
// Validate timestamp to ensure price data is fresh
require(updatedAt > 0, "Invalid updatedAt timestamp");
require(block.timestamp - updatedAt < MAX_PRICE_AGE, "Price data is stale");
require(answeredInRound >= roundID, "Stale price data detected");
// Ensure grace period after sequencer recovery
uint256 timeSinceUp = block.timestamp - startedAt;
require(timeSinceUp > GRACE_PERIOD_TIME, "Grace period is not over");
address market = IPerpetualVault(perpVault).market();
IVaultReader reader = IPerpetualVault(perpVault).vaultReader();
MarketProps memory marketData = reader.getMarket(market);
_check(marketData.indexToken, prices.indexTokenPrice.min);
_check(marketData.indexToken, prices.indexTokenPrice.max);
_check(marketData.longToken, prices.indexTokenPrice.min);
_check(marketData.longToken, prices.indexTokenPrice.max);
_check(marketData.shortToken, prices.shortTokenPrice.min);
_check(marketData.shortToken, prices.shortTokenPrice.max);
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.