Finding Title
Inconsistent Allowance Check in amendSellOrder
Allows Modification of Orders for Disabled Tokens
Summary
There is a logic inconsistency between how new orders and existing orders are handled. The createSellOrder
function correctly checks if a token is allowed for trading via the allowedSellToken
mapping. However, the amendSellOrder
function completely omits this check. This allows a user to successfully amend an order (e.g., change its price or amount) even after the owner has disabled the token for trading, leading to inconsistent and unexpected behavior.
Finding Description
The protocol's control flow for managing token allowances is incomplete.
-
Correct Behavior in createSellOrder
:
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
-
Missing Check in amendSellOrder
: The amendSellOrder
function checks for many conditions (e.g., ownership, order status, deadline) but fails to check if the token is still allowed.
function amendSellOrder(...) public {
}
Scenario: A user creates an order for token XYZ
. The owner then disables XYZ
. The user can still call amendSellOrder
on their XYZ
order, while new XYZ
orders are blocked.
Impact
This inconsistency weakens the administrative control provided by setAllowedSellToken
and creates confusing behavior.
-
Inconsistent State Management: The protocol behaves differently for creating versus amending orders. If a token is disabled, all state-changing operations related to it should logically be disabled.
-
Bypassed Controls: Users can continue to modify orders for tokens that the owner has explicitly tried to freeze from new activity.
Likelihood
High. This inconsistent behavior will occur every time an order is amended for a token that has been subsequently disabled.
Proof of Concept
The following test proves that amendSellOrder
can be successfully called on an order whose token has been disabled.
Test File: test/PoC/InconsistentAllowance.t.sol
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 InconsistentAllowanceTest is Test {
OrderBook book;
MockWETH weth;
MockUSDC usdc;
MockWETH customToken;
address owner = makeAddr("owner");
address seller1 = makeAddr("seller1");
function setUp() public {
weth = new MockWETH(18);
usdc = new MockUSDC(6);
customToken = new MockWETH(18);
vm.prank(owner);
book = new OrderBook(address(weth), address(weth), address(weth), address(usdc), owner);
customToken.mint(seller1, 10e18);
}
function test_PoC_AmendSucceedsForDisabledToken() public {
vm.prank(owner);
book.setAllowedSellToken(address(customToken), true);
vm.stopPrank();
vm.startPrank(seller1);
customToken.approve(address(book), 3e18);
uint256 orderId = book.createSellOrder(address(customToken), 3e18, 1000e6, 1 days);
vm.stopPrank();
vm.prank(owner);
book.setAllowedSellToken(address(customToken), false);
vm.stopPrank();
console2.log("Token has been disabled by the owner.");
assertFalse(book.allowedSellToken(address(customToken)));
vm.startPrank(seller1);
book.amendSellOrder(orderId, 2e18, 900e6, 1 days);
vm.stopPrank();
OrderBook.Order memory orderAfter = book.getOrder(orderId);
assertEq(orderAfter.amountToSell, 2e18, "Amount should have been updated");
assertEq(orderAfter.priceInUSDC, 900e6, "Price should have been updated");
console2.log("VULNERABILITY CONFIRMED: Order was successfully amended even though its token is disabled.");
}
}
Successful Test Output:
[PASS] test_PoC_AmendSucceedsForDisabledToken()
Logs:
Token has been disabled by the owner.
VULNERABILITY CONFIRMED: Order was successfully amended even though its token is disabled.
Recommended Mitigation
Add the missing allowedSellToken
check to the amendSellOrder
function to ensure consistent behavior across the protocol.
// src/OrderBook.sol
function amendSellOrder(
//...
) public {
Order storage order = orders[_orderId];
// ... (existing checks) ...
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
+ // NEW: Ensure token is still allowed for trading before amending
+ if (!allowedSellToken[order.tokenToSell]) revert InvalidToken();
if (_newAmountToSell == 0) revert InvalidAmount();
// ...
}