OrderBook

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

Core Tokens Can Be Permanently Locked Due to Withdrawal Restrictions

Core Tokens Can Be Permanently Locked Due to Withdrawal Restrictions

Description

The emergencyWithdrawERC20 function is designed to block the withdrawal of core tokens (WETH, WBTC, WSOL, USDC) to safeguard assets required for protocol operations. However, this restriction means that any core tokens mistakenly transferred directly to the contract address become permanently locked.

function emergencyWithdrawERC20(address _tokenAddress, uint256 _amount, address _to) external onlyOwner {
@> if (
@> _tokenAddress == address(iWETH) || _tokenAddress == address(iWBTC) || _tokenAddress == address(iWSOL)
@> || _tokenAddress == address(iUSDC)
@> ) {
@> revert("Cannot withdraw core order book tokens via emergency function");
@> }
if (_to == address(0)) {
revert InvalidAddress();
}
IERC20 token = IERC20(_tokenAddress);
token.safeTransfer(_to, _amount);
emit EmergencyWithdrawal(_tokenAddress, _amount, _to);
}

Risk

Likelihood:

A user mistakenly transfers core tokens (WETH, WBTC, WSOL, or USDC) directly to the contract address instead of using one of the contract's functions.

Impact:

The user's mistakenly transferred assets are permanently locked, as there is no function to withdraw them.

Proof of Concept

Below scenario shows that Alice mistakenly transferred WBTC to the OrderBook contract, the withdrawal is reverted and failed to recover.

function test_audit_lockedAsset() public {
// alice mistakenly transferred wbtc to the contract
uint256 mishandledWBTCAmount = 1e8;
vm.prank(alice);
wbtc.transfer(address(book), mishandledWBTCAmount);
assertEq(wbtc.balanceOf(address(book)), mishandledWBTCAmount);
vm.prank(owner);
vm.expectRevert("Cannot withdraw core order book tokens via emergency function");
// failed to recover wbtc
book.emergencyWithdrawERC20(address(wbtc), mishandledWBTCAmount, alice);
}

Recommended Mitigation

Implement a mechanism to track locked assets required for active orders (e.g., a lockedAmounts mapping). Modify emergencyWithdrawERC20 to allow the owner to withdraw any surplus tokens that are not locked in active orders, enabling the recovery of mistakenly sent funds.

+ mapping(address => uint256) public lockedAmounts;
+ createSellOrder: lockedAmounts[_tokenToSell] += _amountToSell;
+ buyOrder / cancelSellOrder: lockedAmounts[order.tokenToSell] -= order.amountToSell;
function emergencyWithdrawERC20(address _tokenAddress, address _to) external onlyOwner {
- if (
- _tokenAddress == address(iWETH) || _tokenAddress == address(iWBTC) || _tokenAddress == address(iWSOL)
- || _tokenAddress == address(iUSDC)
- ) {
- revert("Cannot withdraw core order book tokens via emergency function");
- }
if (_to == address(0)) {
revert InvalidAddress();
}
IERC20 token = IERC20(_tokenAddress);
token.safeTransfer(_to, _amount);
+ uint256 totalBalance = token.balanceOf(address(this));
+ uint256 locked = lockedAmounts[_tokenAddress];
+ require(totalBalance - locked > _amount);
token.safeTransfer(_to, _amount);
emit EmergencyWithdrawal(_tokenAddress, _amount, _to);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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