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

`MarketConfiguration::update()` lacks the relevant implementation for updating `MarketConfiguration::Data::uint32 priceFeedHeartbeatSeconds;` parameter

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.

/// @notice {MarketConfiguration} namespace storage structure.
/// @param name The perp market name.
/// @param symbol The perp market symbol.
/// @param priceAdapter The price oracle contract address.
/// @param initialMarginRateX18 The initial margin rate in 1e18.
/// @param maintenanceMarginRateX18 The maintenance margin rate in 1e18.
/// @param maxOpenInterest The maximum open interest allowed.
/// @param maxSkew The maximum skew allowed.
/// @param maxFundingVelocity The maximum funding velocity allowed.
/// @param minTradeSizeX18 The minimum trade size in 1e18.
/// @param skewScale The skew scale, a configurable parameter that determines price marking and funding.
/// @param orderFees The configured maker and taker order fee tiers.
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;
}
/// @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;
@> // missing self.priceFeedHeartbeatSeconds = params.priceFeedHeartbeatSeconds;
}

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;
// SNIP...
@> 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
})
);
// SNIP...

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
});
// it should emit {LogCreatePerpMarket} event
vm.expectEmit({ emitter: address(perpsEngine) });
emit GlobalConfigurationBranch.LogCreatePerpMarket(users.owner.account, params.marketId);
// it should create perp market
// it should enable perp market
perpsEngine.createPerpMarket({ params: params });
// In order to get the priceFeedHeartbeatSeconds of the markID just created, we will call GlobalConfigurationBranch::updatePerpMarketConfiguration() to update and get the priceFeedHeartbeatSeconds
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) });
// From the event, we can see that `priceFeedHeartbeatSeconds == 0`
emit GlobalConfigurationBranch.CheckMarketPriceFeedHeartbeatSeconds(0);
perpsEngine.updatePerpMarketConfiguration(1,updateParams);
// update again
updateParams.priceFeedHeartbeatSeconds = 4;
vm.expectEmit({ emitter: address(perpsEngine) });
// From the event, we can see that `priceFeedHeartbeatSeconds == 0`
emit GlobalConfigurationBranch.CheckMarketPriceFeedHeartbeatSeconds(0);
perpsEngine.updatePerpMarketConfiguration(1,updateParams);
}
// [PASS] test_Poc_MarketConfiguration_update_missing_priceFeedHeartbeatSeconds() (gas: 390049)

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.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

`MarketConfiguration::update` function lacks `priceFeedHeartbeatSeconds` argument

Support

FAQs

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