Skip to content

Latest commit

 

History

History
102 lines (84 loc) · 3.97 KB

File metadata and controls

102 lines (84 loc) · 3.97 KB

Urban Obsidian Jay

High

Missing minVotesToBuy, maxVotesToBuy and currentVotesToBuy Check.

Summary

In ReputationMarket.sol::buyVotes(), there is no check for minVotesToBuy, maxVotesToBuy, and currentVotesToBuy.

Root Cause

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

Internal pre-conditions

N/A

External pre-conditions

N/A

Attack Path

Malicious users may call the buyVotes() function with minVotesToBuy = maxVotesToBuy = 0. As a result, participants[profileId].length could be increased unintentionally.

Impact

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

  • Graduation: the intent is that upon graduation, each holder of trust and distrust votes receives equivalent ERC-20 tokens
  • representing their position. These tokens will be freely tradable, without the reciprocal pricing mechanism of this contract.

If the length of participants is increased unintentionally, after graduation, each holder of trust and distrust votes may not receive their ERC-20 tokens due to running out of gas.

PoC

ReputationMarket.sol
        function buyVotes(
            uint256 profileId,
            bool isPositive,
            uint256 maxVotesToBuy,
            uint256 minVotesToBuy
        ) public payable whenNotPaused activeMarket(profileId) nonReentrant {
            _checkMarketExists(profileId);
            // preliminary check to ensure this is enough money to buy the minimum requested votes.
            (, , , uint256 total) = _calculateBuy(markets[profileId], isPositive, minVotesToBuy);
            if (total > msg.value) revert InsufficientFunds();

            (
                uint256 purchaseCostBeforeFees,
                uint256 protocolFee,
                uint256 donation,
                uint256 totalCostIncludingFees
            ) = _calculateBuy(markets[profileId], isPositive, maxVotesToBuy);
            uint256 currentVotesToBuy = maxVotesToBuy;
            // if the cost is greater than the maximum votes to buy,
            // decrement vote count and recalculate until we identify the max number of votes they can afford
            while (totalCostIncludingFees > msg.value) {
                currentVotesToBuy--;
                (purchaseCostBeforeFees, protocolFee, donation, totalCostIncludingFees) = _calculateBuy(
                    markets[profileId],
                    isPositive,
                    currentVotesToBuy
                );
            }

            // Update market state
            markets[profileId].votes[isPositive ? TRUST : DISTRUST] += currentVotesToBuy;
            votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] += currentVotesToBuy;

            // Add buyer to participants if not already a participant
474:        if (!isParticipant[profileId][msg.sender]) {
                participants[profileId].push(msg.sender);
                isParticipant[profileId][msg.sender] = true;
            }

            // tally market funds
            marketFunds[profileId] += purchaseCostBeforeFees;

            // Distribute the fees
            applyFees(protocolFee, donation, profileId);

            // Calculate and refund remaining funds
            uint256 refund = msg.value - totalCostIncludingFees;
            if (refund > 0) _sendEth(refund);
            emit VotesBought(
                profileId,
                msg.sender,
                isPositive,
                currentVotesToBuy,
                totalCostIncludingFees,
                block.timestamp
            );
            _emitMarketUpdate(profileId);
        }

Mitigation

+       require(minVotesToBuy <= maxVotesToBuy,"")
+       if (currentVotesToBuy > 0)
474:        if (!isParticipant[profileId][msg.sender]) {
                participants[profileId].push(msg.sender);
                isParticipant[profileId][msg.sender] = true;
            }