Skip to content

Latest commit

 

History

History
96 lines (68 loc) · 3.02 KB

File metadata and controls

96 lines (68 loc) · 3.02 KB

Fluffy Charcoal Toad

High

LMSR Price Manipulation via Inconsistent Rounding

Summary

Inconsistent rounding modes in price calculations will cause a financial loss for the protocol and market participants as an attacker can exploit price differences through sequential buy/sell operations.

Root Cause

In ReputationMarket.sol:_calcVotePrice the rounding modes are inconsistent between trust (Floor) and distrust (Ceil) votes:

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

return odds.mulDiv(
    market.basePrice, 
    1e18, 
    isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil
);

Internal Pre-conditions

  1. Market needs to have active trading (not graduated)
  2. Market needs to have sufficient liquidity for trades
  3. Market votes need to be at relatively low numbers where rounding impact is significant

External Pre-conditions

No response

Attack Path

  1. Attacker calls buyVotes(profileId, true, largeAmount) to buy trust votes at floor-rounded (lower) price
  2. Attacker immediately calls sellVotes(profileId, true, largeAmount) to sell at ceiling-rounded (higher) price
  3. Attacker repeats steps 1-2 to maximize profit
  4. Each cycle generates profit from the rounding difference

Impact

The protocol suffers continuous losses proportional to the volume of trades. Attacker gains the difference between ceiling and floor rounded prices on each cycle. At low vote counts, this can represent a significant percentage of trade value.

PoC

function testLMSRManipulation() public {
    // Setup market with low votes
    uint256 profileId = 1;
    uint256 initialVotes = 100;
    market.createMarket{value: 1 ether}();
    
    // Initial state
    uint256 attackerBalance = address(this).balance;
    
    // Execute attack cycle
    for(uint i = 0; i < 5; i++) {
        uint256 buyPrice = market.getVotePrice(profileId, true);
        market.buyVotes{value: buyPrice * 10}(profileId, true, 10);
        
        uint256 sellPrice = market.getVotePrice(profileId, true);
        market.sellVotes(profileId, true, 10);
        
        // Profit from each cycle
        uint256 profit = sellPrice - buyPrice;
        console.log("Profit from cycle:", profit);
    }
    
    assertGt(address(this).balance, attackerBalance);
}

Mitigation

  1. Use consistent rounding for both trust and distrust
function _calcVotePrice(Market memory market, bool isPositive) private pure returns (uint256) {
    require(market.votes[TRUST] >= MIN_VOTES && market.votes[DISTRUST] >= MIN_VOTES, 
            "Below minimum votes");
    
    uint256 odds = LMSR.getOdds(
        market.votes[TRUST],
        market.votes[DISTRUST],
        market.liquidityParameter,
        isPositive
    );
    
    return odds.mulDiv(market.basePrice, 1e18, Math.Rounding.Floor);
}
  1. Add minimum vote thresholds to reduce impact of rounding
  2. Implement trade size limits
  3. Add cooldown period between buy and sell operations