Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Block Gas Limit Vulnerability in FeeConversionKeeper Leading to Critical Fee Management Disruption

Description

The FeeConversionKeeper contract's unbounded array handling can lead to transactions exceeding block gas limits, causing systematic failure of fee conversion operations in the market making engine.

function checkUpkeep(bytes calldata /**/ ) external view returns (bool upkeepNeeded, bytes memory performData) {
// ...
uint128[] memory marketIds = new uint128[](liveMarketIds.length * 10);
address[] memory assets = new address[](liveMarketIds.length * 10);
// ...
}

In the core fee management flow, checkUpkeep creates unbounded arrays that scale with market count. When these transactions hit block gas limits, the entire fee conversion pipeline stalls. Fee conversions in FeeDistributionBranch.sol begin to fail systematically, leaving accumulated fees unconverted in their original asset form instead of WETH.

This failure cascades to market making operations, where unconverted fees disrupt the compensation schedule for market makers. The delayed conversion and distribution can force market makers to reduce their activities or withdraw liquidity, directly impacting market quality.

At the protocol level, the fee conversion failure creates systemic risk. The MarketMakingEngineConfiguration.sol can't execute its distribution logic, leaving protocol revenue in volatile assets. This exposes the protocol to unnecessary market risk and disrupts the economic incentives designed to maintain market stability.

Impact

The unlimited batch size in fee conversion operations can exceed block gas limits, causing all fee management operations to fail.

Recommended Fix:

contract FeeConversionKeeper is IAutomationCompatible, BaseKeeper {
struct FeeConversionKeeperStorage {
// ... existing storage
uint128 maxBatchSize;
uint256 lastProcessedMarketIndex; // For continuity between batches
}
function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory performData) {
FeeConversionKeeperStorage memory self = _getFeeConversionKeeperStorage();
uint128[] memory liveMarketIds = self.marketMakingEngine.getLiveMarketIds();
// Cap batch size for gas predictability
uint128[] memory marketIds = new uint128[](self.maxBatchSize);
address[] memory assets = new address[](self.maxBatchSize);
uint256 index = 0;
uint256 startFrom = self.lastProcessedMarketIndex % liveMarketIds.length;
// Process markets in rotation, ensuring fair processing
for (uint256 i = 0; i < liveMarketIds.length && index < self.maxBatchSize; i++) {
uint256 marketIndex = (startFrom + i) % liveMarketIds.length;
uint128 marketId = liveMarketIds[marketIndex];
(address[] memory marketAssets, uint256[] memory feesCollected) =
self.marketMakingEngine.getReceivedMarketFees(marketId);
for (uint256 j = 0; j < marketAssets.length && index < self.maxBatchSize; j++) {
if (checkFeeDistributionNeeded(marketAssets[j], feesCollected[j])) {
marketIds[index] = marketId;
assets[index] = marketAssets[j];
index++;
}
}
}
if (index == 0) return (false, "");
return (true, abi.encode(marketIds[0:index], assets[0:index]));
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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