Skip to content

Latest commit

 

History

History
103 lines (66 loc) · 3.33 KB

File metadata and controls

103 lines (66 loc) · 3.33 KB

Decent Currant Halibut

High

LMSR:: _cost() Precision Loss and Incorrect unwarapping from UD60x18 back to uint256

Summary

The LMSR:: _cost() function loses precision and incorrectly scales the result. This is caused by the multiplication after unwrapping from UD60x18 to uint256. When unwrapping, the precision of UD60x18(which scales the result by 1e18) is lost, and the subsequent multiplication with b (which is in uint256 format) causes the result to be much larger than expected.

https://github.com/sherlock-audit/2024-12-ethos-update/blob/main/ethos/packages/contracts/contracts/utils/LMSR.sol#L133

Root Cause

function _cost(
    uint256 yesVotes,
    uint256 noVotes,
    uint256 liquidityParameter
  ) public pure returns (uint256 costResult) {
    // Compute e^(yes/b) and e^(no/b)
    (UD60x18 yesExp, UD60x18 noExp) = _getExponentials(yesVotes, noVotes, liquidityParameter);

    // sumExp = e^(yes/b) + e^(no/b)
    UD60x18 sumExp = yesExp.add(noExp);

    // lnVal = ln(e^(yes/b) + e^(no/b))
    UD60x18 lnVal = sumExp.ln();

    // Unwrap lnVal and multiply by b (also in UD60x18) to get cost

    
    uint256 lnValUnwrapped = unwrap(lnVal);
    costResult = lnValUnwrapped * liquidityParameter;
  }

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

Financial Miscalculation: The loss of precision can lead to significant errors in cost calculations, which directly affect the pricing mechanism of votes in the market. This could result in: Overcharging or undercharging for votes.

Incorrect market dynamics where price does not accurately reflect supply and demand. Potential arbitrage opportunities if the discrepancy is large enough. Market Manipulation: If traders can predict or exploit the precision loss, they might be able to manipulate market outcomes or profit from the mispricing.

PoC

function test__IncorrectScaling() public {
    
        uint256 yesVotes = 1000;
        uint256 noVotes = 1000;

        uint256 cost = LMSR._cost(yesVotes, noVotes, LIQUIDITY_PARAMETER);

        //With equal votes the costs should be around b * ln(2) in UD60x18 format
        uint256 expectedCost = 693147180559945309 * LIQUIDITY_PARAMETER; //ln(2) * 1e18 * b

        //Check if there is significant deviation from the expected cost due to incorrect scaling
        assertApproxEqAbs(cost, expectedCost, 1e15, "Cost is not approximately equal to b * ln(2)"); //Allowing for some imprecision
}

Mitigation

Recommended Mitigation:

Implement the calculation in UD60x18 format throughout, only unwrapping the result at the very end:

function _cost(
    uint256 yesVotes,
    uint256 noVotes,
    uint256 liquidityParameter
  ) public pure returns (uint256 costResult) {
    (UD60x18 yesExp, UD60x18 noExp) = _getExponentials(yesVotes, noVotes, liquidityParameter);
    UD60x18 sumExp = yesExp.add(noExp);
    UD60x18 lnVal = sumExp.ln();
    
    // Convert liquidityParameter to UD60x18 for consistent arithmetic
    UD60x18 b = convert(liquidityParameter);
    
    // Perform multiplication in UD60x18 format before unwrapping
    costResult = unwrap(lnVal.mul(b));
  }