Skip to content

Latest commit

 

History

History
94 lines (66 loc) · 4.52 KB

File metadata and controls

94 lines (66 loc) · 4.52 KB

Nice Pearl Canary

High

Liquidity Mismatch Leading to Potential Market Lock

Summary and Impact

https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L440-L497

The Liquidity Mismatch vulnerability in the ReputationMarket smart contract arises from improper handling of ETH funds during the purchase of votes. Specifically, when users execute the buyVotes function, the contract inaccurately tracks marketFunds[profileId] by adding the pre-fee purchase cost without accounting for the immediately deducted fees (protocol fees and donations). Over multiple transactions, this discrepancy causes marketFunds to reflect a higher value than the actual ETH balance held by the contract.

Impact:

  • Market Lock Potential: As marketFunds[profileId] becomes increasingly inflated relative to the contract's real ETH balance, future attempts to sell votes (sellVotes) may fail. This is because the contract may not possess sufficient ETH to honor the withdrawal, causing transactions to revert.
  • User Experience Degradation: Users attempting to sell their votes may face unexpected failures, undermining trust in the protocol's reliability.
  • Financial Implications: Although funds are not directly stolen, the inability to sell votes can trap user funds within the market, leading to potential financial loss and reduced market liquidity.

While the vulnerability does not directly result in loss or theft of funds, the risk of market freeze and user inability to exit positions presents a significant operational concern, warranting a Medium severity classification.


Vulnerability Details

The ReputationMarket contract's buyVotes function improperly updates the marketFunds mapping by adding the gross purchase cost before fees are deducted. This oversight leads to an imbalance between the tracked marketFunds and the actual ETH held by the contract. Over time, this mismatch can cause the contract to run out of ETH relative to the marketFunds recorded, preventing successful executions of the sellVotes function.

Code Snippet

function buyVotes(
    uint256 profileId,
    bool isPositive,
    uint256 maxVotesToBuy,
    uint256 minVotesToBuy
) public payable whenNotPaused activeMarket(profileId) nonReentrant {
    // ... [omitted for brevity]

    // Update market state
    markets[profileId].votes[isPositive ? TRUST : DISTRUST] += currentVotesToBuy;
    votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] += currentVotesToBuy;

    // Tally market funds
    marketFunds[profileId] += purchaseCostBeforeFees; // Incorrect: Adds pre-fee amount

    // Distribute the fees
    applyFees(protocolFee, donation, profileId);

    // ... [omitted for brevity]
}

Issue:

  • The line marketFunds[profileId] += purchaseCostBeforeFees; adds the total purchase cost before deducting fees.
  • Subsequently, applyFees(protocolFee, donation, profileId); deducts fees from the contract's ETH balance but does not adjust marketFunds accordingly.
  • This results in marketFunds[profileId] being greater than the actual ETH available in the contract.

Test Code Snippet

function testLiquidityMismatch() public {
    // Let user1 create a market for their profile
    vm.prank(user1);
    repMarket.createMarket{value: 2 ether}();

    // User2 performs multiple buys
    vm.startPrank(user2);
    repMarket.buyVotes{value: 1 ether}(profileIdUser1, true, 100, 50);
    repMarket.buyVotes{value: 1 ether}(profileIdUser1, true, 100, 50);
    vm.stopPrank();

    // User2 attempts to sell votes exceeding available ETH
    vm.startPrank(user2);
    vm.expectRevert();
    repMarket.sellVotes(profileIdUser1, true, 200, 0); // Expected to revert due to insufficient ETH
    vm.stopPrank();
}

Explanation:

  1. Market Creation: An admin (user1) creates a reputation market with an initial ETH deposit.
  2. Multiple Buys: Another user (user2) buys votes twice, each time sending ETH that includes fees. However, marketFunds is incremented by the total purchase cost before fees are deducted.
  3. Sell Attempt: When user2 attempts to sell votes, the contract checks if marketFunds can cover the proceeds. Due to the earlier mismatch, the actual ETH balance may be insufficient, causing the transaction to revert and effectively locking the market.

Tools Used

  • Manual Review
  • Foundry

Recommendations

Adjust marketFunds by Net Purchase Cost