Skip to content

Latest commit

 

History

History
82 lines (60 loc) · 2.76 KB

File metadata and controls

82 lines (60 loc) · 2.76 KB

Broad Khaki Wasp

Medium

Incorrect calculation of pricePerVote in the ReputationMarket.sellVotes() function.

Summary

For slippage checks, pricePerVote is calculated and compared with minimumVotePrice. However, pricePerVote is calculated based on proceedsBeforeFees, which includes the protocolFee, rather than proceedsAfterFees, the actual amount received by the seller.

Root Cause

The sellVotes() function calculates pricePerVote as proceedsBeforeFees / votesToSell.

In this context, proceedsBeforeFees includes the protocolFee. The actual total amount the seller receives is proceedsAfterFees, not proceedsBeforeFees. Therefore, pricePerVote should be calculated as proceedsAfterFees / votesToSell.

Since pricePerVote is calculated slightly higher, the slippage check may pass unintentionally, leading to a lower sell amount than the seller intended.

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

553     uint256 pricePerVote = votesToSell > 0 ? proceedsBeforeFees / votesToSell : 0;
        if (pricePerVote < minimumVotePrice) {
          revert SellSlippageLimitExceeded(minimumVotePrice, pricePerVote);
        }

        ...
      }

Internal pre-conditions

External pre-conditions

Attack Path

Impact

pricePerVote is calculated slightly higher, which could result in the seller receiving less than intended.

PoC

Mitigation

Use proceedsAfterFees instead of proceedsBeforeFees.

      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;
+       uint256 pricePerVote = votesToSell > 0 ? proceedsAfterFees / votesToSell : 0;
        if (pricePerVote < minimumVotePrice) {
          revert SellSlippageLimitExceeded(minimumVotePrice, pricePerVote);
        }

        ...
      }