OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Malicious Seller DoS Attack Vulnerability

Summary

The OrderBook contract is vulnerable to Denial of Service (DoS) attacks where malicious sellers can create orders that appear valid but cannot be purchased. By deploying a contract that reverts when receiving USDC payments, attackers can create "zombie orders" that waste buyers' gas and pollute the order book with unbuyable orders.

Root Cause

The buyOrder function transfers USDC to the seller without validating that the seller can receive the payment:

function buyOrder(uint256 _orderId) public {
// ... validation ...
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives); // ❌ Can fail if seller reverts
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}

If order.seller is a malicious contract that reverts on token receipt, the entire transaction fails.

Internal pre-conditions

  1. Order must be created by a malicious contract address

  2. Order must pass all validation checks (valid token, amount, price, deadline)

  3. Order must appear attractive to potential buyers

  4. Malicious seller contract must revert when receiving USDC

External pre-conditions

  1. Attacker deploys a malicious contract that reverts on token receipt

  2. Attacker creates orders using the malicious contract as seller

  3. Legitimate buyers attempt to purchase the malicious orders

  4. Market conditions make the malicious orders appear attractive

Attack Path

  1. Deploy Malicious Contract: Attacker creates a contract that reverts on USDC receipt

  2. Create Attractive Orders: Use malicious contract to create orders with appealing prices

  3. Lock Tokens: Malicious contract deposits tokens to create seemingly valid orders

  4. DoS Buyers: When buyers attempt to purchase, transactions fail due to seller revert

  5. Waste Gas: Buyers lose gas fees on failed transactions

  6. Pollute Order Book: Multiple unbuyable orders clutter the trading interface

  7. Market Manipulation: Fake attractive prices may influence market perception

Impact

  1. Gas Waste: Buyers lose gas fees on failed purchase attempts

  2. Order Book Pollution: Unbuyable orders clutter the trading interface

  3. User Experience Degradation: Frustration from failed transactions

  4. Market Manipulation: Fake orders may distort price discovery

  5. Protocol Reputation: Users may lose trust in the platform

  6. Economic Attack: Systematic creation of fake orders can disrupt trading

PoC

Malicious Seller Contract:

contract MaliciousSellerContract {
// Reverts on any token receipt
receive() external payable {
revert("Malicious seller refuses payment");
}
fallback() external payable {
revert("Malicious seller refuses payment");
}
}

Attack Scenario:

  1. Deploy malicious contract

  2. Create order: 1 wETH for 800 USDC (attractive price)

  3. Buyers see attractive order and attempt to purchase

  4. All purchase attempts fail when trying to send USDC to malicious seller

  5. Order remains active but unbuyable

Test Results:

  • Order creation succeeds (malicious contract can deposit wETH)

  • Order appears valid and attractive to buyers

  • All purchase attempts fail with revert

  • Order remains active, continuing to mislead buyers

  • Buyers waste gas on failed transactions

Mitigation

Option 1: Seller Validation

function createSellOrder(...) public returns (uint256) {
// Validate seller can receive USDC
if (msg.sender.code.length > 0) {
// For contracts, perform a test transfer
try iUSDC.transfer(msg.sender, 0) {} catch {
revert("Seller cannot receive USDC");
}
}
// Continue with order creation...
}

Option 2: Pull Payment Pattern

mapping(address => uint256) public pendingPayments;
function buyOrder(uint256 _orderId) public {
// ... validation ...
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
pendingPayments[order.seller] += sellerReceives; // Store payment for seller to claim
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
function withdrawPayment() external {
uint256 payment = pendingPayments[msg.sender];
require(payment > 0, "No pending payment");
pendingPayments[msg.sender] = 0;
iUSDC.safeTransfer(msg.sender, payment);
}

Option 3: Try-Catch with Fallback

function buyOrder(uint256 _orderId) public {
// ... validation and calculations ...
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
// Try to send payment to seller
try iUSDC.transfer(order.seller, sellerReceives) {
// Payment successful
} catch {
// Payment failed, store for manual withdrawal
pendingPayments[order.seller] += sellerReceives;
emit PaymentFailed(order.seller, sellerReceives);
}
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
// ... rest of function
}

Recommended Solution:

Implement Option 2 (Pull Payment Pattern) as it completely eliminates the DoS vector while ensuring sellers can always claim their payments.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.