function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
@> if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
@> order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
}
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {OrderBook} from "../src/OrderBook.sol";
import {MockERC20} from "../test/mocks/MockERC20.sol";
contract OrderBookStateInconsistencyPoC is Test {
OrderBook public orderBook;
MockERC20 public weth;
MockERC20 public usdc;
address public seller = makeAddr("seller");
address public buyer = makeAddr("buyer");
function setUp() public {
weth = new MockERC20("Wrapped Ether", "WETH", 18);
usdc = new MockERC20("USD Coin", "USDC", 6);
orderBook = new OrderBook(
address(weth),
address(0),
address(0),
address(usdc),
address(this)
);
weth.mint(seller, 100 ether);
usdc.mint(buyer, 1000000 * 10**6);
vm.prank(seller);
weth.approve(address(orderBook), 100 ether);
vm.prank(buyer);
usdc.approve(address(orderBook), 1000000 * 10**6);
}
function testStateInconsistency_ExpiredOrderRemainsActive() public {
console2.log("=== ORDER STATE INCONSISTENCY EXPLOIT ===");
vm.prank(seller);
uint256 orderId = orderBook.createSellOrder(
address(weth),
1 ether,
2000 * 10**6,
1 hours
);
OrderBook.Order memory order = orderBook.getOrder(orderId);
console2.log("Order created - isActive:", order.isActive);
console2.log("Order deadline:", order.deadlineTimestamp);
console2.log("Current time:", block.timestamp);
vm.warp(block.timestamp + 2 hours);
console2.log("Time warped - Current time:", block.timestamp);
order = orderBook.getOrder(orderId);
console2.log("After expiration - isActive:", order.isActive);
console2.log("Order expired?", block.timestamp >= order.deadlineTimestamp);
vm.prank(buyer);
vm.expectRevert();
orderBook.buyOrder(orderId);
order = orderBook.getOrder(orderId);
console2.log("After failed purchase - isActive:", order.isActive);
console2.log("STATE INCONSISTENCY: Order shows active but is expired!");
assertTrue(order.isActive, "Order should appear active due to bug");
assertTrue(block.timestamp >= order.deadlineTimestamp, "Order should be expired");
}
}
forge test --match-test testStateInconsistency_ExpiredOrderRemainsActive -vv
[⠑] Compiling...
[⠢] Compiling 1 files with Solc 0.8.29
[⠰] Solc 0.8.29 finished in 1.45s
Compiler run successful!
Ran 1 test for test/OrderBookStateInconsistencyPoC.t.sol:OrderBookStateInconsistencyPoC
[PASS] testStateInconsistency_ExpiredOrderRemainsActive() (gas: 245680)
Logs:
=== ORDER STATE INCONSISTENCY EXPLOIT ===
Order created - isActive: true
Order deadline: 3600
Current time: 1
Time warped - Current time: 7201
After expiration - isActive: true
Order expired? true
After failed purchase - isActive: true
STATE INCONSISTENCY: Order shows active but is expired!
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.28ms (3.58ms CPU time)
Ran 1 test suite in 10.15ms (4.28ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Update the order state to inactive before reverting when an order is expired, ensuring consistent state management throughout the contract.
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
- if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
+ if (block.timestamp >= order.deadlineTimestamp) {
+ order.isActive = false;
+ emit OrderExpired(_orderId);
+ revert OrderExpired();
+ }
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
// ... rest of function
}