OrderBook

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

Timestamp-Based Expiry Logic

Author Revealed upon completion

Timestamp-Based Expiry Logic

Description

  • The contract uses block.timestamp to determine whether an order has expired. While this is a common pattern, the value of block.timestamp can be manipulated slightly by miners (usually by ±15 seconds). This small window can impact precision-critical expiry logic—such as order fills or amendments close to the deadline.

  • Affected Functions:

buyOrder(uint256)
amendSellOrder(...)
getOrderDetailsString(...)

Risk

Likelihood:

  • Miner manipulation of block.timestamp is real but constrained:

    • Miners can only set it within a small window (~±15 seconds).

    • Requires block production control — unlikely for most users, but possible for MEV-aware miners or bots.

  • If expiry windows are very tight, this issue becomes more likely to affect behavior.

Impact:

  • A malicious or incentivized miner could manipulate timestamp to:

  • Force early order expiration.

  • Delay expiry to allow unauthorized or delayed purchases.

  • Causes fragility around expiration boundaries.

  • Unreliable in environments where seconds matter (e.g., auctions, arbitrage, high-frequency DEXs).


Proof of Concept

function test_orderExpiryBoundaryManipulation() public {
// Step 1: Create a sell order with short deadline
vm.startPrank(alice);
wbtc.approve(address(book), 1e8);
uint256 orderId = book.createSellOrder(address(wbtc), 1e8, 100_000e6, 1 hours);
vm.stopPrank();
// Step 2: Warp to 1 second before expiry and buy
vm.warp(book.getOrder(orderId).deadlineTimestamp - 1);
vm.startPrank(dan);
usdc.approve(address(book), 100_000e6);
book.buyOrder(orderId); // Passes
vm.stopPrank();
// Step 3: Create another order and warp to expiry
vm.startPrank(alice);
wbtc.approve(address(book), 1e8);
uint256 orderId2 = book.createSellOrder(address(wbtc), 1e8, 100_000e6, 1 hours);
vm.stopPrank();
vm.warp(book.getOrder(orderId2).deadlineTimestamp);
vm.startPrank(dan);
usdc.approve(address(book), 100_000e6);
vm.expectRevert(OrderBook.OrderExpired.selector);
book.buyOrder(orderId2); // Reverts
vm.stopPrank();
}

Recommended Mitigation

Add a grace buffer (e.g., allow deadline + 15s margin).

Optionally use block.number with estimated time for greater consistency.

If strict expiry is acceptable, document this edge behavior clearly for users.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 2 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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