OrderBook

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

Owner Can Disable Core Protocol Tokens, Leading to a Denial of Service for Key Markets

Finding Title

Owner Can Disable Core Protocol Tokens, Leading to a Denial of Service for Key Markets

Summary

The setAllowedSellToken() function grants the owner excessive privileges that conflict with the protocol's core design. It allows the owner to disable core protocol tokens (WETH, WBTC, WSOL) that are foundational to the platform's operation. While administrative privileges are expected, these privileges should not extend to undermining the guaranteed, foundational markets of the protocol. This capability creates a direct vector for denying service to key markets and breaks the implicit promise made to users upon the contract's initialization.

Finding Description

The OrderBook is explicitly initialized with addresses for core tokens iWETH, iWBTC, and iWSOL in its constructor. This action establishes them not merely as tradable assets, but as foundational pillars of the protocol, creating an implicit guarantee of their availability to users.

However, the administrative function setAllowedSellToken() fails to protect these core assets, only safeguarding iUSDC.

The Core of the Issue: Expected vs. Excessive Privilege
While it is standard for an owner to manage a whitelist of optional, community-voted tokens, the issue here is that the owner's privilege extends to the protocol's foundational assets. This is not a standard administrative power; it is the power to unilaterally alter the core identity and functionality of the protocol post-deployment.

// src/OrderBook.sol:271-274
function setAllowedSellToken(address _token, bool _isAllowed) external onlyOwner {
if (_token == address(0) || _token == address(iUSDC)) revert InvalidToken(); // Only protects USDC
allowedSellToken[_token] = _isAllowed;
// ...
}

As a result, the owner can at any time call setAllowedSellToken(address(iWETH), false), breaking the implicit guarantee and freezing all new market activity for WETH.

Impact

This excessive privilege creates a powerful and centralized point of failure.

  • Denial of Service (DoS) on Core Functionality: The owner can unilaterally halt new orders for foundational assets, effectively shutting down a key market guaranteed at launch.

  • Broken Protocol Promise: Allowing core, constructor-defined tokens to be disabled breaks the implicit promise of their availability, making the protocol's behavior unpredictable and untrustworthy.

  • Excessive Centralization Risk: This goes beyond simple maintenance, granting the owner control over the protocol's fundamental features, which is a significant centralization risk.

Likelihood

Medium. The action can be performed at any time by the owner with a single transaction. It does not require special conditions and could even be triggered by mistake.

Proof of Concept

The following test proves that the owner can disable WETH, a core protocol token, preventing new orders from being created.

Test File: test/CoreTokenDisabling.t.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {OrderBook} from "../../src/OrderBook.sol";
import {MockUSDC} from "../mocks/MockUSDC.sol";
import {MockWETH} from "../mocks/MockWETH.sol";
contract CoreTokenDisablingTest is Test {
OrderBook book;
MockWETH weth;
MockUSDC usdc;
address owner = makeAddr("owner");
address seller1 = makeAddr("seller1");
function setUp() public {
weth = new MockWETH(18);
usdc = new MockUSDC(6);
vm.prank(owner);
book = new OrderBook(address(weth), address(weth), address(weth), address(usdc), owner);
weth.mint(seller1, 10e18);
}
/// @notice This PoC proves that a core protocol token (WETH) can be disabled by the owner,
/// leading to a Denial of Service for creating new orders in that market.
function test_PoC_CoreTokenCanBeDisabled() public {
// Verify that WETH is initially allowed (set in constructor)
assertTrue(book.allowedSellToken(address(weth)), "WETH should initially be allowed");
// --- Vulnerability: Owner disables WETH ---
vm.prank(owner);
book.setAllowedSellToken(address(weth), false);
vm.stopPrank();
console2.log("VULNERABILITY: WETH, a core token, was successfully disabled!");
assertFalse(book.allowedSellToken(address(weth)), "WETH should now be disabled");
// --- Impact: New WETH orders are blocked ---
vm.startPrank(seller1);
weth.approve(address(book), 1e18);
vm.expectRevert(bytes("InvalidToken()"));
book.createSellOrder(address(weth), 1e18, 1000e6, 1 days);
vm.stopPrank();
console2.log("DoS Confirmed: New WETH orders are blocked, halting the market.");
}
}

Successful Test Output:

[PASS] test_PoC_CoreTokenCanBeDisabled()
Logs:
VULNERABILITY: WETH, a core token, was successfully disabled!
DoS Confirmed: New WETH orders are blocked, halting the market.

Recommended Mitigation

Protect all core tokens from being disabled. The setAllowedSellToken function should be restricted to managing non-core, community-added tokens only.

// src/OrderBook.sol
function setAllowedSellToken(address _token, bool _isAllowed) external onlyOwner {
- if (_token == address(0) || _token == address(iUSDC)) revert InvalidToken();
+ if (_token == address(0) ||
+ _token == address(iUSDC) ||
+ _token == address(iWETH) ||
+ _token == address(iWBTC) ||
+ _token == address(iWSOL)) {
+ revert InvalidToken(); // Cannot alter the status of core protocol tokens
+ }
allowedSellToken[_token] = _isAllowed;
emit TokenAllowed(_token, _isAllowed);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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