OrderBook

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

USDC Transfer Front-Running Risk

Author Revealed upon completion

Root + Impact

Description

  • A user could frontrun an incoming buyOrder() transaction by calling cancelSellOrder(), causing the buyer’s transaction to revert or fail unexpectedly.

  • The contract lacks protection against front-running of order cancellation.

  • If order.isActive becomes false between mempool submission and execution, it can lead to failed trades.

if (!order.isActive) revert OrderNotActive();

Risk

>When a buyer sends a buyOrder(orderId) transaction, it enters the public mempool.

>A malicious actor—often the seller—can detect this pending transaction and immediately send a cancelSellOrder(orderId) before the buyer’s tx gets mined.

>Since both functions are publicly accessible and there is no mutex, nonce lock, or mempool shielding, this race condition is consistently exploitable, especially for high-value trades.


Proof of Concept

// Buyer signs and sends this tx
orderBook.buyOrder(orderId); // goes to mempool
// Seller frontruns with:
orderBook.cancelSellOrder(orderId); // gets mined first
// BuyOrder now reverts:
OrderNotActive();
Explanation:
PoC Explanation: This vulnerability occurs because all Ethereum transactions are visible in the public mempool before they're mined.
When a buyer submits buyOrder(orderId), any malicious actor—including the seller—can observe it and immediately submit a cancelSellOrder(orderId) targeting the same orderId.
Since cancelSellOrder() is allowed for any active order owned by the caller, it will execute before the buy transaction if timed right, flipping isActive to false.
This causes the buyer’s tx to revert due to the require(order.isActive) check at the start of buyOrder().
As a result, the buyer suffers gas loss and failed execution, compromising UX and reliability.

Recommended Mitigation

1)
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract OrderBook is Ownable, ReentrancyGuard {
...
}
2)Apply the nonReentrant modifier to buyOrder() and cancelSellOrder()
function buyOrder(uint256 _orderId) public nonReentrant {
...
}
function cancelSellOrder(uint256 _orderId) public nonReentrant {
...
}
Mitigation Explanation:
To prevent front-running race conditions, the contract should enforce atomicity and internal state consistency using
the nonReentrant modifier.
This protects against nested execution that can arise during the critical window between order.isActive checks and external calls
. Additionally, updating order.isActive = false before initiating any safeTransferFrom or safeTransfer ensures consistent behavior even in concurrent transaction environments.
These changes fortify the contract against mempool-based timing attacks and ensure users experience predictable transaction behavior.
Updates

Lead Judging Commences

yeahchibyke Lead Judge
4 days ago
yeahchibyke Lead Judge about 9 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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