OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Precision Loss in Protocol Fee Calculation

Integer division truncation in fee calculation leads to precision loss and inconsistent fee collection.

Description

The issue occurs because Solidity performs integer division, which truncates any remainder. This means:

  1. Small amounts: If order.priceInUSDC = 1, then protocolFee = (1 * 3) / 100 = 0 (truncated from 0.03)

  2. Amounts with remainders: If order.priceInUSDC = 101, then protocolFee = (101 * 3) / 100 = 3 (truncated from 3.03)

Examples of Precision Loss

Example 1: Very small order

  • priceInUSDC = 1 (0.000001 USDC)

  • protocolFee = (1 * 3) / 100 = 0

  • sellerReceives = 1 - 0 = 1

  • Result: No fee collected, seller gets full amount

Example 2: Amount with remainder

  • priceInUSDC = 101 (0.000101 USDC)

  • protocolFee = (101 * 3) / 100 = 3 (0.000003 USDC)

  • sellerReceives = 101 - 3 = 98 (0.000098 USDC)

  • Result: Fee is 2.97% instead of 3%

Example 3: Large order with precision loss

  • priceInUSDC = 1,234,567,890,001 (1,234,567.890001 USDC)

  • protocolFee = (1,234,567,890,001 * 3) / 100 = 37,037,036,700 (37,037.0367 USDC)

  • sellerReceives = 1,234,567,890,001 - 37,037,036,700 = 1,197,530,853,301 (1,197,530.853301 USDC)

  • Result: Fee is 2.999999% instead of 3%

The vulnerability exists in the OrderBook.sol:buyOrder function where the protocol fee is calculated using integer division:

@> uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
@> uint256 sellerReceives = order.priceInUSDC - protocolFee;

Risk

Likelihood: HIGH

  • Affects every order that doesn't result in a clean division

Impact: MEDIUM

  • Revenue Loss: Protocol loses fees on small orders and gets reduced fees on larger orders

  • Inconsistent Fee Rates: Actual fee rate varies from 0% to 3% depending on order size

  • Dust Accumulation: Small amounts that should be fees remain with sellers

Proof of Concept

The proof of concept demonstrates the precision loss vulnerability. Add this code to the test file and run it.

function testPrecisionLoss() public {
// Create an order with price that will cause precision loss
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
uint256 aliceId = book.createSellOrder(
address(wbtc),
2e8,
182_324_222,
2 days
);
vm.stopPrank();
// Buy the order
usdc.mint(dan, 182_324_222);
vm.startPrank(dan);
usdc.approve(address(book), 182_324_222);
book.buyOrder(aliceId);
vm.stopPrank();
// Check that fee is less than 3%
uint256 expectedFee = (182_324_222 * 3);
uint256 actualFee = book.totalFees() * 100;
assertLt(
actualFee,
expectedFee,
"Fee should be less than expected due to precision loss"
);
}

Recommended Mitigation

  • Round Up Fees
    Modify the fee calculation to round up instead of truncating. Ensures the protocol always gets at least the intended fee by rounding up:

- uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
+ uint256 protocolFee = (order.priceInUSDC * FEE + PRECISION - 1) / PRECISION;
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Fee can be bypassed

Protocol Suffers Potential Revenue Leakage due to Precision Loss in Fee Calculation

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.