OrderBook

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

M - Dust Orders Bypass Fee Collection and Pollute Order Book

Root + Impact

Description

The OrderBook contract should collect a 3% fee on all trades to generate protocol revenue. Users create sell orders with a specified price in USDC, and when bought, the protocol deducts fees before transferring payment to the seller.

The fee calculation uses integer division that rounds down to zero for orders priced at 33 USDC units or less, allowing attackers to create dust orders that completely bypass fee collection while cluttering the order book storage.

function buyOrder(uint256 _orderId) public {
// ... validation checks
order.isActive = false;
@> uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
// For orders ≤ 33 units: (33 * 3) / 100 = 99 / 100 = 0 (rounds down)
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
// ... rest of function
}

Risk

Likelihood:

  • Any user can exploit this by creating orders priced at 33 or fewer USDC units

  • No special conditions or complex setup required - simply call createSellOrder with low prices

  • Attack can be automated to create hundreds of fee-free orders

Impact:

  • Complete loss of protocol revenue on dust orders (100% fee bypass)

  • Permanent state bloat as each order consumes 7 storage slots even after completion

  • Order book pollution degrading user experience and order indexing

  • Enables wash trading and market manipulation without fee costs

Proof of Concept

This test demonstrates how an attacker can exploit the integer division precision loss to completely bypass protocol fees. By creating orders priced at exactly 33 USDC units, the fee calculation (33 * 3) / 100 = 0, allowing 100 dust trades that generate zero revenue for the protocol while cluttering the order book with spam.

function test_dustAttack_withPrecisionLoss() public {
// Setup: Bob creates many dust orders that pay zero fees
vm.startPrank(bob);
weth.mint(bob, 1e18);
weth.approve(address(book), 1e18);
// Create 100 dust orders, each priced at 33 units (below fee threshold)
uint256[] memory orderIds = new uint256[](100);
for (uint i = 0; i < 100; i++) {
orderIds[i] = book.createSellOrder(
address(weth),
1, // 1 wei of WETH
33, // 33 smallest units of USDC
2 days
);
}
vm.stopPrank();
// Dan buys all orders
vm.startPrank(dan);
usdc.mint(dan, 10000);
usdc.approve(address(book), 10000);
for (uint i = 0; i < 100; i++) {
book.buyOrder(orderIds[i]);
}
vm.stopPrank();
// Verify: 0 fees collected despite 3300 units of trading volume
assertEq(book.totalFees(), 0);
assertEq(usdc.balanceOf(bob), 3300); // Seller receives full amount
}

Recommended Mitigation

+ uint256 public constant MIN_ORDER_VALUE = 10e6; // $10 minimum in USDC
+ error OrderTooSmall();
function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration
) public returns (uint256) {
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
if (_amountToSell == 0) revert InvalidAmount();
if (_priceInUSDC == 0) revert InvalidPrice();
+ if (_priceInUSDC < MIN_ORDER_VALUE) revert OrderTooSmall();
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// ... rest of function
}

Note: This fixes the fee bypass issue but doesn't completely prevent order book spam. Users could still create 1 wei orders at $10+ prices. Additional measures like minimum token amounts or order book size limits may be needed for complete spam protection.

Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 2 months 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.