Summary
GlobalConfigurationBranch.updatePerpMarketConfiguration()
function does not update priceFeedHeartbeatSeconds
.
Vulnerability Details
updatePerpMarketConfiguration()
updates the configuration variables of the given perp market id. It does this by first validating all values that are not the default ones and then proceeds to write in storage.
Reference in code: link
function updatePerpMarketConfiguration(
uint128 marketId,
UpdatePerpMarketConfigurationParams calldata params
)
external
onlyOwner
onlyWhenPerpMarketIsInitialized(marketId)
{
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
MarketConfiguration.Data storage perpMarketConfiguration = perpMarket.configuration;
if (abi.encodePacked(params.name).length == 0) {
revert Errors.ZeroInput("name");
}
if (abi.encodePacked(params.symbol).length == 0) {
revert Errors.ZeroInput("symbol");
}
if (params.priceAdapter == address(0)) {
revert Errors.ZeroInput("priceAdapter");
}
if (params.maintenanceMarginRateX18 == 0) {
revert Errors.ZeroInput("maintenanceMarginRateX18");
}
if (params.maxOpenInterest == 0) {
revert Errors.ZeroInput("maxOpenInterest");
}
if (params.maxSkew == 0) {
revert Errors.ZeroInput("maxSkew");
}
if (params.initialMarginRateX18 == 0) {
revert Errors.ZeroInput("initialMarginRateX18");
}
if (params.initialMarginRateX18 <= params.maintenanceMarginRateX18) {
revert Errors.InitialMarginRateLessOrEqualThanMaintenanceMarginRate();
}
if (params.skewScale == 0) {
revert Errors.ZeroInput("skewScale");
}
if (params.minTradeSizeX18 == 0) {
revert Errors.ZeroInput("minTradeSizeX18");
}
if (params.maxFundingVelocity == 0) {
revert Errors.ZeroInput("maxFundingVelocity");
}
if (params.priceFeedHeartbeatSeconds == 0) {
revert Errors.ZeroInput("priceFeedHeartbeatSeconds");
}
perpMarketConfiguration.update(
MarketConfiguration.Data({
name: params.name,
symbol: params.symbol,
priceAdapter: params.priceAdapter,
initialMarginRateX18: params.initialMarginRateX18,
maintenanceMarginRateX18: params.maintenanceMarginRateX18,
maxOpenInterest: params.maxOpenInterest,
maxSkew: params.maxSkew,
maxFundingVelocity: params.maxFundingVelocity,
minTradeSizeX18: params.minTradeSizeX18,
skewScale: params.skewScale,
orderFees: params.orderFees,
@> priceFeedHeartbeatSeconds: params.priceFeedHeartbeatSeconds
})
);
emit LogUpdatePerpMarketConfiguration(msg.sender, marketId);
}
The issue lies inside the perpMarketConfiguration.update()
- it does not use priceFeedHeartbeatSeconds
provided in the input params
, preventing its value from getting updated
Reference in code: link
struct Data {
string name;
string symbol;
address priceAdapter;
uint128 initialMarginRateX18;
uint128 maintenanceMarginRateX18;
uint128 maxOpenInterest;
uint128 maxSkew;
uint128 maxFundingVelocity;
uint128 minTradeSizeX18;
uint256 skewScale;
OrderFees.Data orderFees;
@> uint32 priceFeedHeartbeatSeconds;
}
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;
@>
}
Chainlink heartbeats can change over time and in order to be sure that the protocol is not using any stale prices there should be a way to update the price feed heartbeat.
Impact
Not being able to change the chainlink price feed heartbeat could lead to the usage of stale prices and loss of funds.
Tools Used
Manual Review
Recommended Mitigation
Make the following changes to the update function:
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;
}