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

Markets don't work in Avalanche since the chain has no sequencer

Summary

The keeper proxy contract is attempting to call a functionality unavailable on Avalanche and won't work.

Vulnerability Details

The protocol implemented a sequencer uptime check as recommended by Guardian in the previous report. Since the keeper proxy contract makes no distinction between which chain it's currently deployed on, the same logic will be applied to Avalanche, which has no sequencer, as it is not a L2.

Notice how there's a hardcoded aggregator address on the initializer of KeeperProxy.sol. If you verify it on an Avalanche C-Chain explorer, you'll see it's an empty address with zero code. When attempting to verify the price, the contract will run a high level call to the empty address, reverting.

function initialize() external initializer {
__Ownable2Step_init();
sequencerUptimeFeed = AggregatorV2V3Interface(0xFdB631F5EE196F0ed6FAa767959853A9F217697D);
}

As a consequence, calls of run() and runNextAction() will revert, preventing the protocol from opening positions on this chain.

function run(
address perpVault,
bool isOpen,
bool isLong,
MarketPrices memory prices,
bytes[] memory _swapData
) external onlyKeeper {
_validatePrice(perpVault, prices);
IPerpetualVault(perpVault).run(isOpen, isLong, prices, _swapData);
}
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(); //reverts here
. . .
}

Impact

Contracts will have to be redesigned and redeployed. No funds are lost, however, if the protocol does not notice this oversight after funds are deposited while the vault's position is closed, the system operates correctly until a keeper attempts to call run(), causing users to have to wait for the lock time of 1 week until they can withdraw funds again.

Recommendations

Skip the sequencer uptime check if the current chain is not Arbitrum.

function _validatePrice(address perpVault, MarketPrices memory prices) internal view {
if (block.chainid == 42161) {
// 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"); //@low avax doesn't have this
// 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);
}
Updates

Lead Judging Commences

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

finding_Avalanche_has_no_sequencer

Likelihood: High, run and runNextAction will revert. Impact: Low, any deposit will be retrieve thanks to cancelFlow.

Support

FAQs

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

Give us feedback!