Weird Token List

ERC20 tokens are widely used in the Ethereum ecosystem, but not all tokens adhere strictly to the ERC20 standard. In this section, we will cover various unusual and unexpected behaviors of ERC20 tokens, illustrating real-world cases where these behaviors have been exploited. The goal is to help developers and auditors understand potential pitfalls when integrating or interacting with ERC20 tokens in smart contracts.


Reentrant Calls

Some tokens, such as ERC777 tokens, allow reentrant calls during transfers. This means that during a token transfer, an external call can be made back into the smart contract before the original transfer completes, which can lead to vulnerabilities. This behavior has been exploited, for instance, in the imBTC Uniswap pool, where reentrant calls led to drained funds.

Example: Reentrant.sol


Missing Return Values

Certain tokens do not return a boolean (bool) value from ERC20 methods as expected. Tokens like USDT, BNB, and OMG are examples of this. Some tokens, such as BNB, may return a boolean for some methods but fail to do so for others, which caused stuck tokens in Uniswap v1. In extreme cases, such as Tether Gold, tokens may declare a bool return type but always return false even when transfers are successful.

To handle this issue, a robust transfer abstraction (example provided) can help, though some tokens are impossible to handle consistently due to their broken implementations.

  • Example Tokens:

    • MissingReturns.sol: Does not return a boolean for any ERC20 operation.

    • ReturnsFalse.sol: Always returns false for all ERC20 operations.


Fee on Transfer

Some tokens charge a fee on transfers, such as STA and PAXG. Even tokens that don't currently charge fees, like USDT or USDC, may introduce them in the future. These transfer fees can disrupt systems, such as when $500k was drained from Balancer pools due to the STA transfer fee.

Example: TransferFee.sol


Balance Modifications Outside of Transfers (Rebasing / Airdrops)

Certain tokens modify user balances without initiating a transfer. These include rebasing tokens like Ampleforth and airdrop models like Compound’s governance tokens. Such arbitrary balance changes can break systems that cache balances, like Uniswap V2 or Balancer. To prevent this, some systems ensure their pools are updated atomically during rebasing.

Example: A rebasing token implementation.


Upgradable Tokens

Tokens like USDC and USDT can be upgraded, allowing the contract owner to change the token's behavior at any time. This poses a risk to smart contracts that depend on specific behaviors from tokens. To mitigate this, developers can introduce logic that freezes interactions with an upgradable token if an upgrade is detected, as MakerDAO did with the TUSD adapter.

Example: Upgradable.sol


Flash Mintable Tokens

Tokens like DAI support "flash minting," allowing tokens to be minted for the duration of a single transaction, provided they are returned by the end of the transaction. This is similar to flash loans but without requiring pre-existing tokens. Such tokens could technically mint up to max uint256 tokens within a single transaction.

Documentation for the MakerDAO flash mint module can be found here.


Tokens with Blocklists

Tokens like USDC and USDT implement admin-controlled blocklists that prevent transfers to or from certain addresses. This could be used to trap funds, for example, by adding a contract’s address to the blocklist. This risk may arise due to regulatory action or even malicious intent.

Example: BlockList.sol


Pausable Tokens

Tokens such as BNB and ZIL allow an admin to pause the token, preventing all transfers. This can expose users to risk if the admin is compromised or acts maliciously.

Example: Pausable.sol


Approval Race Protections

Some tokens, like USDT and KNC, do not allow increasing an approved amount (M > 0) if an existing amount (N > 0) is already approved. This is a protection against an ERC20 attack vector described here.

Example: Approval.sol


Revert on Approval to Zero Address

Tokens like those in the OpenZeppelin framework will revert if an attempt is made to approve the zero address to spend tokens. Developers may need to implement special logic to handle this behavior.

Example: ApprovalToZeroAddress.sol


Revert on Zero Value Approvals

Some tokens, such as BNB, revert when approving a zero-value amount. Integrators must account for this by adding special cases in their contract logic.

Example: ApprovalWithZeroValue.sol


Revert on Zero Value Transfers

Some tokens revert when a zero-value transfer is initiated.

Example: RevertZero.sol


Multiple Token Addresses

Some proxied tokens, particularly those with multiple addresses, pose unique risks. For example, a rescueFunds function could allow a contract owner to steal all tokens in a pool if the logic assumes a single token address per contract.

Example: Proxied.sol


Low Decimals

Tokens with fewer than 18 decimals, like USDC (which has 6), may introduce precision loss in calculations. Even more extreme, tokens like Gemini USD only have 2 decimals.

Example: LowDecimals.sol


High Decimals

Some tokens, such as YAM-V2, have more than 18 decimals (YAM-V2 has 24). This can cause overflows and introduce risks of unexpected reverts in smart contracts.

Example: HighDecimals.sol


TransferFrom with src == msg.sender

Some tokens, like DSToken, do not attempt to decrease the caller's allowance if the caller is also the sender, making transferFrom behave like transfer. In contrast, other tokens, like those using OpenZeppelin, always decrease the allowance.

Examples:

  • ERC20.sol: Does not decrease allowance.

  • TransferFromSelf.sol: Always decreases allowance.


Non-String Metadata

Tokens like MKR encode their metadata (e.g., name, symbol) as bytes32 rather than string. This can lead to issues when attempting to read metadata.

Example: Bytes32Metadata.sol


Revert on Transfer to the Zero Address

Tokens, such as those based on OpenZeppelin’s implementation, revert when transferring to address(0). This could disrupt systems relying on address(0) for burning tokens.

Example: RevertToZero.sol


No Revert on Failure

Some tokens, such as ZRX and EURS, return false rather than reverting on failure. This behavior, while technically compliant with ERC20, deviates from typical Solidity coding practices and may be overlooked by developers who forget to handle non-revert failures.

Example: NoRevert.sol


Revert on Large Approvals & Transfers

Tokens like UNI and COMP revert if the value passed to approve or transfer exceeds uint96. They also implement special logic for approve that sets allowances to type(uint96).max if the approval amount equals uint256(-1).

Example: Uint96.sol


Code Injection via Token Name

Some malicious tokens include JavaScript code in their name attribute, allowing attackers to steal private keys from users interacting with the token via vulnerable frontends. This tactic has been used in the wild, notably to exploit EtherDelta users.


Unusual Permit Function

Certain tokens, such as DAI, RAI, GLM, and others, have a permit() function that does not follow EIP2612. Tokens without a proper permit() implementation may not revert, leading to unexpected execution of subsequent lines of code. Uniswap’s Permit2 is a more compatible alternative for handling such tokens.

Example: DaiPermit.sol


Transfer of Less Than Amount

Tokens like cUSDCv3 contain special handling for transfers when amount == type(uint256).max, transferring only the user’s balance. Systems that transfer user-supplied amounts without verifying the actual transferred value may encounter issues with these tokens.


This section provides examples and insights into some of the common pitfalls smart contract developers face when interacting with ERC20 tokens. These behaviors can break smart contracts or lead to vulnerabilities if not accounted for properly. The provided examples illustrate these scenarios, encouraging caution and defensive programming practices.

Last updated