Beyond the Basics: ERC20 Approval Pitfalls for Smart Contracts
Understanding the ERC‑20 Approval Mechanism
The ERC‑20 standard introduces a straightforward permission model: a token holder calls approve to grant another address—often a smart contract—permission to spend a defined amount of tokens on their behalf. The delegated address then calls transferFrom to move tokens from the holder’s balance to any destination. This pattern is ubiquitous across DeFi protocols, lending platforms, NFT marketplaces, and more.
While the interface looks simple, the interaction between approve, allowance, and transferFrom hides several subtle assumptions. These assumptions become dangerous when contracts are reused, upgraded, or composed with external actors. The core of the problem is that approve establishes a mutable state (the allowance mapping) that is shared between multiple parties. Every state change carries the risk of race conditions, accidental double spending, or inadvertent revocation.
Below we dissect the most common pitfalls, illustrate how they can be exploited, and outline mitigations that can be applied in contract design and audits.
Common Approval Pitfalls
The “Double Approval” Problem
When a user wants to update the allowance from a previous value to a new one, the usual pattern is:
approve(spender, 0)approve(spender, newAmount)
This two‑step sequence is intended to mitigate a race condition: a malicious spender could spend the old allowance before the new one is set. The zero‑approval step forces the spender to acknowledge the old allowance has been cleared. However, many projects skip the intermediate zero call because they assume atomicity. This oversight can lead to double spending if a transaction that uses the old allowance is mined between the two approvals.
Missing Allowance Checks
Some contracts directly call transferFrom without first verifying that the caller has a sufficient allowance. They rely on transferFrom to revert if the allowance is insufficient. While this works under normal circumstances, it can lead to surprising revert messages or hidden costs if transferFrom fails silently in wrapped contracts or proxies. To guard against this, it’s wise to follow the guidelines in “Secure Your ERC20 Tokens: Best Practices for Approval and transferFrom”.
Blind Trust in the Owner’s Approval
In many DEX or yield‑aggregator contracts, the owner of a token approves a large allowance once, and the contract never revokes it. If the owner loses control of the wallet or a key is compromised, the attacker can drain the contract’s entire balance by repeatedly calling transferFrom. The absence of an automatic revocation policy or time‑based expiration turns the approve call into a long‑term liability. Strategies for safeguarding funds are detailed in “How to Protect Your DeFi Funds from transferFrom Attacks”.
Reentrancy via transferFrom
A contract that receives ERC‑20 tokens in its fallback or receive function may inadvertently call transferFrom inside that callback. If the token’s transferFrom implementation invokes a hook (e.g., ERC‑777’s tokensReceived), reentrancy becomes possible. The spender can re‑enter the contract and perform additional actions before the state updates complete, potentially draining balances or manipulating internal accounting. For guidance on defensive patterns, see “Guarding Against transferFrom Attacks: A Guide for DeFi Projects”.
Delegated Control and Governance Escrows
In many DAOs and governance contracts, a multi‑sig wallet holds the authority to approve token allowances for the contract. If that wallet’s key is compromised or the governance logic is flawed, an attacker can approve themselves to spend unlimited tokens, circumventing the contract’s own security checks. This kind of vulnerability is explored in depth in “Mitigating transferFrom Exploits in Decentralized Finance”.
Race Condition Vulnerabilities
Classic Approval Race
Consider a scenario where a user intends to reduce the allowance from 100 tokens to 50. The user submits two transactions:
- Tx A:
approve(spender, 0) - Tx B:
approve(spender, 50)
If Tx B is mined before Tx A, the spender receives a 50‑token allowance immediately. A malicious actor can exploit this by sending transferFrom between the two approvals, receiving 100 tokens before the allowance is reduced. The attacker’s strategy relies on transaction ordering in the mempool, a feature that users often cannot control.
Timing Attacks via Block Ordering
In some cases, attackers can observe the mempool and front‑run a transaction that clears an allowance. By placing their own transaction with higher gas, they can modify the allowance in the same block, creating a window where transferFrom succeeds with an unexpectedly high amount. Even if the spender’s contract checks the allowance before calling transferFrom, the gas costs associated with the check and subsequent transfer can be leveraged for economic gain.
Missing Allowance Checks
A common pattern in DeFi protocols is to trust that the token contract will revert if the allowance is insufficient. However, tokens that deviate from the standard or implement custom logic may silently return false or perform no action. Contracts that do not explicitly check the return value can miss the failure, leading to state inconsistencies.
Example:
// Bad practice: trusting the token contract to revert
token.transferFrom(msg.sender, address(this), amount);
If the token returns false, the caller may assume the transfer succeeded, but the state will remain unchanged. In more complex systems, this mismatch can cascade, corrupting user balances and protocol metrics.
Reentrancy via transferFrom
Reentrancy is traditionally associated with call or delegatecall, but transferFrom can also trigger callbacks if the token implements hook functions (ERC‑777, ERC‑20 with extensions). A contract that calls transferFrom in its fallback or receive logic may be vulnerable:
function onERC20Received(address, address, uint256, bytes calldata) external {
// Dangerous: re‑entering contract
token.transferFrom(msg.sender, address(this), 100);
}
If the token emits an event or calls back into the receiving contract before updating the allowance, an attacker can recursively invoke the function, draining funds or bypassing access controls.
Delegated Control Risks
Delegated approvals are attractive for governance, but they shift the risk to external actors. The contract cannot verify that the approved spender truly follows protocol rules because the approval is an external state change. If the approved address is compromised or malicious, the contract’s internal logic becomes irrelevant. The safest approach is to avoid long‑term approvals and instead request allowances only when needed, using one‑off approvals or short‑lived allowances.
Best Practices for Safe Approvals
1. Use the “Check‑Effect‑Interaction” Pattern
Always check the allowance before updating state or interacting with external contracts. This mitigates reentrancy and race conditions.
uint256 allowed = token.allowance(msg.sender, address(this));
require(allowed >= amount, "Insufficient allowance");
2. Implement “SafeApprove” or “SafeIncreaseAllowance”
Instead of calling approve directly, use wrappers that enforce zero‑approval or check the current allowance before modifying it.
function safeApprove(address token, address spender, uint256 amount) internal {
IERC20(token).approve(spender, 0); // Ensure old allowance cleared
IERC20(token).approve(spender, amount);
}
Libraries such as OpenZeppelin provide SafeERC20 that guard against non‑reverting tokens and non‑boolean returns.
3. Adopt “Permit” Extensions
EIP‑2612 introduces permit, which allows approvals via signatures instead of on‑chain transactions. This eliminates the need for an approve call and reduces the attack surface. When available, use permit for single‑use allowances.
4. Use Short‑Lived or One‑Time Approvals
If the contract only needs a limited number of tokens for a specific operation, request the allowance for that exact amount and revoke it immediately after the operation.
token.approve(spender, amount);
// Execute operation
token.approve(spender, 0); // Revoke
5. Avoid Permanent Global Approvals
Do not approve the contract to spend an unlimited amount of a user’s tokens. If you must, restrict the allowance to a reasonable cap (e.g., the maximum expected trade size) and rotate it frequently.
6. Enforce Timed Allowance Expirations
Design a token wrapper that tracks approval timestamps and automatically revokes allowances after a preset duration. This reduces the risk of stale approvals.
7. Audit Token Compatibility
Before integrating a new token, confirm that its transferFrom adheres to the standard: it must revert on failure, return a boolean, and update balances atomically. If a token is non‑standard, consider wrapping it or refusing to interact.
8. Leverage Upgradeable Proxy Patterns Carefully
When using proxies, ensure that approval logic resides in the implementation contract, not in the proxy. Proxies should not store state that can be inadvertently modified by an attacker through the upgrade mechanism.
Tools and Auditing Techniques
Static Analysis
Tools like Slither, Mythril, and SmartCheck can detect patterns that lead to approval misuse, such as missing allowance checks or unsafe approve usage. Auditors should look for:
- Direct calls to
approvewithout safety wrappers - Usage of non‑standard tokens
- Absence of
requirechecks for allowance
Formal Verification
Frameworks like Certora and K Framework allow formal proofs that a contract enforces allowance invariants. By modeling the contract as a state machine, auditors can prove that transferFrom cannot succeed unless the allowance is adequate.
Runtime Monitoring
Deploy a monitoring layer that tracks all approve events on the blockchain. Flag unusually large approvals or approvals that persist for extended periods. Real‑time alerts help developers react before a compromise materializes.
Penetration Testing
Simulate race condition attacks by submitting competing approve transactions from controlled accounts. Test how the contract behaves when an attacker attempts to front‑run or reorder transactions.
Real‑World Cases
Case Study 1: The 2020 ERC‑20 Double Approval Exploit
In early 2020, a popular lending protocol suffered a loss of $12 million when an attacker exploited the double‑approval race. The protocol’s contract allowed users to set a new allowance by calling approve twice. The attacker submitted two approve calls simultaneously: one to set the allowance to zero, the other to set a new amount. The higher gas price transaction cleared the allowance first, enabling the attacker to perform a transferFrom before the new allowance took effect. The audit revealed that the contract did not enforce a zero‑approval step, a pattern that had been documented in Solidity best‑practice guides but was omitted.
Case Study 2: The 2021 Token Reentrancy via transferFrom
A decentralized exchange implemented a flash swap that borrowed ERC‑20 tokens by calling transferFrom from a vault contract. The ERC‑20 token used a custom hook that emitted a callback after transfer. The exchange contract did not guard against reentrancy in its transferFrom calls. An attacker exploited this by recursively calling transferFrom within the callback, draining the vault. The incident highlighted the need for safe allowance patterns and reentrancy guards even when interacting with standard ERC‑20 tokens.
Case Study 3: Governance Escape through Delegated Approvals
A DAO used a multi‑sig wallet to approve token allowances for its treasury contract. The wallet’s keys were stored on a cold storage device. An attacker compromised the device and used the wallet to approve themselves a large allowance, then performed a transferFrom to siphon the DAO’s treasury. This breach exposed the risk of delegating long‑term approval authority to an external entity without revocation or monitoring.
Conclusion
ERC‑20 approvals are deceptively simple yet notoriously fragile. The combination of mutable allowance state, external approvals, and the lack of a standard for expiration creates a fertile ground for exploits. By understanding the common pitfalls—double approvals, missing checks, race conditions, reentrancy, and delegated control—developers can design safer contracts.
Key takeaways:
- Never assume atomicity: Always clear allowances before setting new ones or use
permit. - Explicitly check allowances: Validate before any state‑changing interaction.
- Use safety wrappers:
SafeERC20,safeApprove, and similar libraries reduce risk. - Implement revocation: Revoke allowances after use or after a reasonable timeout.
- Audit comprehensively: Static analysis, formal verification, and penetration testing are essential.
- Monitor in production: Real‑time alerts on approvals can catch anomalous behavior early.
By applying these best practices, the DeFi ecosystem can move beyond the basics and safeguard users against the subtle yet damaging vulnerabilities that lurk in the approval mechanics.
Lucas Tanaka
Lucas is a data-driven DeFi analyst focused on algorithmic trading and smart contract automation. His background in quantitative finance helps him bridge complex crypto mechanics with practical insights for builders, investors, and enthusiasts alike.
Random Posts
How NFT Fi Enhances Game Fi A Comprehensive Deep Dive
NFTFi merges DeFi liquidity and NFT rarity, letting players, devs, and investors trade in-game assets like real markets, boosting GameFi value.
6 months ago
A Beginner’s Map to DeFi Security and Rollup Mechanics
Discover the essentials of DeFi security, learn how smart contracts guard assets, and demystify optimistic vs. zero, knowledge rollups, all in clear, beginner, friendly language.
6 months ago
Building Confidence in DeFi with Core Library Concepts
Unlock DeFi confidence by mastering core library concepts, cryptography, consensus, smart-contract patterns, and scalability layers. Get clear on security terms and learn to navigate Optimistic and ZK roll-ups with ease.
3 weeks ago
Mastering DeFi Revenue Models with Tokenomics and Metrics
Learn how tokenomics fuels DeFi revenue, build sustainable models, measure success, and iterate to boost protocol value.
2 months ago
Uncovering Access Misconfigurations In DeFi Systems
Discover how misconfigured access controls in DeFi can open vaults to bad actors, exposing hidden vulnerabilities that turn promising yield farms into risky traps. Learn to spot and fix these critical gaps.
5 months ago
Latest Posts
Deep Dive Into L2 Scaling For DeFi And The Cost Of ZK Rollup Proof Generation
Learn how Layer-2, especially ZK rollups, boosts DeFi with faster, cheaper transactions and uncovering the real cost of generating zk proofs.
1 day ago
Modeling Interest Rates in Decentralized Finance
Discover how DeFi protocols set dynamic interest rates using supply-demand curves, optimize yields, and shield against liquidations, essential insights for developers and liquidity providers.
1 day ago
Managing Debt Ceilings and Stability Fees Explained
Debt ceilings cap synthetic coin supply, keeping collateral above debt. Dynamic limits via governance and risk metrics protect lenders, token holders, and system stability.
1 day ago