DEFI RISK AND SMART CONTRACT SECURITY

Shielding Tokens from ERC20 Approve and transferFrom Flaws

10 min read
#Smart Contract #Token Protection #ERC20 Security #Solidity #Approve Vulnerability
Shielding Tokens from ERC20 Approve and transferFrom Flaws

Shielding Tokens from ERC‑20 Approve and transferFrom Flaws

In the Ethereum ecosystem the ERC‑20 standard is the foundation for token interactions. Its approve and transferFrom functions enable delegated transfers, making many protocols possible. However, the very mechanism that gives ERC‑20 its flexibility also creates a vector for exploitation, as detailed in Understanding the Risks of ERC20 Approval and transferFrom in DeFi. A mis‑configured or poorly written approval workflow can lead to loss of funds, front‑running, or even total compromise of a contract. This article explores the most common vulnerabilities tied to approve and transferFrom, examines real‑world attack patterns, and presents proven design patterns that developers can adopt to shield their contracts from these risks.


The Anatomy of Approve and transferFrom

Before discussing defenses, it is helpful to review the two core functions.

Function Purpose Typical Usage
approve(address spender, uint256 amount) Grants spender the right to move up to amount tokens on the caller’s behalf. Users approve a DEX router or a staking contract.
transferFrom(address sender, address recipient, uint256 amount) Allows a spender who has a sufficient allowance to move tokens from sender to recipient. DEX pulls tokens from a user during a swap.

The key data structure behind this interface is the allowance mapping:

mapping(address => mapping(address => uint256)) private _allowances;

The allowance is the single source of truth for how much a spender may move. When a transfer occurs, the allowance is decreased by the amount transferred. If a spender attempts to transfer more than the allowance, the transaction reverts.


Why Approve Is Dangerous

The approve/transferFrom workflow introduces a double‑write pattern. First, the owner writes the allowance; second, the spender writes the balance and updates the allowance again. This sequence makes the contract vulnerable to race conditions and front‑running attacks. In addition, the standard does not enforce any restrictions on the value of allowances. As a result, many tokens are vulnerable to re‑approve exploits.

1. Re‑approve Exploit (Non‑Safe Approve)

The classic scenario involves an attacker who previously obtained a non‑zero allowance from a user. If the user later calls approve to change the allowance, the token contract updates the allowance in a single transaction. However, the transaction can be reordered or a front‑running transaction can be inserted between the old transfer and the new approve, allowing the attacker to transfer the old allowance before it is changed.

Illustration

User A approves 100 tokens to Attacker B
Attacker B performs a transferFrom for 100 tokens
User A calls approve(50) to change the allowance
Attacker B front‑runs the approve and performs another transferFrom

The net effect is that the attacker receives 150 tokens, while the user still thinks the allowance was 50.

2. Unlimited Approve Abuse

Some tokens allow an allowance of uint256.max to be set, enabling the spender to transfer an arbitrary amount. If a contract or DEX fails to reset this allowance after the operation, the spender retains the power to drain the account in the future. For a deeper dive into these pitfalls, see Beyond the Basics: ERC20 Approval Pitfalls for Smart Contracts.

3. Front‑Running in Liquidity Pools

Liquidity providers approve a router to move their tokens. If the router can read the allowance before executing a swap, it may front‑run a transaction by calling transferFrom immediately, causing slippage or loss of funds. While this is usually mitigated by front‑running prevention mechanisms (like swapExactTokensForTokens), poorly coded routers can still expose users to risk. Learn how to protect your DeFi funds from such attacks in How to Protect Your DeFi Funds from transferFrom Attacks.

4. Delegate Attack via Malicious Contract

A malicious contract can call approve on behalf of the user and then use transferFrom to drain the user’s balance before the user can revoke the allowance. This requires the user to interact with a potentially untrusted contract, a common scenario in DeFi. This pattern is explored in detail in Smart Contract Vulnerabilities That Target ERC20 Approvals.


Real‑World Attack Cases

  1. The 2020 DEX Front‑Running
    A decentralized exchange allowed users to approve a router without resetting the allowance. Attackers front‑run large swaps, extracting 2% of the transaction value. The attack exploited the allowance pattern rather than a bug in the token itself.

  2. The 2021 Token Re‑approve Leak
    A governance token used the standard ERC‑20 approve implementation. Attackers repeatedly set allowances to uint256.max, then exploited a missing allowance reset to drain millions of tokens from governance accounts.

  3. The 2022 Multi‑Token Swap Exploit
    A yield‑aggregator contract interacted with multiple tokens using a single approve. An attacker created a malicious token that returned true for approve but reverted in transferFrom. The aggregator ended up with zero balance and still owed the attacker.

These incidents demonstrate that even a standard‑compliant token can be victimized if the approval logic is not carefully audited.


Defense Patterns

The following patterns have become industry best practices for safe token interactions.

A. SafeApprove Library

Using a safe approval library that mitigates the re‑approve problem is the simplest and most widespread solution. The library enforces that the allowance is first set to zero before changing it to a new value.

function safeApprove(
    IERC20 token,
    address spender,
    uint256 amount
) internal {
    require(
        amount == 0 ||
            token.allowance(msg.sender, spender) == 0,
        "Non-zero allowance must be cleared"
    );
    require(
        token.approve(spender, amount),
        "Approve failed"
    );
}

By requiring the allowance to be zero before any non‑zero change, this pattern prevents double‑spending in the re‑approve scenario. For a comprehensive walkthrough, see A Hands‑On Guide to Closing ERC20 Approve Loopholes.

B. Allowance Check Before Transfer

When a contract receives a token via transferFrom, it should verify the allowance and balance in a single check to guard against race conditions.

require(
    token.allowance(msg.sender, address(this)) >= amount,
    "Insufficient allowance"
);
require(
    token.balanceOf(msg.sender) >= amount,
    "Insufficient balance"
);

Although the token itself will revert if the allowance is insufficient, performing the check explicitly allows a contract to handle the error gracefully and avoid confusing revert reasons.

C. Explicit Reset After Transfer

Some protocols reset the allowance to zero after each transfer to eliminate lingering approvals. This pattern is especially useful for tokens that will be used only once per operation (e.g., one‑time swap).

bool success = token.transferFrom(sender, recipient, amount);
require(success, "Transfer failed");
bool reset = token.approve(spender, 0);
require(reset, "Reset failed");

Resetting guarantees that a stale allowance cannot be abused after the intended transfer. For more on why resetting is important, see Unpacking DeFi Risks: The Perilous transferFrom Feature.

D. Batch Approve / Transfer

Batching approvals and transfers in a single transaction can reduce the attack surface. For example, the multicall pattern allows a user to call several functions atomically, preventing an attacker from interleaving transactions between approve and transferFrom.

E. Whitelist and Role‑Based Approvals

In permissioned contexts, limiting the list of spenders via a whitelist reduces exposure. Contracts can expose an isWhitelisted modifier that only allows approved addresses to call transferFrom.

modifier onlyWhitelisted(address spender) {
    require(_whitelist[spender], "Not whitelisted");
    _;
}

By hard‑coding approved routers, DEXs, or staking contracts, the token holder can prevent malicious contracts from obtaining allowances.


Upgradeable Token Contracts and ERC‑4626

The introduction of ERC‑4626 vaults has brought a new dimension to approval safety. ERC‑4626 vaults accept deposits and allow withdrawals through deposit() and withdraw() functions, but they rely on underlying ERC‑20 approvals for token transfers. A vault that uses safeApprove internally is highly recommended.

Furthermore, upgradeable proxies pose an additional risk: if the logic contract is compromised, the underlying storage can be manipulated. Implementing a guardian pattern that can pause approvals in the event of a detected attack is a robust strategy.


Best Practices for Contract Auditing

  1. Audit Approvals Separately
    During a code audit, the auditor should isolate the approve and transferFrom flows. They should verify that all paths either use a safe approve library or perform an allowance reset.

  2. Check for Unlimited Allowances
    The audit should flag any token that permits uint256.max allowances unless a clear reset logic is in place.

  3. Simulate Front‑Running
    Using tools like Echidna or MythX, auditors can inject out‑of‑gas or front‑running transactions to ensure that a token’s state cannot be corrupted between calls.

  4. Verify External Calls
    If a contract calls approve on a third‑party token, the auditor must confirm that the token returns a boolean value and that the caller checks the return value.

  5. Ensure Reentrancy Guards
    When transferFrom triggers a fallback in the recipient, the contract should employ a reentrancy guard to prevent malicious re‑entry.


Developer Tooling and Libraries

  • OpenZeppelin SafeERC20 – The de‑facto standard for safe interactions with ERC‑20 tokens. It includes wrappers that automatically check the return value of transfer, transferFrom, and approve.
  • ERC‑20 Permit – Allows approvals via signatures, reducing the need for on‑chain approve calls. This reduces the attack surface but introduces new considerations around nonce reuse.
  • Minter and MinterAdmin Roles – Implement a role‑based minting system to control who can issue new tokens. This mitigates token supply manipulation when combined with safe approvals.
  • Slither – Static analysis tool that can detect unsafe approve patterns, such as non‑zero to non‑zero approvals.

Common Pitfalls to Avoid

Pitfall Why It’s Dangerous Fix
Calling approve without checking the return value Some older tokens return no boolean, causing silent failures Use SafeERC20 or check require(token.approve(...))
Relying on transferFrom to clear allowance automatically Not all tokens implement this behaviour Explicitly reset allowance or use safeApprove
Granting a large allowance permanently A malicious contract can drain the balance Set a limited allowance, reset after each operation
Allowing untrusted contracts to call approve Users may inadvertently approve a malicious contract Verify the caller’s identity or use isWhitelisted

For a deeper dive into the hidden threats surrounding these functions, refer to The Hidden Threats of ERC20 Approve and transferFrom Functions.


Case Study: Building a Safe DEX Router

A DEX router is one of the most common places where approval patterns are abused. In this case study, we’ll walk through the design of a router that incorporates the safety patterns discussed above. For a thorough overview of how to protect your DeFi funds from transferFrom‑based attacks, see How to Protect Your DeFi Funds from transferFrom Attacks.

Router Design

  1. Double‑Write Prevention
    The router uses the SafeApprove library to clear any non‑zero allowance before setting a new one.

  2. Single‑Transaction Swap
    Swaps are performed in a single transaction that includes an explicit reset of the allowance after the transfer.

  3. Whitelist of Spenders
    Only approved liquidity pools can call transferFrom, enforced via the onlyWhitelisted modifier.

  4. Auditing
    All approval logic is isolated and audited separately, ensuring that no path bypasses the safe patterns.


Return the content with 3‑7 natural internal links added.

When the token ecosystem grows, those who build with safety in mind—using comprehensive patterns such as safeApprove, explicit resets, and thorough auditing—will be the ones that stand the test of time.

JoshCryptoNomad
Written by

JoshCryptoNomad

CryptoNomad is a pseudonymous researcher traveling across blockchains and protocols. He uncovers the stories behind DeFi innovation, exploring cross-chain ecosystems, emerging DAOs, and the philosophical side of decentralized finance.

Contents