Skip to content

Latest commit

 

History

History
114 lines (87 loc) · 3.54 KB

File metadata and controls

114 lines (87 loc) · 3.54 KB

Tiny Mulberry Buffalo

Medium

Wrong rounding will make a market insolvent

Summary

When calculating the votes cost, the rounding is based on whether the votes acted on are TRUST/ DISTRUST, and not based on whether it is a buy/ sell. This could cause the market to go insolvent as users would be able to sell their votes for just a few wei more than what they bought it for. Then, if the market was created by an ReputationMarket admin, which did not send any extra funds, this could cause the user trying to sell the last vote, to be unable to do so, due to insufficient funds.

    int256 costRatio = LMSR.getCost(
      market.votes[TRUST],
      market.votes[DISTRUST],
      voteDelta[0],
      voteDelta[1],
      market.liquidityParameter
    );

    uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1);
    // multiply cost ratio by base price to get cost; divide by 1e18 to apply ratio
    cost = positiveCostRatio.mulDiv(
      market.basePrice,
      1e18,
      isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil.  // @audit - wrong rounding
    );

Root Cause

Wrong rounding.

Attack Path

  1. User buys TRUST tokens in large batch.
  2. User then sells them into smaller batches
  3. Due to individual rounding up in each sell, the user sells them for a few more wei than what they bought it for, making the market go insolvent
  4. Due to this, last user to withdraw is not able to (due to insufficient funds)

Impact

DoS

Affected Code

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

PoC

As the codebase lacked a foundry test suite, I imported just the needed files. The attached below showcases the issue. Note that the initial votes values can be pretty much any. These were chosen at random as the bug was found via fuzzing.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/LMSR.sol";

contract MyTest is Test {
    uint256 basePrice = 0.16325e18;
    struct Market { 
        uint256[2] votes;
    }

    function testIsBuyRoundingIssue() public { 
        uint256 initYesVotes = 48;
        uint256 initNoVotes = 34;

        int256 costRatio = LMSR.getCost(
            initYesVotes, 
            initNoVotes,    
            initYesVotes + 20, 
            initNoVotes, 
            1000);

        initYesVotes += 20;
        uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1 );
        uint256 cost = positiveCostRatio * basePrice / 1e18;
        if ((positiveCostRatio * basePrice) % 1e18 != 0 ) cost++;    


        uint256 totalSold;
        for (uint256 i; i < 20; i++ ){
            int256 costRatio = LMSR.getCost(
                initYesVotes, 
                initNoVotes, 
                --initYesVotes, 
                initNoVotes, 
                1000);

            uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1 );
            uint256 cost = positiveCostRatio * basePrice / 1e18;
            if ((positiveCostRatio * basePrice) % 1e18 != 0 ) cost++;

            totalSold += cost;
        }

        console.log(totalSold);
        console.log(cost);
        assertGt(totalSold, cost);      // totalSold exceeds cost

    }
}

Logs:

Ran 1 test for test/Counter.t.sol:MyTest
[PASS] testIsBuyRoundingIssue() (gas: 561183)
Logs:
  1652088896517131430
  1652088896517131423

Mitigation

Change the rounding, to be based on whether it is a buy or a sell.