Summary
MarketConfiguration::update()
lacks the relevant implementation for updating MarketConfiguration::Data::uint32 priceFeedHeartbeatSeconds;
parameter.
Vulnerability Details
Judging from the comments of struct Data
, MarketConfiguration::Data::uint32 priceFeedHeartbeatSeconds;
should be added later, but MarketConfiguration::update()
lacks the relevant implementation for updating this parameter.
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;
@>
}
We check the PerpMarket::create()
method as follows, and we can see that MarketConfiguration::update()
is called to update the relevant configuration. At the same time, multiple contracts will call PerpMarket::create()
to perform related operations
@> using MarketConfiguration for MarketConfiguration.Data;
@> self.configuration.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
})
);
Poc
For better observation, we add the following event in the GlobalConfigurationBranch::updatePerpMarketConfiguration()
method to obtain priceFeedHeartbeatSeconds
.
+ event CheckMarketPriceFeedHeartbeatSeconds(uint32);
function updatePerpMarketConfiguration(
uint128 marketId,
UpdatePerpMarketConfigurationParams calldata params
)
external
onlyOwner
onlyWhenPerpMarketIsInitialized(marketId)
{
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
MarketConfiguration.Data storage perpMarketConfiguration = perpMarket.configuration;
+ emit CheckMarketPriceFeedHeartbeatSeconds(perpMarketConfiguration.priceFeedHeartbeatSeconds);
// SNIP...
}
Please add the test code to test/integration/perpetuals/global-configuration-branch/createPerpMarket/createPerpMarket.t.sol
and execute
function test_Poc_MarketConfiguration_update_missing_priceFeedHeartbeatSeconds()
external
whenMarketIdIsNotZero
whenLengthOfNameIsNotZero
whenLengthOfSymbolIsNotZero
whenPriceAdapterIsNotZero
whenMaintenanceMarginRateIsNotZero
whenMaxOpenInterestIsNotZero
whenMaxSkewIsNotZero
whenInitialMarginIsNotLessOrEqualToMaintenanceMargin
whenSkewScaleIsNotZero
whenMinTradeSizeIsNotZero
whenMaxFundingVelocityIsNotZero
{
changePrank({ msgSender: users.owner.account });
SettlementConfiguration.Data memory offchainOrdersConfiguration;
SettlementConfiguration.Data memory marketOrderConfiguration;
GlobalConfigurationBranch.CreatePerpMarketParams memory params = GlobalConfigurationBranch
.CreatePerpMarketParams({
marketId: 1,
name: "BTC/USD",
symbol: "BTC",
priceAdapter: address(0x20),
initialMarginRateX18: 2,
maintenanceMarginRateX18: 1,
maxOpenInterest: 1,
maxSkew: 1,
maxFundingVelocity: 1,
minTradeSizeX18: 1,
skewScale: 1,
marketOrderConfiguration: marketOrderConfiguration,
offchainOrdersConfiguration: offchainOrdersConfiguration,
orderFees: OrderFees.Data({ makerFee: 0.0004e18, takerFee: 0.0008e18 }),
priceFeedHeartbeatSeconds: 1
});
vm.expectEmit({ emitter: address(perpsEngine) });
emit GlobalConfigurationBranch.LogCreatePerpMarket(users.owner.account, params.marketId);
perpsEngine.createPerpMarket({ params: params });
GlobalConfigurationBranch.UpdatePerpMarketConfigurationParams memory updateParams = GlobalConfigurationBranch.UpdatePerpMarketConfigurationParams({
name: "BTC/USD",
symbol: "BTC",
priceAdapter: address(0x20),
initialMarginRateX18: 2,
maintenanceMarginRateX18: 1,
maxOpenInterest: 1,
maxSkew: 1,
minTradeSizeX18: 1,
maxFundingVelocity: 1,
skewScale: 1,
orderFees: OrderFees.Data({ makerFee: 0.0004e18, takerFee: 0.0008e18 }),
priceFeedHeartbeatSeconds: 3
});
vm.expectEmit({ emitter: address(perpsEngine) });
emit GlobalConfigurationBranch.CheckMarketPriceFeedHeartbeatSeconds(0);
perpsEngine.updatePerpMarketConfiguration(1,updateParams);
updateParams.priceFeedHeartbeatSeconds = 4;
vm.expectEmit({ emitter: address(perpsEngine) });
emit GlobalConfigurationBranch.CheckMarketPriceFeedHeartbeatSeconds(0);
perpsEngine.updatePerpMarketConfiguration(1,updateParams);
}
output:
├─ emit CheckMarketPriceFeedHeartbeatSeconds(: 0)
Code Snippet
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/MarketConfiguration.sol#L8-L49
Impact
MarketConfiguration::update()
lacks the relevant implementation for updating MarketConfiguration::Data::uint32 priceFeedHeartbeatSeconds;
parameter.
Tools Used
Manual Review
Recommendations
Added related implementation
for example:
/// @notice Updates the given market configuration.
/// @dev See {MarketConfiguration.Data} for parameter details.
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;
}
test again, it's work.