Skip to content

Latest commit

 

History

History
111 lines (84 loc) · 3.36 KB

File metadata and controls

111 lines (84 loc) · 3.36 KB

Loud Onyx Perch

Medium

Wrong vote price/cost will be returned due to inversed rounding condition

Summary

When the protocol calculates a vote price, it computes the costRatio then which is divided by the price's base price, so some rounding should be made here, currently, the protocol rounds the answer down for TRUST votes:

cost = positiveCostRatio.mulDiv(
  market.basePrice,
  1e18,
  isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil
);

On the other hand, in _calcVotePrice, which is used for the same goal but represents a midpoint between the next marginal transaction, the comment clearly states that the answer should be rounded up for TRUST votes:

  function _calcVotePrice(Market memory market, bool isPositive) private pure returns (uint256) {
    // odds are in a ratio of N / 1e18
    uint256 odds = LMSR.getOdds(
      market.votes[TRUST],
      market.votes[DISTRUST],
      market.liquidityParameter,
      isPositive
    );
    // multiply odds by base price to get price; divide by 1e18 to get price in wei
@>  // round up for trust, down for distrust so that prices always equal basePrice
    return
      odds.mulDiv(market.basePrice, 1e18, isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil);
  }

This results in misleading vote costs being returned to the users.

Root Cause

Inversed rounding conditions in both _calcVotePrice and _calcCost:

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

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

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

The wrong vote price/cost will be returned.

PoC

Add the following test in ethos/packages/contracts/test/reputationMarket/rep.fees.test.ts:

describe('PoC', () => {
  it('vote price rounding inveresed', async () => {
    const user1 = ethosUserA.signer;

    await reputationMarket
      .connect(user1)
      .buyVotes(DEFAULT.profileId, true, 1, 1, { value: DEFAULT.creationCost });

    expect(await reputationMarket.getVotePrice(DEFAULT.profileId, true)).to.be.equal(
      5002499999791666n, // Rounded down
    );
  });
});

Mitigation

Inverse the rounding in both _calcVotePrice and _calcCost.

  function _calcVotePrice(Market memory market, bool isPositive) private pure returns (uint256) {
    // ...

    // multiply odds by base price to get price; divide by 1e18 to get price in wei
    // round up for trust, down for distrust so that prices always equal basePrice
-   return odds.mulDiv(market.basePrice, 1e18, isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil);
+   return odds.mulDiv(market.basePrice, 1e18, isPositive ? Math.Rounding.Ceil : Math.Rounding.Floor);
  }

  function _calcCost(
    Market memory market,
    bool isPositive,
    bool isBuy,
    uint256 amount
  ) private pure returns (uint256 cost) {
    // ...

    cost = positiveCostRatio.mulDiv(
      market.basePrice,
      1e18,
-     isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil
+     isPositive ? Math.Rounding.Ceil : Math.Rounding.Floor
    );
  }