Skip to content

Latest commit

 

History

History
105 lines (74 loc) · 4.8 KB

File metadata and controls

105 lines (74 loc) · 4.8 KB

Nice Pearl Canary

Medium

Permanent Market Freeze Through Liquidity Exhaustion

Summary and Impact

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

The Permanent Market Freeze Through Liquidity Exhaustion vulnerability in the ReputationMarket.sol contract allows malicious actors to deplete the market’s liquidity, rendering the market permanently inactive. This occurs through the exploitation of the sellVotes() function, which inadequately manages the reduction of marketFunds when users sell their votes.

Impact:

  • Market Inaccessibility: Once liquidity is drained below a critical threshold, the market becomes unusable for all participants. Remaining vote holders are unable to exit their positions, effectively locking their assets.
  • Operational Disruption: The inability to perform trades compromises the core functionality of the Reputation Market, undermining user trust and the protocol’s integrity.
  • Dependency on Graduation: Resolving the freeze requires market graduation, which may not be promptly actionable, prolonging the market’s inaccessibility.

While the issue does not directly result in financial loss or unauthorized access, it disrupts the intended operations and user interactions within the protocol, justifying a medium severity classification.


Vulnerability Details

The vulnerability arises from the sellVotes() function in the ReputationMarket.sol contract. Specifically, the function allows users to sell their votes without enforcing a safeguard to prevent the depletion of marketFunds below an operational threshold. This oversight enables attackers to drain the market’s liquidity, leading to a permanent freeze of the market.

Code Snippet

function sellVotes(
    uint256 profileId,
    bool isPositive,
    uint256 votesToSell,
    uint256 minimumVotePrice
) public whenNotPaused activeMarket(profileId) nonReentrant {
    _checkMarketExists(profileId);
    (uint256 proceedsBeforeFees, uint256 protocolFee, uint256 proceedsAfterFees) = _calculateSell(
        markets[profileId],
        profileId,
        isPositive,
        votesToSell
    );

    uint256 pricePerVote = votesToSell > 0 ? proceedsBeforeFees / votesToSell : 0;
    if (pricePerVote < minimumVotePrice) {
        revert SellSlippageLimitExceeded(minimumVotePrice, pricePerVote);
    }

    markets[profileId].votes[isPositive ? TRUST : DISTRUST] -= votesToSell;
    votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] -= votesToSell;
    // Tally market funds
    marketFunds[profileId] -= proceedsBeforeFees;

    // Apply protocol fees
    applyFees(protocolFee, 0, profileId);

    // Send the proceeds to the seller
    _sendEth(proceedsAfterFees);

    emit VotesSold(
        profileId,
        msg.sender,
        isPositive,
        votesToSell,
        proceedsAfterFees,
        block.timestamp
    );
    _emitMarketUpdate(profileId);
}

Exploit Steps:

  1. Market Creation and Initial Setup:

    • An attacker (User2) acquires a significant number of votes by purchasing them through buyVotes().
    • The attacker ensures that the market is active and holds enough votes to influence liquidity.
  2. Liquidity Drainage:

    • The attacker initiates multiple sellVotes() transactions to withdraw ETH from the market.
    • Each sell operation reduces marketFunds[profileId] by proceedsBeforeFees without verifying whether the remaining funds are sufficient for future transactions.
  3. Market Freeze:

    • After draining a substantial portion of marketFunds, the attacker performs a final sellVotes() operation that reduces liquidity below the operational threshold.
    • Subsequent users attempting to sell their votes encounter insufficient funds, effectively freezing the market.

Violation of Invariants:

The contract’s invariant stipulates that the contract must never pay out the initial liquidity deposited as part of trading. However, the sellVotes() function allows marketFunds to be reduced without enforcing a lower bound, thereby violating this invariant. This oversight enables the market's liquidity to be exhausted, contradicting the system's expected behavior of maintaining operational funds until market graduation.


Tools Used

Manual Review Foundry

Recommendations

To mitigate the Permanent Market Freeze Through Liquidity Exhaustion vulnerability, the following measures are recommended:

  1. Implement a Minimum Liquidity Reserve:
    • Introduce a minimum threshold for marketFunds that must remain untouched to ensure ongoing market operations.
    • Modify the sellVotes() function to include a check that prevents marketFunds[profileId] from dropping below this reserve.