Root + Impact
Description
-
The getOrderDetailsString
function is designed to return human-readable order details including a token symbol for the asset being sold
-
The function only handles three hardcoded tokens (wETH, wBTC, wSOL) but fails to provide a symbol for other tokens that may be allowed via setAllowedSellToken()
, resulting in empty token symbols in the output string
function getOrderDetailsString(uint256 _orderId) public view returns (string memory details) {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
string memory tokenSymbol;
if (order.tokenToSell == address(iWETH)) {
tokenSymbol = "wETH";
} else if (order.tokenToSell == address(iWBTC)) {
tokenSymbol = "wBTC";
} else if (order.tokenToSell == address(iWSOL)) {
tokenSymbol = "wSOL";
}
details = string(
abi.encodePacked(
"Order ID: ",
order.id.toString(),
"\n",
"Seller: ",
Strings.toHexString(uint160(order.seller), 20),
"\n",
"Selling: ",
order.amountToSell.toString(),
" ",
tokenSymbol,
"\n",
"Asking Price: ",
order.priceInUSDC.toString(),
" USDC\n"
)
);
Risk
Likelihood:
-
The owner can add new allowed tokens using setAllowedSellToken()
at any time
-
Users can create sell orders for these newly allowed tokens through createSellOrder()
-
Any call to getOrderDetailsString()
for orders with non-standard tokens will produce incomplete information
Impact:
-
User interfaces consuming this function will display confusing order details with missing token information
-
Order details will show "Selling: 1000000 " instead of "Selling: 1000000 USDT" for example
-
This creates poor user experience and potential confusion about what asset is being traded
Proof of Concept
As you can see from the console.log() the details contains an empty strings.
[PASS] test_emptyTokenSymbolForNonStandardTokens() (gas: 1270950)
Logs:
Order Details:
Order ID: 1
Seller: 0xaf6db259343d020e372f4ab69cad536aaf79d0ac
Selling: 1000000
Asking Price: 1000000 USDC
Deadline Timestamp: 86401
Status: Active
function test_emptyTokenSymbolForNonStandardTokens() public {
MockUSDC usdt = new MockUSDC(6);
usdt.mint(alice, 1000000);
vm.prank(owner);
book.setAllowedSellToken(address(usdt), true);
vm.startPrank(alice);
usdt.approve(address(book), 1000000);
uint256 orderId = book.createSellOrder(address(usdt), 1000000, 1000000, 1 days);
vm.stopPrank();
string memory details = book.getOrderDetailsString(orderId);
console2.log("Order Details:");
console2.log(details);
assertTrue(bytes(details).length > 0);
}
Recommended Mitigation
Add a fallback mechanism to display the token address when no predefined symbol exists. This ensures users always know which token is being traded:
string memory tokenSymbol;
if (order.tokenToSell == address(iWETH)) {
tokenSymbol = "wETH";
} else if (order.tokenToSell == address(iWBTC)) {
tokenSymbol = "wBTC";
} else if (order.tokenToSell == address(iWSOL)) {
tokenSymbol = "wSOL";
+} else {
+ // Fallback to showing the token address for non-standard tokens
+ tokenSymbol = Strings.toHexString(uint160(order.tokenToSell), 20);
}