OrderBook

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

emergencyWithdrawERC20 doesn't handle new tokens added

Root + Impact

Description

  • The emergencyWithdrawERC20 function is designed to prevent withdrawal of core order book tokens by checking against a hardcoded list of tokens (wETH, wBTC, wSOL, USDC) to protect funds that are part of active orders.

  • The function fails to account for newly added tokens that are allowed as sell tokens in the order book system, creating a vulnerability where these tokens can be withdrawn via the emergency function even when they are locked in active orders.

if (
@> _tokenAddress == address(iWETH) ||
@> _tokenAddress == address(iWBTC) ||
@> _tokenAddress == address(iWSOL) ||
@> _tokenAddress == address(iUSDC)
) {
revert("Cannot withdraw core order book tokens via emergency function");
}

Risk

Likelihood:

  • This vulnerability will occur whenever new tokens are added to the order book system via setAllowedSellToken and users create sell orders with these tokens

  • The owner can exploit this immediately after new tokens are added and users deposit funds through sell orders

Impact:

  • Loss of funds for users with active sell orders using newly added tokens

  • Broken order fulfillment mechanism preventing buyers from completing purchases

  • Complete compromise of the order book's integrity and user trust

  • Potential rug pull scenario enabling systematic extraction of user funds

Proof of Concept

This test demonstrates how the owner can extract user funds from newly added tokens that are locked in active orders:

function test_emergencyWithdrawERC20_canBreakProtocolWithNewTokenAdded() external {
// owner allows wMATIC as a sell token
vm.prank(owner);
book.setAllowedSellToken(address(wmatic), true);
// user makes a sell order with wMATIC
vm.startPrank(seller_wmatic);
wmatic.approve(address(book), 1e18);
book.createSellOrder(address(wmatic), 1e18, 20_000e6, 2 days);
vm.stopPrank();
// owner steals the locked wMATIC
vm.prank(owner);
book.emergencyWithdrawERC20(address(wmatic), 1e18, owner);
assertEq(wmatic.balanceOf(address(book)), 0);
assertEq(wmatic.balanceOf(owner), 1e18);
// buyer can't buy the order
vm.startPrank(buyer);
usdc.approve(address(book), 20_000e6);
vm.expectPartialRevert(IERC20Errors.ERC20InsufficientBalance.selector);
book.buyOrder(1);
vm.stopPrank();
}

Recommended Mitigation

Replace the hardcoded token check with a dynamic check using the existing allowedSellToken mapping to protect all tokens that can be part of active orders:

- if (
- _tokenAddress == address(iWETH) ||
- _tokenAddress == address(iWBTC) ||
- _tokenAddress == address(iWSOL) ||
- _tokenAddress == address(iUSDC)
- ) {
- revert("Cannot withdraw core order book tokens via emergency function");
- }
+ if (allowedSellToken[_tokenAddress] || _tokenAddress == address(iUSDC)) {
+ revert("Cannot withdraw core order book tokens via emergency function");
+ }
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

ehl Submitter
about 1 month ago
yeahchibyke Lead Judge
about 1 month ago
ehl Submitter
about 1 month ago
ehl Submitter
about 1 month ago
yeahchibyke Lead Judge
about 1 month ago
ehl Submitter
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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