OrderBook

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

Protocol Fee Bypass via Micropayment Precision Truncation

Root + Impact

Description

The protocol is designed to collect fee, expressed as percentage of 3%, on each order transaction. This fee is calculated and deducted from the order payment amount before funds are transferred to the seller. However, when the order value is sufficiently small, the computed fee rounds down to zero due to Solidity's integer division and lack of floating-point support. As a result, the fee is silently omitted, and the full payment is transferred to the seller without any protocol fee being retained.

uint256 fee = (price * FEE) / PRECISION;
// @> When `price` is too small relative to `PRECISION / FEE`, this expression evaluates to zero.
// @> The protocol unintentionally waives fees for such orders.

Risk

Likelihood: High

This issue will occur whenever the order price is too low for the computed fee to be at least 1 unit of the quote token.
Attackers can exploit this by splitting large orders into multiple smaller ones that individually fall below the minimum fee threshold, thus avoiding protocol fees entirely.

Impact: High

The protocol fails to collect intended fees on low-value orders, resulting in lost revenue. The absence of minimum fee opens the door to fee avoidance and could undermine the economic integrity and long-term viability of the protocol.

Proof of Concept

function test_zeroFeesPOC() public {
// Seller wants to sell 1 wei of WETH for 10 units of USDC
deal(address(weth), alice, 1);
deal(address(usdc), bob, 10);
vm.startPrank(alice);
weth.approve(address(book), 1);
uint256 orderId = book.createSellOrder(address(weth), 1, 10, 2 days);
vm.stopPrank();
assertEq(weth.balanceOf(alice), 0);
assertEq(weth.balanceOf(address(book)), 1);
vm.startPrank(bob);
usdc.approve(address(book), 10);
book.buyOrder(orderId);
vm.stopPrank();
// Fee is 3% of 10 = 0 (truncated)
assertEq(usdc.balanceOf(bob), 0); // Buyer paid full amount
assertEq(usdc.balanceOf(address(book)), 0); // Protocol collected no fee
assertEq(usdc.balanceOf(alice), 10); // Seller received full payment
}

Recommended Mitigation

Introduce a minimum fee enforcement mechansim to ensure the protocol always collects at least one unit of the quote token.

- uint256 fee = (price * FEE) / PRECISION;
+ uint256 fee = (price * FEE) / PRECISION;
+ if (fee == 0) {
+ fee = 1; // Enforce a minimum fee of 1 unit
+ }
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month 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.