OrderBook

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

Fee Precision Loss + Protocol Revenue Loss

Description

  • The protocol should collect a 3% fee on all transactions to generate revenue for the platform.

  • The fee calculation uses integer division which rounds down to zero for small transactions, causing the protocol to lose fee revenue on orders below a certain threshold.

function buyOrder(uint256 _orderId) public {
// ...
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION; // FEE=3, PRECISION=100
uint256 sellerReceives = order.priceInUSDC - protocolFee;
// ...
}

Risk

Likelihood:

  • When users place orders with priceInUSDC values less than 34 USDC (since 33 * 3 / 100 = 0)

  • When the contract is used for micro-transactions or testing with small amounts

  • When users deliberately place many small orders to avoid fees

Impact:

  • Protocol loses fee revenue on a significant portion of small transactions

  • Users can exploit this by splitting large orders into many small fee-free orders

  • Economic model of the protocol becomes unsustainable for small-value trading

Proof of Concept

Fee Avoidance Exploit: This demonstrates how users can avoid protocol fees by splitting large orders into many small ones.

function testZeroFeeExploit() public {
// Alice creates multiple small sell orders to avoid fees
vm.startPrank(alice);
weth.approve(address(book), 1e18);
// Create 10 orders of 33 USDC each (total 330 USDC)
uint256[] memory orderIds = new uint256[](10);
for (uint i = 0; i < 10; i++) {
orderIds[i] = book.createSellOrder(address(weth), 0.1e18, 33e6, 1 days);
}
vm.stopPrank();
// Dan buys all orders
vm.startPrank(dan);
usdc.approve(address(book), 330e6);
uint256 feesBefore = book.totalFees();
for (uint i = 0; i < 10; i++) {
book.buyOrder(orderIds[i]);
}
uint256 feesAfter = book.totalFees();
vm.stopPrank();
// No fees collected despite 330 USDC in transactions
assertEq(feesAfter - feesBefore, 0);
// Compare with single large order
vm.startPrank(alice);
uint256 largeOrderId = book.createSellOrder(address(weth), 1e18, 330e6, 1 days);
vm.stopPrank();
vm.startPrank(dan);
book.buyOrder(largeOrderId);
vm.stopPrank();
// Large order would generate 9.9 USDC in fees
assertEq(book.totalFees(), 9.9e6);
}

Mathematical Breakdown:

  • Fee calculation: protocolFee = (priceInUSDC * 3) / 100

  • For 33 USDC: (33 * 3) / 100 = 99 / 100 = 0 (integer division rounds down)

  • For 34 USDC: (34 * 3) / 100 = 102 / 100 = 1 (first value that generates fee)

  • By splitting 330 USDC into 10×33 USDC orders, user pays 0 fees instead of 9.9 USDC

  • This represents a 100% fee avoidance on substantial transaction volume

Recommended Mitigation

+ uint256 public constant MIN_FEE = 1e6; // Minimum 1 USDC fee
function buyOrder(uint256 _orderId) public {
// ...
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
+ if (protocolFee < MIN_FEE) {
+ protocolFee = MIN_FEE;
+ }
uint256 sellerReceives = order.priceInUSDC - protocolFee;
// ...
}

Why this works:

  • Guarantees minimum revenue of 1 USDC per transaction regardless of order size

  • Eliminates fee avoidance through order splitting strategies

  • Maintains percentage-based fees for larger orders while protecting against small order exploitation

  • Simple implementation that doesn't require complex precision arithmetic

Alternative approaches:

  1. Higher precision: Use basis points (10000) instead of percentage (100) for more granular fees

  2. Minimum order size: Require orders above a certain threshold (e.g., 100 USDC minimum)

  3. Tiered fee structure: Different fee rates based on order size brackets

  4. Gas + percentage: Combine fixed gas cost coverage with percentage fees

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!