Root + Impact
struct Order {
address seller;
address token;
uint256 tokenAmount;
uint256 usdcAmount;
uint256 deadline;
bool filled;
}
mapping(uint256 => Order) public orders;
uint256 public nextOrderId;
function createOrder(address token, uint256 tokenAmount, uint256 usdcAmount, uint256 deadline)
function fulfillOrder(uint256 orderId)
function cancelOrder(uint256 orderId)
Vulnerability Context:
The fulfillOrder function (lines XX-XX) lacked critical deadline validation, allowing order fulfillment after expiration. This violated the core protocol invariant that orders should only be executable before their deadline.
Fix Implementation:
function fulfillOrder(uint256 orderId) external {
}
function fulfillOrder(uint256 orderId) external {
require(block.timestamp <= order.deadline, "Order expired");
}
Related Code Sections:
Order creation sets deadlines:
function createOrder(... uint256 deadline) {
require(deadline > block.timestamp, "Invalid deadline");
}
Cancellation handles expiration:
function cancelOrder(uint256 orderId) {
require(block.timestamp > orders[orderId].deadline || ..., "Active order");
}
Description
function fulfillOrder(uint256 orderId) external {
Order storage order = orders[orderId];
require(!order.filled, "Order already filled");
require(order.seller != address(0), "Invalid order");
USDC.safeTransferFrom(msg.sender, order.seller, order.usdcAmount);
IERC20(order.token).safeTransfer(msg.sender, order.tokenAmount);
order.filled = true;
}
require(block.timestamp <= order.deadline, "Order expired");
Risk
Likelihood:
Impact:
Proof of Concept
function test_expiredOrderExploit() public {
uint256 orderId = orderBook.createOrder(
address(wETH),
1 ether,
2000e6,
block.timestamp + 1 days
);
vm.warp(block.timestamp + 2 days);
address attacker = makeAddr("arbitrageur");
deal(address(USDC), attacker, 2000e6);
vm.prank(attacker);
orderBook.fulfillOrder(orderId);
assertEq(wETH.balanceOf(attacker), 1 ether);
assertEq(USDC.balanceOf(seller), 2000e6);
}
function fulfillOrder(uint256 orderId) external {
Order storage order = orders[orderId];
require(!order.filled, "Filled");
require(order.seller != address(0), "Invalid order");
USDC.safeTransferFrom(msg.sender, order.seller, order.usdcAmount);
IERC20(order.token).safeTransfer(msg.sender, order.tokenAmount);
order.filled = true;
}
Recommended Mitigation
@@ -XXX, +XXX, @@ @@
function fulfillOrder(uint256 orderId) external {
Order storage order = orders[orderId];
require(!order.filled, "Order already filled");
require(order.seller != address(0), "Invalid order");
+ require(block.timestamp <= order.deadline, "Order expired"); // Critical security fix
USDC.safeTransferFrom(msg.sender, order.seller, order.usdcAmount);
IERC20(order.token).safeTransfer(msg.sender, order.tokenAmount);