Tiny Mulberry Buffalo
Medium
When calculating the votes cost, the rounding is based on whether the votes acted on are TRUST/ DISTRUST, and not based on whether it is a buy/ sell. This could cause the market to go insolvent as users would be able to sell their votes for just a few wei more than what they bought it for. Then, if the market was created by an ReputationMarket admin, which did not send any extra funds, this could cause the user trying to sell the last vote, to be unable to do so, due to insufficient funds.
int256 costRatio = LMSR.getCost(
market.votes[TRUST],
market.votes[DISTRUST],
voteDelta[0],
voteDelta[1],
market.liquidityParameter
);
uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1);
// multiply cost ratio by base price to get cost; divide by 1e18 to apply ratio
cost = positiveCostRatio.mulDiv(
market.basePrice,
1e18,
isPositive ? Math.Rounding.Floor : Math.Rounding.Ceil. // @audit - wrong rounding
);
Wrong rounding.
- User buys TRUST tokens in large batch.
- User then sells them into smaller batches
- Due to individual rounding up in each sell, the user sells them for a few more wei than what they bought it for, making the market go insolvent
- Due to this, last user to withdraw is not able to (due to insufficient funds)
DoS
As the codebase lacked a foundry test suite, I imported just the needed files. The attached below showcases the issue. Note that the initial votes values can be pretty much any. These were chosen at random as the bug was found via fuzzing.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/LMSR.sol";
contract MyTest is Test {
uint256 basePrice = 0.16325e18;
struct Market {
uint256[2] votes;
}
function testIsBuyRoundingIssue() public {
uint256 initYesVotes = 48;
uint256 initNoVotes = 34;
int256 costRatio = LMSR.getCost(
initYesVotes,
initNoVotes,
initYesVotes + 20,
initNoVotes,
1000);
initYesVotes += 20;
uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1 );
uint256 cost = positiveCostRatio * basePrice / 1e18;
if ((positiveCostRatio * basePrice) % 1e18 != 0 ) cost++;
uint256 totalSold;
for (uint256 i; i < 20; i++ ){
int256 costRatio = LMSR.getCost(
initYesVotes,
initNoVotes,
--initYesVotes,
initNoVotes,
1000);
uint256 positiveCostRatio = costRatio > 0 ? uint256(costRatio) : uint256(costRatio * -1 );
uint256 cost = positiveCostRatio * basePrice / 1e18;
if ((positiveCostRatio * basePrice) % 1e18 != 0 ) cost++;
totalSold += cost;
}
console.log(totalSold);
console.log(cost);
assertGt(totalSold, cost); // totalSold exceeds cost
}
}
Logs:
Ran 1 test for test/Counter.t.sol:MyTest
[PASS] testIsBuyRoundingIssue() (gas: 561183)
Logs:
1652088896517131430
1652088896517131423
Change the rounding, to be based on whether it is a buy or a sell.