OrderBook

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

Fake Transfer / Balance Mismatch in createSellOrder

Root + Impact

Fake Transfer Success via Malicious Token in createSellOrder()

Description

The function createSellOrder() calls IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell) to pull tokens from the seller into the contract. However, it does not verify that the contract actually received the tokens, and simply proceeds to create and store an active order assuming success.

This opens the door for malicious ERC-20 tokens that return true from transferFrom() without performing an actual transfer. Since the safeTransferFrom() function in SafeERC20 relies only on the return value, this trick bypasses actual token delivery.

As a result, an attacker could create an order with a malicious token, appear to transfer tokens into the contract, but leave the contract holding zero tokens. When an innocent buyer purchases the order, they’ll receive nothing, even after paying in USDC — resulting in direct financial loss for users.

Risk

Likelihood:

  • Can be exploited by anyone using a malicious or misbehaving token (especially if setAllowedSellToken() is misused).

  • Known class of bugs in real-world exploits, e.g., with fake stablecoins.

Impact:

  • Results in user fund loss, with buyers paying USDC but receiving no tokens.

  • Damages trust in the platform and breaks core trading logic.

Proof of Concept

Malicious Token Contract:

contract FakeToken is IERC20 {
function transferFrom(address, address, uint256) external override returns (bool) {
return true; // returns success, but doesn't actually transfer tokens
}
// Dummy implementations to compile
function totalSupply() public view override returns (uint256) {}
function balanceOf(address) public view override returns (uint256) {}
function allowance(address, address) public view override returns (uint256) {}
function approve(address, uint256) public override returns (bool) {}
function transfer(address, uint256) public override returns (bool) {}
}

Exploit Scenario:

  1. Attacker adds FakeToken to the allowlist using setAllowedSellToken() (assumes admin compromise or collusion).

  2. Calls createSellOrder(FakeToken, 1000, 1000, 86400) — token's transferFrom() says success, but transfers nothing.

  3. An innocent buyer calls buyOrder(orderId) and pays 1000 USDC.

  4. Buyer receives 0 FakeTokens, and seller (attacker) receives 97% of the USDC — a complete scam.

Recommended Mitigation

** Use Balance Snapshot Verification (Strongly Recommended)**

Before and after calling safeTransferFrom(), compare the contract’s token balance to ensure the expected amount was received.

uint256 beforeBalance = IERC20(_tokenToSell).balanceOf(address(this));
IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
uint256 afterBalance = IERC20(_tokenToSell).balanceOf(address(this));
if (afterBalance < beforeBalance + _amountToSell) {
revert("Token transfer did not complete");
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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