The issue occurs because Solidity performs integer division, which truncates any remainder. This means:
Small amounts: If order.priceInUSDC = 1, then protocolFee = (1 * 3) / 100 = 0 (truncated from 0.03)
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:
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
The proof of concept demonstrates the precision loss vulnerability. Add this code to the test file and run it.
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:
Protocol Suffers Potential Revenue Leakage due to Precision Loss in Fee Calculation
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.