OrderBook

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

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

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
6 months ago
yeahchibyke Lead Judge 6 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.

Give us feedback!