Rich Charcoal Sardine
Medium
Rounding must be in favor of the protocol. In the _calcCost
function, however, rounding is incorrectly applied, which could lead to a DoS of subsequent market actions.
In the _calcCost
function, rounding is done based on isPositive
, which is not in favor of the protocol.
https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L1054-L1058
cost = positiveCostRatio.mulDiv(
market.basePrice,
1e18,
isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil
);
And, the marketFunds
of the market is updated by using the cost.
https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L480
marketFunds[profileId] += purchaseCostBeforeFees;
marketFunds[profileId] -= proceedsBeforeFees;
So, a bit bigger amount can be reduced from marketFunds
, if a buyer buys more than one UNTRUST
votes at once and sells them individually. Because rounding up is done only once when buying, but more than one times of rounding up are done when selling them.
This may lead to a DoS for selling the last votes.
Consider the following scenario:
- A market is created with 0
createCost
. - Alice buys 10
UNTRUST
votes at once. - Alice sells all of them by selling one vote at a time.
As a result, selling the last vote of the market can be DoSed due to overflow from rounding error.
This DoS is possible in two kind of markets, where the initial marketFunds[profileId]
is 0.
First, markets created by an ADMIN has no minimum creation cost. https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L341-L342
// when an admin creates a market, there is no minimum creation cost; use whatever they sent
marketFunds[profileId] = msg.value;
Second, this DoS can well happen in markets with 0 creationCost
, because there is no minimum check for creationCost.
https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L366-L382
function addMarketConfig(
uint256 liquidity,
uint256 basePrice,
uint256 creationCost
) public onlyAdmin whenNotPaused returns (uint256) {
if (liquidity < 100) revert InvalidMarketConfigOption("Min liquidity not met");
if (basePrice < MINIMUM_BASE_PRICE) revert InvalidMarketConfigOption("Insufficient base price");
marketConfigs.push(
MarketConfig({ liquidity: liquidity, basePrice: basePrice, creationCost: creationCost })
);
uint256 configIndex = marketConfigs.length - 1;
emit MarketConfigAdded(configIndex, marketConfigs[configIndex]);
return configIndex;
}
none
none
none
Last vote of a market cannot be sold.
Rounding should be done based on isBuy
.
cost = positiveCostRatio.mulDiv(
market.basePrice,
1e18,
- isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil
+ isBuy ? Math.Rounding.Ceil : Math.Rounding.Floor
);