Part 2

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

Denial of Service (DoS) Vulnerability in getLiveMarketsIds Function

Summary

The getLiveMarketsIds function in the LiveMarkets contract does not impose any limits on the number of market IDs returned in a single call. This can lead to a Denial of Service (DoS) vulnerability when a large number of market IDs are added to the contract, causing excessive gas consumption and potential failure of the transaction due to exceeding the block gas limit.

  • Severity: Medium

  • Likelihood: High

  • Impact: Medium

Affected line of code

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/LiveMarkets.sol#L51-L59

Vulnerability Details

The getLiveMarketsIds function retrieves all market IDs stored in the contract. However, as the number of live market IDs grows, the gas required to process the loop in the function increases. If the set of market IDs becomes large enough, calling this function can consume more gas than the block allows, leading to a failure (out-of-gas error).

Impact

  • Denial of Service (DoS): An attacker or user could add a large number of market IDs, and then any attempt to retrieve the list of market IDs would fail due to excessive gas usage.

  • This could make the contract unusable for retrieving market IDs once the list grows beyond a certain size.

"POC" (Proof Of Concept)

  1. Test Setup:

    • Add a large number of market IDs to the contract (e.g., 100,000 markets).

    • Call the getLiveMarketsIds function to retrieve the list of market IDs.

  2. Test Code:

// Test function to add a large number of markets
function testAddLargeNumberOfMarkets() public {
uint128 numberOfMarkets = 100000; // Set a large number
for (uint128 i = 0; i < numberOfMarkets; i++) {
LiveMarkets.addMarket(liveMarketsData, i); // Add market
}
// Call the function getLiveMarketsIds to see if it runs out of gas
uint128[] memory marketIds = LiveMarkets.getLiveMarketsIds(liveMarketsData);
// Assert that we successfully retrieved the market IDs (if it didn't run out of gas)
assertEq(marketIds.length, numberOfMarkets, "The number of market IDs should match");
}
  1. Test Results:

    • Running the test resulted in an OutOfGas error, confirming that the function fails when attempting to handle a large number of market IDs:

    [FAIL: EvmError: OutOfGas] testAddLargeNumberOfMarkets() (gas: 1073720760)
    [FAIL: EvmError: OutOfGas] testGetLiveMarketsIdsGasLimit() (gas: 1073720760)

Tools Used

Manual Review, Foundry

Recommendations

  • Pagination: Modify the getLiveMarketsIds function to implement pagination, allowing users to query a smaller subset of market IDs at a time. This would limit the gas usage per query and prevent DoS.

  • Limit the number of market IDs: Implement a maximum limit on the number of market IDs that can be stored or retrieved in a single transaction.

For example, update the getLiveMarketsIds function to accept an offset and limit parameter to return a portion of the market IDs:

function getLiveMarketsIds(
Data storage self,
uint256 offset,
uint256 limit ) internal view returns (uint128[] memory marketIds) {
uint256 liveMarketsLength = self.liveMarketIds.length();
uint256 end = offset + limit > liveMarketsLength ? liveMarketsLength : offset + limit;
marketIds = new uint128[](end - offset);
for (uint256 i = offset; i < end; i++) {
marketIds[i - offset] = uint128(self.liveMarketIds.at(i));
}
}

This change would allow for smaller, paginated queries, reducing the risk of excessive gas consumption.

Here’s an enhanced test suite that demonstrates the vulnerability and provides clear expectations for judges:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { LiveMarkets } from "../../src/market-making/leaves/LiveMarkets.sol"; // Import the LiveMarkets library
import { Test } from "forge-std/Test.sol"; // Use Forge's Test library
contract TestLiveMarkets is Test {
// Declare the Data struct to interact with LiveMarkets
LiveMarkets.Data internal liveMarketsData;
function setUp() public {
// You can initialize your test data here, if needed
}
// Test function to add a large number of markets
function testAddLargeNumberOfMarkets() public {
// Add a large number of market IDs
uint128 numberOfMarkets = 100000; // Set a large number
for (uint128 i = 0; i < numberOfMarkets; i++) {
LiveMarkets.addMarket(liveMarketsData, i); // Add market
}
// Call the function getLiveMarketsIds to see if it runs out of gas
uint128[] memory marketIds = LiveMarkets.getLiveMarketsIds(liveMarketsData);
// Assert that we successfully retrieved the market IDs (if it didn't run out of gas)
assertEq(marketIds.length, numberOfMarkets, "The number of market IDs should match");
}
// Additional test case to check if the function really runs out of gas
function testGetLiveMarketsIdsGasLimit() public {
uint128 numberOfMarkets = 100000; // Large number to test gas consumption
// Adding markets to the set
for (uint128 i = 0; i < numberOfMarkets; i++) {
LiveMarkets.addMarket(liveMarketsData, i);
}
// Try to get the live market IDs, expecting it to consume a lot of gas
vm.expectRevert("out of gas"); // Expect the test to fail if it runs out of gas
LiveMarkets.getLiveMarketsIds(liveMarketsData);
}
}
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.