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

Incorrect Price Validation for Long Token in KeeperProxy

Summary

Long token prices are wrongly validated against index token data.

Vulnerability Details

In the KeeperProxy._validatePrice() function, the long token’s price is compared to prices.indexTokenPrice instead of prices.longTokenPrice, allowing deviations for the long token to go undetected.

/**
* @notice Validates the market prices against the Chainlink data feed.
* @dev Internal function to check the prices of the market tokens.
* @param perpVault The address of the PerpetualVault.
* @param prices The market prices used for validation.
*/
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");
// Make sure the grace period has passed after the sequencer is back up.
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);
}

The correct approach should be to validate the long token’s price using its dedicated price feed (prices.longTokenPrice.min/max). Using the index token’s price for long token validation fails to catch deviations specific to the long token. As a result, if the long token’s price is manipulated or inaccurate, the keeper may operate on invalid data, potentially leading to mispriced trades.

Proof of Concept:-

  • Long token (e.g., BTC) and index token (e.g., ETH) with divergent prices.

  • ETH price drops 10%, BTC remains stable.

  • KeeperProxy uses ETH’s price for BTC validation.

  • Invalid liquidations of BTC positions occur.

Therefore, misaligned price references let keepers exploit arbitrage gaps or trigger unfair liquidations.

Impact

If the long token price deviates significantly from the true market value without detection, orders that depend on accurate price feeds (such as opening or closing positions) may be executed under wrong conditions. This misalignment risks under-collateralization, unexpected liquidations, or losses that can affect both traders and the overall protocol.

Tools Used

Manual Review

Recommendations

Adjust the code in _validatePrice() function to use prices.longTokenPrice.min and prices.longTokenPrice.max for the long token.

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");
// Make sure the grace period has passed after the sequencer is back up.
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.longToken, prices.longToken.min);
+ _check(marketData.longToken, prices.longToken.max);
_check(marketData.shortToken, prices.shortTokenPrice.min);
_check(marketData.shortToken, prices.shortTokenPrice.max);
}
Updates

Lead Judging Commences

n0kto Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_validatePrice_no_check_for_longTokenPrice

Likelihood: None/Very Low, everytime the keeper send a price via run/runNextAction (sent by the Gamma keeper). Impact: Medium/High, does not check the longTokenPrice, it could go out of range. Keep in mind indexToken == longToken, an error from the keeper could be considered informational.

Support

FAQs

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