Skip to content

Latest commit

 

History

History
100 lines (70 loc) · 3.55 KB

File metadata and controls

100 lines (70 loc) · 3.55 KB

Narrow Lipstick Moose

Medium

Rounding Issue in sellVotes Function

Summary

The sellVotes function in the contract calculates the price per vote during a vote sell transaction by dividing proceedsBeforeFees by votesToSell. However, this calculation uses Solidity's default integer division, which truncates fractional results (rounding down), potentially leading to inaccurate price calculations. This rounding issue could lead to smaller than expected sale proceeds for the seller and misalignments in the vote price, especially in markets where fractional values are significant.

Root Cause

The issue can be observed here: https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L539-L578

  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);
  }

Here is the line:

  uint256 pricePerVote = votesToSell > 0 ? proceedsBeforeFees / votesToSell : 0; //@audit 

This division performs integer division, which truncates decimal values. This results in rounding down instead of rounding to the nearest integer. When calculating the price per vote, the function ignores fractional parts of the price, which may lead to a smaller price being applied than intended.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

Sellers receive less than expected from the sale of votes due to the rounding down of fractional values. Example: If the proceeds before fees are 5 ETH and votesToSell = 3, the price per vote should be 1.666... ETH, but it will be truncated to 1 ETH instead, resulting in a loss of 0.666... ETH per vote for the seller.

PoC

Let’s assume:

proceedsBeforeFees = 5 ETH (5e18 Wei) votesToSell = 3 (3 votes to sell) minimumVotePrice = 1 ETH When proceedsBeforeFees is divided by votesToSell:

pricePerVote = 5 ETH / 3 = 1.6666666... ETH However, Solidity will perform integer division, so the result will be truncated:

pricePerVote = 1 ETH (due to truncation of fractional values) Thus, even though the exact price should have been 1.666... ETH, the actual pricePerVote is 1 ETH, and this could cause the seller to receive less than expected.

Mitigation

Implement the use of mulDiv with the appropriate rounding mode or enhance the price calculation with explicit rounding logic to ensure that rounding errors do not impact the fairness or integrity of vote transactions.