Skip to content

Latest commit

 

History

History
103 lines (69 loc) · 4.17 KB

File metadata and controls

103 lines (69 loc) · 4.17 KB

Narrow Lipstick Moose

High

Market Configuration Vote Initialization Discrepancy

Summary

The createMarketWithConfig function in the ReputationMarket.sol contract allows users to create markets using predefined configurations (Default, Deluxe, Premium) with associated liquidity and vote levels. However, the _createMarket function sets the initial votes for Trust and Distrust to 1, regardless of the selected tier. This discrepancy results in all markets being initialized at the Default tier's vote levels, even when users pay for higher-tier configurations like Deluxe or Premium.

This behavior misaligns with the intended design and user expectations, potentially leading to disputes and dissatisfaction from users who do not receive what they pay for.

Prices are determined by a bonding curve formula based on the ratio of Trust and Distrust votes. Initializing both with a minimal value (1) creates disproportionate sensitivities to small changes in votes, resulting in unpredictable and unstable pricing dynamics.

Root Cause

The issue can be observed here : https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L318-L355

  function _createMarket(
    uint256 profileId,
    address recipient,
    uint256 marketConfigIndex
  ) private nonReentrant {
    // ensure a market doesn't already exist for this profile
    if (markets[profileId].votes[TRUST] != 0 || markets[profileId].votes[DISTRUST] != 0)
      revert MarketAlreadyExists(profileId);

    // ensure the specified config option is valid
    if (marketConfigIndex >= marketConfigs.length)
      revert InvalidMarketConfigOption("Invalid config index");

    uint256 creationCost = marketConfigs[marketConfigIndex].creationCost;

    // Handle creation cost, refunds and market funds for non-admin users
    if (!hasRole(ADMIN_ROLE, msg.sender)) {
      if (msg.value < creationCost) revert InsufficientLiquidity(creationCost);
      marketFunds[profileId] = creationCost;
      if (msg.value > creationCost) {
        _sendEth(msg.value - creationCost);
      }
    } else {
      // when an admin creates a market, there is no minimum creation cost; use whatever they sent
      marketFunds[profileId] = msg.value;
    }

    // Create the new market using the specified config
    markets[profileId].votes[TRUST] = 1;
    markets[profileId].votes[DISTRUST] = 1;
    markets[profileId].basePrice = marketConfigs[marketConfigIndex].basePrice;
    markets[profileId].liquidityParameter = marketConfigs[marketConfigIndex].liquidity;

    donationRecipient[profileId] = recipient;

    emit MarketCreated(profileId, msg.sender, marketConfigs[marketConfigIndex]);
    _emitMarketUpdate(profileId);
  }

In this function, no matter what the marketIndexConfig(0,1 or 2) is, the market will always be of type default because

    markets[profileId].votes[TRUST] = 1;  //@audit always default type
    markets[profileId].votes[DISTRUST] = 1; //@audit always default type

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

  • Users who create markets at higher tiers (Deluxe or Premium) are not assigned the appropriate vote levels, reducing the liquidity and stability of their markets.
  • Users paying higher creation costs do not receive the benefits associated with these tiers, leading to potential disputes and reputational risks for the protocol.
  • Market behaviors and pricing based on vote levels will be skewed due to incorrectly initialized markets, affecting fairness in price discovery.

PoC

Steps to Reproduce:

  • Call the createMarketWithConfig function with ex. marketConfigIndex = 2 (Premium tier), which should initialize 10,000 votes for Trust and Distrust.

  • Observe that the actual votes for the created market are set to 1 for both Trust and Distrust.

Mitigation

Add initialVotes in the MarketConfigs struct. And update the _createMarket by doing:

    markets[profileId].votes[TRUST] = marketConfigs[marketConfigIndex].initialVotes;
    markets[profileId].votes[DISTRUST] = marketConfigs[marketConfigIndex].initialVotes;