Festive Mint Platypus
Medium
The invariant described in the code states that the price of trust vote and the distrust vote must always sum up to the market's base price. However there are cases when the getVotePrice(trust) + getVotePrice(distrust) < base price
In ReputationMarket.sol:1000 the developer makes the assumption to round up one of the values and round down the other value so that the sum total will preserved (and be equal to base price)
While this may be true in some cases
v1 = floor(a / (a + b))
v2 = ceil(b / (a + b))
v1 + v2 == 1
it doesn't necessarily hold true due to solidity's precision errors when multiplying integers
There are multiple cases that can be generated which will break this rule all under the constraints laid out by the developer as shown in the fuzzing suite below
N/A
N/A
N/A
When users call the getVotePrice()
function to make a decision to purchase, this sum value deflatation may confuse them.
This would be a low finding since it's in the view function and the buyVotes
or the sellVotes
don't necessarily use the same getVotePrice
function to calculate the changes.
However this condition is stated as an invariant hence I am marking it as Medium.
Add the following in rep.market.test.ts
describe('POC: LMSR invariant check fails for view function', () => {
it('sums up the voting prices to a value less than base price', async () => {
await userA.buyVotes({ votesToBuy: 109090n, isPositive: true });
await userA.buyVotes({ votesToBuy: 3500n, isPositive: false });
const market = await reputationMarket.getMarket(DEFAULT.profileId);
expect(market.trustVotes).to.be.equal(109091n);
expect(market.distrustVotes).to.be.equal(3501n);
const trustVotePrice = await reputationMarket.getVotePrice(DEFAULT.profileId, true);
const distrustVotePrice = await reputationMarket.getVotePrice(DEFAULT.profileId, false);
expect(trustVotePrice + distrustVotePrice).to.be.lessThan(market.basePrice);
});
});
If you want to generate more such values for a diverse range of properties checkout the below fuzz test
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {LMSR} from "../src/LMSR.sol";
import {console2 as console} from "forge-std/console2.sol";
import "forge-std/Test.sol";
contract Confirm is Test {
using Math for uint256;
uint256 public constant DEFAULT_PRICE = 0.01 ether;
uint256 public constant MINIMUM_BASE_PRICE = 0.0001 ether;
// Use this fuzz test to generate examples
function testFuzz_OddsSumTo1(
uint256 basePrice,
uint256 trust,
uint256 distrust,
uint256 liquidityParameter
) public pure {
liquidityParameter = bound(liquidityParameter, 10_000, 100_000);
trust = bound(trust, 1, 133 * liquidityParameter);
distrust = bound(distrust, 1, 133 * liquidityParameter);
basePrice = bound(basePrice, MINIMUM_BASE_PRICE, DEFAULT_PRICE);
uint256 ratio1 = LMSR.getOdds(
trust,
distrust,
liquidityParameter,
true
);
uint256 cost1 = ratio1.mulDiv(basePrice, 1e18, Math.Rounding.Floor);
uint256 ratio2 = LMSR.getOdds(
trust,
distrust,
liquidityParameter,
false
);
uint256 cost2 = ratio2.mulDiv(basePrice, 1e18, Math.Rounding.Ceil);
assertEq(cost1 + cost2, basePrice);
}