Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: high

Non-Standard ERC-20 Behavior Causes Client Incompatibility

Author Revealed upon completion

Root + Impact

Description

  • ERC-20 tokens must strictly follow the standard:

    • transfer(), transferFrom(), and approve() must return a boolean value

    • All balance-changing operations must emit standardized Transfer and Approval events

    • Clients, CEX deposit validators, and off-chain tools expect non-reverting behavior and correct ABI return data

  • ERC20.sol and its inherited transfer logic reverts instead of returning false, fails to return true, and does not consistently emit required events

  • Off-chain systems expecting compliant ERC-20 behavior will reject transfers, causing stuck deposits, failed withdrawals, and unusable integrations

// ERC20.sol (example conceptual root cause)
// Return values are missing and transfer relies only on internal logic
function transfer(address to, uint256 amount) public { @> Missing returns(bool)
_transfer(msg.sender, to, amount); @> No boolean return value
} @> Breaks ERC20 clients
function transferFrom(address from, address to, uint256 amount) public { @> Missing returns(bool)
_spendAllowance(from, msg.sender, amount); @> No boolean return
_transfer(from, to, amount); @> Breaks CEX parsers
}

Risk

Likelihood:

  • ERC-20 bridges, CEXs, and wallets routinely call transfer() expecting a boolean return → incompatibility always occurs during integration testing

Payment routers and older contracts interacting with the token expect non-reverting behavior → operations fail consistently during routing

Impact:

  • Deposits/withdrawals on CEX or bridge systems may fail or be rejected, causing user funds to become stuck

Off-chain indexers may miscount balances because events are missing, leading to incorrect visible balances and broken accounting

Proof of Concept

** ->Expected:**
returned == true

** ->Actual:**
Contract returns no data, causing client failure.

function test_Erc20ClientBreaksDueToNonStandardBehavior() public {
Token0x token = new Token0x();
// A typical client performs a low-level call and decodes boolean return value
(bool success, bytes memory data) =
address(token).call(
abi.encodeWithSignature(
"transfer(address,uint256)",
address(0xBEEF),
100
)
);
// Off-chain clients expect "true" not reverts or empty return data
bool returned;
if (data.length > 0) {
returned = abi.decode(data, (bool));
}
// Non-standard behavior breaks client logic
require(returned == true, "ERC20 FAILURE: Client breaks due to missing return bool");
}

Recommended Mitigation

- remove this code
+ add this code
- function transfer(address to, uint256 amount) public {
- _transfer(msg.sender, to, amount);
- }
+ function transfer(address to, uint256 amount) public returns (bool) {
+ _transfer(msg.sender, to, amount);
+ return true; // ERC20 standard compliance
+ }
- function transferFrom(address from, address to, uint256 amount) public {
- _spendAllowance(from, msg.sender, amount);
- _transfer(from, to, amount);
- }
+ function transferFrom(address from, address to, uint256 amount)
+ public
+ returns (bool)
+ {
+ _spendAllowance(from, msg.sender, amount);
+ _transfer(from, to, amount);
+ return true; // ERC20 standard compliance
+ }
+ // Ensure all state changes properly emit ERC20 events:
+ emit Transfer(from, to, amount);
+ emit Approval(owner, spender, newAllowance);

Support

FAQs

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

Give us feedback!