OrderBook

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

Indefinite Order Extension Breaks Core Protocol Invariant

Indefinite Order Extension Breaks Core Protocol Invariant

Description

The protocol is designed to enforce a maximum lifetime for all orders, as specified by the MAX_DEADLINE_DURATION constant, to prevent stale listings. This is a key feature advertised in the project's README.md.

However, the amendSellOrder function calculates the new deadline based on the current block.timestamp instead of the order's original creation time. This flaw allows sellers to repeatedly reset the deadline countdown, enabling them to keep their orders active indefinitely and breaking the core protocol invariant.

// src/OrderBook.sol:192-202
function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) public {
// ...
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
@> uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
IERC20 token = IERC20(order.tokenToSell);
//...
}

Risk

Likelihood: High

  • When a seller of an active order decides to amend it, they can perpetually extend its lifetime before it expires.

  • This action does not require any special permissions beyond being the owner of the order.

Impact: Medium

  • The protocol's core, documented invariant of having a maximum order lifetime is broken, making the system's behavior unpredictable.

  • User trust is eroded as the "Deadline enforcement" feature, a key promise made in the README.md, is rendered ineffective.

Proof of Concept

The following test proves that a seller can extend an order's lifetime repeatedly, causing its total lifetime to exceed the MAX_DEADLINE_DURATION defined by the protocol.

// test/PoC/DeadlineExtension.t.sol
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {OrderBook} from "../../src/OrderBook.sol";
import {MockUSDC} from "../mocks/MockUSDC.sol";
import {MockWETH} from "../mocks/MockWETH.sol";
contract DeadlineExtensionTest is Test {
OrderBook book;
MockWETH weth;
address owner = makeAddr("owner");
address seller = makeAddr("seller"); // A regular user
uint256 maxDeadlineDuration;
function setUp() public {
weth = new MockWETH(18);
MockUSDC usdc = new MockUSDC(6);
vm.prank(owner);
book = new OrderBook(address(weth), address(weth), address(weth), address(usdc), owner);
weth.mint(seller, 1e18);
maxDeadlineDuration = book.MAX_DEADLINE_DURATION();
}
/// @notice This test proves that a seller can indefinitely extend an order's
/// deadline by repeatedly calling amendSellOrder(), breaking the core
/// "Deadline enforcement" invariant.
function test_PoC_IndefiniteDeadlineExtension() public {
// --- Setup Phase ---
uint256 initialAmount = 1e18;
uint256 initialPrice = 1000e6;
uint256 initialDuration = 1 days;
vm.startPrank(seller);
weth.approve(address(book), initialAmount);
uint256 orderId = book.createSellOrder(address(weth), initialAmount, initialPrice, initialDuration);
vm.stopPrank();
uint256 creationTime = block.timestamp;
// --- Execution Phase 1: First Extension ---
vm.warp(block.timestamp + initialDuration - 1 hours);
vm.prank(seller);
book.amendSellOrder(orderId, initialAmount, initialPrice, maxDeadlineDuration);
// --- Execution Phase 2: Second Extension ---
vm.warp(block.timestamp + maxDeadlineDuration - 1 hours);
vm.prank(seller);
book.amendSellOrder(orderId, initialAmount, initialPrice, maxDeadlineDuration);
// --- Assertion Phase ---
OrderBook.Order memory finalOrder = book.getOrder(orderId);
uint256 totalLifetime = finalOrder.deadlineTimestamp - creationTime;
console2.log("Protocol's Maximum Allowed Lifetime (Invariant):", maxDeadlineDuration);
console2.log("Actual Order Lifetime After Two Extensions:", totalLifetime);
assertTrue(
totalLifetime > maxDeadlineDuration,
"INVARIANT BROKEN: Order lifetime exceeded the protocol's stated maximum."
);
}
}

Execution Command & Result:

$ forge test --match-path test/PoC/DeadlineExtension.t.sol
[PASS] test_PoC_IndefiniteDeadlineExtension() (gas: 267980)
Logs:
Protocol's Maximum Allowed Lifetime (Invariant): 259200
Actual Order Lifetime After Two Extensions: 597600

Recommended Mitigation

To fix this flaw, the order's creationTimestamp must be stored and checked against MAX_DEADLINE_DURATION during amendments.

1. Modify the Order struct to include creationTimestamp:

// src/OrderBook.sol:34-41
struct Order {
uint256 id;
address seller;
address tokenToSell;
uint256 amountToSell;
uint256 priceInUSDC;
- uint256 deadlineTimestamp;
+ uint256 creationTimestamp;
+ uint256 deadlineTimestamp;
bool isActive;
}

2. Store the creationTimestamp when an order is created:

// src/OrderBook.sol:154-162
orders[orderId] = Order({
id: orderId,
seller: msg.sender,
tokenToSell: _tokenToSell,
amountToSell: _amountToSell,
priceInUSDC: _priceInUSDC,
- deadlineTimestamp: deadlineTimestamp,
+ creationTimestamp: block.timestamp,
+ deadlineTimestamp: deadlineTimestamp,
isActive: true
});

3. Add a validation check in amendSellOrder:

// src/OrderBook.sol:198-202
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
+ // Enforce that the new deadline does not exceed the maximum lifetime from the original creation time.
+ if (newDeadlineTimestamp > order.creationTimestamp + MAX_DEADLINE_DURATION) {
+ revert InvalidDeadline();
+ }
IERC20 token = IERC20(order.tokenToSell);
Updates

Lead Judging Commences

yeahchibyke Lead Judge
4 months ago
yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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