OrderBook

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

Lock In Period For Buy orders

Root + Impact

Description

  • Under normal behavior, a buyer interacts with an active sell order by calling buyOrder(), expecting that the terms (price, amount, availability) remain constant during transaction processing.

    However, due to the public and delayed nature of blockchain transactions, sellers can monitor the mempool and front-run buyers by cancelling or amending their order before the buyer’s transaction is mined — effectively voiding or altering the trade.

    This leads to:

    • Failed buyOrder() transactions (wasting gas)

    • Broken trust in order execution

    • Potential manipulation of pricing and availability

function cancelSellOrder(uint256 _orderId) public nonReentrant {
...
@> // No check to prevent seller from cancelling while buyer tx is pending
order.isActive = false;
...
}
function amendSellOrder(...) public nonReentrant {
...
@> // No restriction on modifying order terms while buyer transaction may be in flight
order.amountToSell = _newAmountToSell;
...
}

Risk

Likelihood:

Likelihood: Medium

  • This issue arises specifically during the window when a buyer's transaction is pending in the mempool, especially on congested chains like Ethereum Mainnet or in low-GWEI environments.

  • Sophisticated or malicious sellers (or bots) can use tools to monitor pending transactions and react accordingly.

  • In protocols without a relayer or off-chain matching, this is more likely.

Impact:

Impact: High

  • Buyers waste gas on failed buyOrder() calls.

  • Seller can manipulate the trade price or availability last-minute.

  • Trust in the trading experience is broken, harming protocol reputation and UX.

  • Malicious actors can automate this behavior to grief or manipulate markets.

Proof of Concept

This can also occur with amendSellOrder() if the seller raises the price just before the buy.

// Buyer sees Order #1 available
lockOrderForBuy(1); // Optional: called by relayer
// Buyer broadcasts buyOrder(1) — goes to mempool
// Seller monitors mempool, sees buyer's tx, and cancels the order:
cancelSellOrder(1);
// Buyer’s buyOrder() fails with OrderNotActive()

Recommended Mitigation

This the the changes for the whole codebase, refer this link
https://github.com/CodeHawks-Contests/2025-07-orderbook/commit/78f5ea5709d7dd243cb7de292405cca46d627743

Introduce a time-based buy lock in the Order struct, and enforce it across key order-modifying functions to prevent sellers from front-running buyers.


🧱 1. Update Order Struct

Add a buyLockExpiresAt timestamp to manage temporary locks during a buy attempt:

struct Order { ... uint256 buyLockExpiresAt; // blocks seller from cancel/amend during active buy lock }


🛡️ 2. Add lockOrderForBuy() Function

This is used by the frontend, bot, or relayer before calling buyOrder():

function lockOrderForBuy(uint256 _orderId) external {
Order storage order = orders[_orderId];
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
if (block.timestamp < order.buyLockExpiresAt) revert AlreadyLockedForBuy();
order.buyLockExpiresAt = block.timestamp + BUY_LOCKIN_PERIOD;
emit OrderLocked(_orderId, msg.sender, order.buyLockExpiresAt);
}
  • BUY_LOCKIN_PERIOD can be defined as a constant, e.g., 10 minutes.


❌ 3. Update cancelSellOrder() to Prevent Cancellation During Lock

function cancelSellOrder(uint256 _orderId) public nonReentrant {
Order storage order = orders[_orderId];
+ if (block.timestamp < order.buyLockExpiresAt) {
+ revert OrderLockedForBuy();
+ }
...
}

✏️ 4. Update amendSellOrder() to Prevent Changes During Lock

function amendSellOrder(...) public nonReentrant {
Order storage order = orders[_orderId];
+ if (block.timestamp < order.buyLockExpiresAt) {
+ revert OrderLockedForBuy();
+ }
...
}

✅ 5. Clear Lock After Successful buyOrder()

To prevent stale locks:

function buyOrder(uint256 _orderId) public nonReentrant {
Order storage order = orders[_orderId];
...
+ order.buyLockExpiresAt = 0; // Clear buy lock after successful trade
}

✅ Result

This ensures:

  • Buyers can reserve a fair 10-minute window to submit their transaction

  • Sellers cannot grief, amend, or cancel an order mid-buy

  • The lock naturally expires if the buyer doesn’t follow through

  • Simplicity: only one variable needed (buyLockExpiresAt)

Updates

Lead Judging Commences

yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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