OrderBook

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

[M-01] Fee truncation allows complete fee bypass via micro-order splitting

Author Revealed upon completion

Risk:

Impact:

Protocol Failure to Collect Fees on Small Orders: The protocol fails to collect a 3% fee for orders where priceInUSDC < 34. This leads to a direct economic loss.
Exploitability via Micro-Orders: Attackers can exploit this flaw by splitting large trades into micro-orders (e.g., 1 USDC each), effectively bypassing fees altogether.

Revenue Loss and Potential Order Book Spam: Exploiting this vulnerability repeatedly can lead to significant revenue loss for the protocol and potential abuse of the order book with many tiny orders.

Likelihood:

High Likelihood: The issue will occur every time an order is listed with priceInUSDC < 34.

Ease of Exploitation: Traders can easily exploit this flaw by splitting larger trades into micro-orders without being detected, creating significant damage over time.

Description:
The buyOrder() function collects a 3% protocol fee from the buyer in USDC. This fee is calculated as a percentage of the order’s total USDC price and transferred to the contract, with the remainder sent to the seller.

Issue:

Due to Solidity’s integer division, (priceInUSDC * 3) / 100 results in zero when priceInUSDC < 34. This means that the protocol fee will be effectively zero for prices under 34 USDC. A malicious actor can split a large 1000 USDC trade into 1000 smaller 1 USDC trades, completely bypassing the fee collection mechanism.

Root Cause in Code:
The issue lies in the way the protocol fee is calculated, specifically the division operation that results in zero for smaller prices.

Vulnerable Code:

function buyOrder(uint256 _orderId) public {...uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;uint256 sellerReceives = order.priceInUSDC - protocolFee;...}Example of Exploitable Case:Seller creates a micro-order (priceInUSDC = 1):

orderBook.createSellOrder(wETH, 1e18, 1e6, 1000); // 1 token for 1 USDC\

Buyer purchases order:

orderBook.buyOrder(orderId);\

Internally:

uint256 protocolFee = (1e6 * 3) / 100; // = 30_000 => fineHowever, for orders with priceInUSDC = 33, the calculation fails:
uint256 protocolFee = (33 * 3) / 100; // = 0 (no fee collected)

Proof of Concept:

Exploit Steps:

Seller creates micro-orders: A malicious user can create many micro-orders with a priceInUSDC less than 34.

orderBook.createSellOrder(wETH, 1e18, 1e6, 1000); // 1 token for 1 USDC\

Buyer exploits the bug: When the buyer purchases these micro-orders, the fee calculation becomes zero for orders with priceInUSDC < 34.

uint256 protocolFee = (33 * 3) / 100; // This results in 0 fee collected

Recommended Mitigation:

To fix this issue, the protocol fee calculation should be updated to handle small order amounts properly. Suggested changes include:

Change Fee Variables to Use Basis Points:

uint256 public constant FEE_BPS = 300; // 3% in basis pointsuint256 public constant PRECISION_BPS = 10000; // Precision set for basis pointsUpdate Fee Calculation:

Update the fee calculation formula to use the new constants:

uint256 protocolFee = (order.priceInUSDC * FEE_BPS) / PRECISION_BPS;\

Enforce Minimum Fee:

If the calculated fee is zero (due to small order size), enforce a minimum fee to ensure the protocol collects a fee even on micro-orders:

if (protocolFee == 0 && order.priceInUSDC > 0) {
protocolFee = 1; // Enforce minimum flat fee
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
5 days ago
yeahchibyke Lead Judge about 22 hours 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.