OrderBook

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

Miner-Manipulable Timestamp Can Lead to Unfavorable Trades or Denial of Service

Miner-Manipulable Timestamp Can Lead to Unfavorable Trades or Denial of Service

Description

  • In createSellOrder and amendSellOrder, the deadline of the order is calculated by adding seller-defined duration to the block.timestamp. Sellers expect their order to be active only for a specific duration after they submitted the transaction to restrict the trades considering market condition. In addition, in buyOrder there is a deadline comparison using block.timestamp for checking whether the order is expired.

  • Since miners can influence block.timestamp to a limited degree, they can manipulate the effective expiration date of a seller's order. This can extend or shorten an order's lifetime beyond the seller's intent, potentially forcing a trade under market conditions the seller sought to avoid, or causing a legitimate order to expire prematurely.

// Root cause in the codebase with @> marks to highlight the relevant section
// function createSellOrder
@> uint256 deadlineTimestamp = block.timestamp + _deadlineDuration;
// function amendSellOrder
@> uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
// function buyOrder
@> if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();

Risk

Likelihood:

  • Miner can include the seller's createSellOrder and amendSellOrder transaction in a block with a timestamp that is advantageous to the miner.

  • Miner can delay the buyer's buyOrder transaction and include it in a block where the timestamp is after the deadlineTimestamp.

Impact:

  • Forced Unfavorable Trades: The delayed seller's transaction can be outside the user's intended market conditions. This can force a user into a trade they no longer consider profitable.

  • Denial of Service: The delayed buyer's transaction can be reverted, forcing the user to lose their gas fee and preventing them from executing a trade they rightfully should have been able to make.

Proof of Concept

Below code shows both problems.

  1. Forced Unfavorable Trades: Alice is willing to sell 1e8 WBTC with 100_000e6 USDC in just 2 days consdering the market condition. However, the timestamp is mainpulated while the BTC price goes up 5% in the period. Dan takes the profit.

  2. Denial of Service: Bob creates sell order but the buying orders are rejceted due to the timestamp manipulation. Bob's order is expired so he needs to cancel it wihtout matched.

function test_audit_manipulableTimestamp() public {
// 1. unfavorable trade
// alice sells 1e8 wbtc with 100_000e6 usdc
uint256 sellingWBTCAmount = 1e8;
uint256 usdcPrice = 100_000e6;
vm.startPrank(alice);
wbtc.approve(address(book), sellingWBTCAmount);
// timestamp manipulated, btc goes up 105_000e6 usdc in 5 days
vm.warp(block.timestamp + 5 days);
uint256 aliceId = book.createSellOrder(address(wbtc), sellingWBTCAmount, usdcPrice, 2 days);
vm.stopPrank();
// dan buys 1e8 wbtc in 100_000e6 usdc
vm.startPrank(dan);
usdc.approve(address(book), usdcPrice);
book.buyOrder(aliceId);
vm.stopPrank();
// 2. DoS
// bob sells weth
uint256 sellingWETHAmount = 1e18;
usdcPrice = 2_500e6;
vm.startPrank(bob);
weth.approve(address(book), sellingWETHAmount);
uint256 bobId = book.createSellOrder(address(weth), sellingWETHAmount, usdcPrice, 2 days);
vm.stopPrank();
// timestamp manipulated to reject the buyOrder
vm.warp(block.timestamp + 5 days);
vm.startPrank(dan);
usdc.approve(address(book), usdcPrice);
vm.expectRevert(OrderBook.OrderExpired.selector);
book.buyOrder(bobId);
vm.stopPrank();
}

Recommended Mitigation

Use user defined UNIX deadline parameter instead of duration and block.timestamp.

function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
- uint256 _deadlineDuration
+ uint256 _deadlineTimestamp
) public returns (uint256) {
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
if (_amountToSell == 0) revert InvalidAmount();
if (_priceInUSDC == 0) revert InvalidPrice();
- if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
+ if (_deadlineTimestamp <= block.timestamp) revert InvalidDeadline();
- uint256 deadlineTimestamp = block.timestamp + _deadlineDuration;
+ uint256 deadlineTimestamp = _deadlineTimestamp;
uint256 orderId = _nextOrderId++;
...
}
function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
- uint256 _newDeadlineDuration
+ uint256 _newDeadlineTimestamp
) public {
...
- if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
+ if (_newDeadlineTimestamp <= order.deadlineTimestamp) revert InvalidDeadline();
- uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
+ uint256 newDeadlineTimestamp = _newDeadlineTimestamp;
...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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