ERC20 Smart Contract Security

ERC20 is the most widely implemented token standard in the Ethereum ecosystem. Its simplicity made it ubiquitous — and its edge cases make it one of the most common sources of smart contract vulnerabilities. Integrating ERC20 tokens safely requires understanding what the standard guarantees and, critically, what it does not.

What ERC20 defines (and what it doesn't)

The ERC20 standard defines a minimal interface: transfer, transferFrom, approve, allowance, totalSupply, and balanceOf. It does not specify:

  • Whether transfer must return true (many older tokens return nothing)
  • Whether transfers must fail by reverting (some return false silently)
  • Whether the amount received equals the amount sent (fee-on-transfer tokens take a cut)
  • Whether the permit extension is implemented or is a no-op

Vulnerability 1: Non-standard return values

Several major tokens — including USDT, BNB, and OMG — do not return a boolean from transfer or transferFrom. In Solidity 0.8+, calling these functions with the standard ABI encoding will revert because the ABI decoder expects a return value and gets nothing.

Fix: Always use OpenZeppelin's SafeERC20 library instead of calling transfer directly:

using SafeERC20 for IERC20;
token.safeTransfer(recipient, amount);   // safe for all tokens
token.safeTransferFrom(from, to, amount);

Affected tokens: USDT, BNB (ERC20), OMG. See unchecked return values.

Vulnerability 2: Silent failure on transfer (no revert on failure)

Some tokens return false on failure instead of reverting. If you call token.transfer() and don't check the return value, the transfer silently fails while your contract proceeds — leading to accounting errors or fund loss.

Affected tokens: BAT, HT, cUSDC, ZRX. Using SafeERC20.safeTransfer automatically handles this case.

Vulnerability 3: Fee-on-transfer and rebasing tokens

Tokens like PAXG charge a fee on every transfer — the recipient receives less than the sender sent. Rebasing tokens like stETH change balances automatically every day. Contracts that cache transfer amounts or snapshot balances will have accounting errors.

Fix: Measure actual received amounts with balance-before/after checks:

uint256 before = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), amount);
uint256 received = token.balanceOf(address(this)) - before;

Affected tokens: PAXG, stETH.

Vulnerability 4: Non-standard permit (EIP-2612)

The permit extension lets users sign off-chain approvals. WETH's permit is a no-op — it silently does nothing. DAI uses a non-standard permit format. Protocols that assume standard EIP-2612 behavior break silently when called with these tokens.

Affected tokens: WETH, DAI. See also ERC2612 security.

Vulnerability 5: Blocklist and pausing

Stablecoins like USDC include a blocklist — Circle can prevent any address from sending or receiving USDC. If a user or protocol address is blocked, token transfers will revert unexpectedly, breaking accounting and potentially locking funds.

Affected tokens: USDC, TUSD.

Vulnerability 6: Approve race condition

ERC20's approve function has a known front-running race condition. If Alice has approved Bob for 100 tokens and wants to change it to 50, Bob can front-run the second approval to spend 150 total (100 from the original approval, then 50 from the new one).

Fix: Use increaseAllowance / decreaseAllowance from OpenZeppelin, or reset allowance to 0 before setting a new value.

Related standards

  • ERC777 — Adds transfer hooks, enables reentrancy
  • ERC2612 — Adds permit() for gasless approvals
  • ERC4626 — Tokenized vault standard built on ERC20

Audit ERC20 token integration

SmartContract.us automatically scans for all known non-standard token interactions in your contract, including USDT compatibility, fee-on-transfer handling, and permit misuse.