Skip to content

Latest commit

 

History

History
91 lines (66 loc) · 3.63 KB

File metadata and controls

91 lines (66 loc) · 3.63 KB

Cool Mango Bobcat

Medium

LMSR Market Cost Sign Conversion Bug Forces Payments for Sell Operations

Summary

The ReputationMarket's _calcCost function contains a critical flaw in the handling of LMSR cost calculations, specifically in how it processes costs for sell operations. The core of the issue lies in this conversion:

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

uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1);

When users sell votes in an LMSR market, the cost should be negative, representing funds they receive back. However, by converting negative costs to positive values, the contract erroneously requires users to pay when selling votes instead of receiving proceeds.

While the market's state transitions and LMSR price calculations continue to function correctly - properly tracking vote counts and adjusting prices according to market movements - the economic incentives are broken. Users must pay both to enter and exit positions, with no way to withdraw their position's value without incurring additional costs.

This creates a critical usability problem where market participants' capital becomes effectively trapped, as exiting a position requires payment rather than generating proceeds. The bug does not affect the market's price discovery mechanism or state calculations, but it makes the market economically non-viable for participants.

Proof of Concept

Scenario:

  1. Market starts balanced:

    • 100 trust votes, 100 distrust votes
    • 0.01 ETH per vote base price
  2. Alice buys 50 trust votes:

    • Pays 0.5 ETH
    • Market now: 150 trust, 100 distrust
    • Price of trust votes increases per LMSR
  3. Alice sells 50 trust votes:

    • Should receive ~0.5 ETH due to similar market state
    • Instead must pay ~0.5 ETH due to sign conversion bug
    • Market changes to 100 trust, 100 distrust
    • Prices adjust accordingly per LMSR
  4. Bob with existing votes:

    • Market state and prices still move normally
    • But selling requires payment instead of generating proceeds
    • The magnitude of required payment follows LMSR curves
  5. Market operation:

    • State transitions and price calculations still follow LMSR math
    • Only the direction of payment is inverted for sells
    • Price signals still reflect vote counts, but economic incentives are broken

Recommended mitigation steps

The fix requires proper handling of signed costs in the LMSR calculations. A corrected implementation should preserve the sign information throughout the cost calculation process:

function _calcCost(
    Market memory market,
    bool isPositive,
    bool isBuy,
    uint256 amount
) private pure returns (int256 signedCost) {
    uint256[] memory currentState = new uint256[](2);
    uint256[] memory outcomeState = new uint256[](2);
    
    currentState[0] = market.votes[TRUST];
    currentState[1] = market.votes[DISTRUST];
    outcomeState[0] = currentState[0];
    outcomeState[1] = currentState[1];
    
    if (isBuy) {
        outcomeState[isPositive ? 0 : 1] += amount;
    } else {
        outcomeState[isPositive ? 0 : 1] -= amount;
    }
    
    signedCost = LMSR.getCost(
        currentState[0],
        currentState[1],
        outcomeState[0],
        outcomeState[1],
        market.liquidityParameter
    );
    
    // Scale the signed cost while preserving its sign
    signedCost = signedCost * int256(market.basePrice) / 1e18;
}

Then the buyVotes and sellVotes functions would need to be updated to handle the signed costs appropriately, paying when positive and receiving when negative.