Lines of Code
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/MarketConfiguration.sol#L37-L49
Impact
Because the priceFeedHeartbeatSeconds
is never set, it is always 0. This results in a DOS of the protocol since every call to ChainlinkUtil.getPrice
reverts because the price provided by the chainlink oracle is seen as stale.
Proof of Concept
When creating a new market or updating the MarketConfiguration of an existing market, the function MarketConfiguration.update()
is called to set the new values of the MarketConfiguration.
The issue arises from the fact that even though a value for priceFeedHeartbeatSeconds
is provided, the value is never set in the market’s MarketConfiguration:
function update(Data storage self, Data memory params) internal {
self.name = params.name;
self.symbol = params.symbol;
self.priceAdapter = params.priceAdapter;
self.initialMarginRateX18 = params.initialMarginRateX18;
self.maintenanceMarginRateX18 = params.maintenanceMarginRateX18;
self.maxOpenInterest = params.maxOpenInterest;
self.maxSkew = params.maxSkew;
self.maxFundingVelocity = params.maxFundingVelocity;
self.minTradeSizeX18 = params.minTradeSizeX18;
self.skewScale = params.skewScale;
self.orderFees = params.orderFees;
<==@audit “priceFeedHeartbeatSeconds” is not set
}
The priceFeedHeartbeatSeconds
parameter is crucial for determining the validity of the price feed data. If this parameter is not set, it defaults to 0, causing the system to always consider the price feed data as outdated when calling the priceAdapter and will revert.
function getPrice(
IAggregatorV3 priceFeed,
uint32 priceFeedHeartbeatSeconds,
IAggregatorV3 sequencerUptimeFeed
)
internal
view
returns (UD60x18 price)
{
…
try priceFeed.latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) {
if (block.timestamp - updatedAt > priceFeedHeartbeatSeconds) { <== @audit will always revert because the priceFeedHeartbeatSeconds is 0
revert Errors.OraclePriceFeedHeartbeat(address(priceFeed));
}
…
}
Since a valid price is crucial for creating and filling orders, both functionalities will be DOSed since they call getPrice
.
Recommended Mitigation Steps
Ensure that the priceFeedHeartbeatSeconds
parameter is properly set during the market creation and updating process.
function update(Data storage self, Data memory params) internal {
self.name = params.name;
self.symbol = params.symbol;
self.priceAdapter = params.priceAdapter;
self.initialMarginRateX18 = params.initialMarginRateX18;
self.maintenanceMarginRateX18 = params.maintenanceMarginRateX18;
self.maxOpenInterest = params.maxOpenInterest;
self.maxSkew = params.maxSkew;
self.maxFundingVelocity = params.maxFundingVelocity;
self.minTradeSizeX18 = params.minTradeSizeX18;
self.skewScale = params.skewScale;
self.orderFees = params.orderFees;
+ self.priceFeedHeartbeatSeconds = params.priceFeedHeartbeatSeconds;
}