OrderBook

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

Lack of Zero-Value Transfer Checks Creates Potential DoS Vector with Non-Compliant ERC20 Tokens

Summary

The contract's token transfer functions, such as those in cancelSellOrder and buyOrder, do not validate whether the amount being transferred is non-zero before execution. While the ERC20 standard requires tokens to handle zero-value transfers, some widely used tokens do not comply and will revert the transaction. If such a non-compliant token is ever whitelisted, any operation that results in a zero-value transfer attempt will unexpectedly fail. This represents a lack of defensive programming against known token integration issues, creating a potential Denial of Service (DoS) vector.

Finding Description

Several functions within the OrderBook contract perform token transfers without first ensuring the amount is greater than zero. A key example is in cancelSellOrder:

// src/OrderBook.sol:186-188
// Mark as inactive
order.isActive = false;
// Return locked tokens to the seller
@> IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);

If an order with amountToSell = 0 were to exist in the contract state, calling cancelSellOrder on it would trigger safeTransfer(..., 0). With a non-compliant token that reverts on zero-value transfers, this call would fail.

A similar pattern exists in buyOrder where sellerReceives could theoretically be zero:

// src/OrderBook.sol:207-208
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);

While the current business logic in createSellOrder prevents the creation of zero-amount orders, relying solely on upstream logic is fragile. Best practices dictate that low-level interactions (like a token transfer) should be self-defensive. A future code change could inadvertently allow a zero-amount state, at which point this latent issue would become an active bug.

Impact

The impact is a potential Denial of Service (DoS) for certain operations, conditional on a non-compliant token being whitelisted.

  • A call to cancelSellOrder for a (malformed) zero-amount order would be permanently unrevertable, trapping the order state.

  • A buyOrder call that results in a zero share for the seller would fail, preventing the order from being filled.
    This degrades the protocol's robustness and its compatibility with the broader token ecosystem.

Likelihood

Low. This vulnerability requires the owner to whitelist a non-compliant ERC20 token and for a zero-value state to occur. However, given the existence of such tokens, this represents a tangible integration risk.

Proof of Concept

A practical PoC is difficult to construct without modifying the contract's logic, as the current validation in createSellOrder effectively prevents the necessary preconditions (a zero-amount order) from being met. Attempts to bypass this using storage manipulation (vm.store) proved complex and unreliable due to the packed nature of the Order struct.

However, a conceptual PoC remains valid:

  1. Assumption: A non-compliant RevertingToken that reverts on transfer(to, 0) is whitelisted.

  2. Assumption: A code path (perhaps through a future, unforeseen update) allows an Order with amountToSell = 0 to be created.

  3. Action: A user calls cancelSellOrder on this specific order.

  4. Result: The contract attempts safeTransfer(seller, 0). The RevertingToken reverts, causing the entire cancelSellOrder transaction to fail. The order is now permanently stuck and cannot be cancelled.

The inability to easily test this highlights the issue: the contract's safety relies on perfect upstream logic, rather than defensive, self-contained function calls.

Recommended Mitigation

Implement explicit non-zero checks before any token transfer to make the contract more robust and resilient to integration issues.

// src/OrderBook.sol:187
// Return locked tokens to the seller
- IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
+ if (order.amountToSell > 0) {
+ IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
+ }
// src/OrderBook.sol:207
- iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
+ if (sellerReceives > 0) {
+ iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
+ }
// Also recommended for the protocol fee transfer for full robustness
- iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
+ if (protocolFee > 0) {
+ iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
+ }

This approach follows the principle of defensive programming and ensures the contract will not fail due to known non-compliant token behaviors.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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