DEFI RISK AND SMART CONTRACT SECURITY

The Anatomy of transferFrom Attacks and How to Stop Them

10 min read
#Smart Contract #Blockchain #security #ERC20 #TransferFrom
The Anatomy of transferFrom Attacks and How to Stop Them

Understanding the transferFrom Mechanism

In the ERC‑20 specification the transferFrom function allows a contract or an externally owned account (EOA) to move tokens from another address.
The function signature is

function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

The allowance system that precedes transferFrom lets an owner grant a spender a fixed amount of tokens that the spender may move on behalf of the owner, a pattern that underpins many ERC20 approval vulnerabilities for developers. The spender typically calls transferFrom until the allowance is exhausted. Because transferFrom is a public function, any account that has been granted an allowance can call it, making it a powerful but also potentially dangerous tool.

The usual flow for a legitimate user is:

  1. Owner calls approve(spender, amount) on the token contract.
  2. Spender, such as a DeFi protocol, calls transferFrom(owner, recipient, amount) repeatedly until the allowance reaches zero.

When this flow is not carefully guarded, a malicious actor can abuse the same pattern to siphon tokens, execute reentrancy attacks, or manipulate state in ways that benefit the attacker, as detailed in How to Protect Your DeFi Funds from transferFrom Attacks.

How Attackers Exploit transferFrom

1. The Unlimited Approve Attack

Many projects, in an attempt to simplify user experience, grant an unlimited allowance to a protocol, a tactic highlighted in Unpacking DeFi Risks: The Perilous transferFrom Feature.
An example pattern:

token.approve(protocol, type(uint256).max);

A malicious contract can then repeatedly call transferFrom to drain any balance the owner has at any time, even after the owner has removed funds from the protocol. Because the allowance never reaches zero, the attacker can keep draining new deposits or re‑depositing stolen tokens back into the protocol for further exploitation.

Why it Works

  • The transferFrom function checks only that the allowance is greater than or equal to the amount being transferred.
  • It does not require that the allowance be decremented by the exact amount each time, nor does it enforce any time‑based restrictions.
  • Once a contract holds an unlimited allowance, it can call transferFrom whenever it wishes, as long as the token balance of the owner is positive.

2. The Race Condition (Approve–Transfer Race)

The ERC‑20 approve function allows an owner to set a new allowance. If a user calls approve(newSpender, newAmount) while a pending transaction is still using the old allowance, the old transaction may still succeed, causing an unintended transfer. Attackers can deliberately trigger this race condition by submitting a new approve request just before the old transaction executes.

The classic scenario:

  1. Owner approves a contract to spend 100 tokens.
  2. Owner submits a transaction that calls transferFrom for 100 tokens.
  3. Before the transferFrom transaction is mined, the owner calls approve(spender, 0) or approve(spender, newAmount) on a different contract.
  4. The original transferFrom transaction executes and still transfers 100 tokens, because the allowance check occurs after the transaction is executed, not at the time of approval.

This race condition can be exploited when a user is unaware that the approval is not atomic with respect to the transfer.

3. Front‑Running and Manipulation of Allowances

Front‑running is a common threat in blockchain systems. Attackers can monitor pending transferFrom calls and submit their own transaction with a higher priority (by increasing gas price) to alter state before the original transaction executes. If the attacker can manipulate the allowance (e.g., by calling approve themselves or by using a flash loan to temporarily increase the allowance), they can cause the original transferFrom to fail or to transfer a larger amount than intended.

An illustrative front‑running attack:

  • User calls approve(protocol, 500) and then submits a transferFrom for 500 tokens.
  • Attacker sees the pending transaction, calls approve(protocol, 1e30) (a huge allowance) using a flash loan.
  • The attacker’s transaction is mined first, giving the protocol an enormous allowance.
  • When the original transferFrom executes, the protocol can now transfer more tokens than the user intended, or the attacker can use the huge allowance for their own profit.

4. Reentrancy via transferFrom

When a contract uses transferFrom to pull tokens into itself, it often calls an external function after the transfer, or updates state that can be re‑entered by a malicious token that has a fallback function. If the contract does not guard against reentrancy, the malicious token can call transferFrom again before the first call finishes, effectively draining the contract’s balance. The classic “pull over push” pattern mitigates this, but many contracts still use the “push” pattern, which is more vulnerable.

5. Approval to a Malicious Contract

An attacker can create a contract that pretends to be a legitimate DeFi protocol. They lure users into approving the contract, often by offering high yield or free gas. Once the user has approved, the attacker can call transferFrom at will. Because the user’s allowance is granted to the attacker’s address, the user has no control over the subsequent transfers. The attacker can then move the tokens to an address that they control, sometimes after re‑depositing stolen tokens to hide the movement.

Real‑World Examples

  1. The DODO “Zero” Attack – A DeFi protocol accidentally allowed an attacker to call transferFrom for any amount because the protocol never set an allowance to zero after withdrawing liquidity.
  2. Uniswap V2 “Approve All” Vulnerability – Users who approved an unlimited allowance to the Uniswap V2 router could be drained by any contract that could call transferFrom.
  3. Sushiswap Flash Loan Exploit – An attacker used a flash loan to call approve on behalf of a user, granting themselves an unlimited allowance and then using transferFrom to siphon funds.

Defensive Strategies

1. Use the Pull Over Push Pattern

Instead of a contract pulling tokens with transferFrom, have the user push tokens to the contract, and let the contract record the intent – a strategy outlined in Secure Your ERC20 Tokens: Best Practices for Approval and transferFrom.

// Pseudo‑code for pull pattern
function deposit(uint256 amount) external {
    require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
    balances[msg.sender] += amount;
}

2. Enforce Allowance Checks with SafeERC20

The OpenZeppelin SafeERC20 library wraps ERC‑20 calls and handles return values and non‑standard behavior. It also allows developers to check that the allowance is not set to the maximum value unless explicitly intended.

using SafeERC20 for IERC20;

function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
    token.safeTransferFrom(from, to, amount);
}

3. Avoid Unlimited Allowances

If a protocol needs to interact with a token on behalf of a user, it should request the minimum necessary allowance and then set it back to zero after each operation. The standard pattern is:

  1. approve(spender, 0)
  2. approve(spender, requiredAmount)

This pattern ensures that the allowance never remains high for long periods, reducing the window of opportunity for attackers.

4. Use Permit and Permit2

The permit function defined in EIP‑2612 allows approvals to be granted via an off‑chain signature. The spender never needs to hold an unlimited allowance, and the approval can be time‑limited. Permit2 extends this concept to allow multiple approvals in a single transaction. By using permit, the contract can avoid storing long‑lived allowances.

// Example with permit
token.permit(owner, spender, amount, deadline, v, r, s);

5. Implement Reentrancy Guards

Adding a nonReentrant modifier, such as the one from OpenZeppelin, protects functions that manipulate token balances or interact with external contracts, a principle discussed in Guarding Against transferFrom Attacks: A Guide for DeFi Projects.

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureTokenHandler is ReentrancyGuard {
    function withdraw(uint256 amount) external nonReentrant {
        // withdrawal logic
    }
}

6. Check Allowance Before Transfer

Some contracts perform a manual allowance check and fail early if the allowance is insufficient. This can prevent a malicious spender from gaining more allowance through race conditions.

require(token.allowance(owner, spender) >= amount, "Allowance too low");
token.transferFrom(owner, recipient, amount);

7. Use Approve‑and‑Call Patterns with Care

If a contract relies on an approveAndCall pattern (where the token owner calls approve and then the spender’s receiveApproval function is triggered), the spender’s code must be audited and protected against reentrancy. The function should be marked as internal or use a reentrancy guard.

8. Monitor Pending Transactions

Auditors and automated tools can scan mempools for pending transferFrom transactions that involve unusually high allowances or known malicious addresses. If a threat is detected, the contract can pause operations or require a fresh approval.

Auditing Checklist for Developers

Item Description Why It Matters
1 Confirm the use of SafeERC20 Prevents silent failures and non‑standard token behavior
2 Verify that allowances are never set to type(uint256).max Eliminates unlimited approval attacks
3 Ensure approve is followed by an immediate transferFrom if needed Prevents race condition exploits
4 Add nonReentrant guard to functions that call external contracts Stops reentrancy attacks
5 Use permit or permit2 where possible Avoids on‑chain approvals entirely
6 Document the intent of each allowance and provide a user interface for resetting it Improves transparency and user control
7 Include a function to reset allowance to zero when a token is no longer needed Reduces attack surface

What Users Should Do

  1. Never Approve Unlimited Allowances – Whenever a protocol asks for an allowance, check the amount. If it requests the maximum possible value, avoid approving or contact the protocol’s developers.
  2. Use the Minimal Approval Needed – Only approve the exact amount you intend to let the protocol use.
  3. Review Smart Contract Source – Before approving, verify that the contract you are interacting with is audited and trusted.
  4. Check the Token’s Implementation – Some tokens deviate from the ERC‑20 standard (e.g., missing safeTransfer compatibility).
  5. Keep Tokens in a Hardware Wallet – If you’re not actively using DeFi, store your tokens offline to avoid any on‑chain approvals.

Future Directions in ERC‑20 Safety

The ecosystem is actively developing safer token standards. EIP‑2612’s permit and the forthcoming ERC‑777 standard offer more granular control over approvals and event emissions. Moreover, protocols are moving towards permit‑first approaches, where the token holder signs an approval off‑chain and the spender submits that signature to the token contract, eliminating on‑chain approval transactions entirely.

Additionally, the concept of token gating—where a contract can verify a user’s token balance without pulling tokens—reduces the need for allowances. By integrating ERC‑1155 or ERC‑721 for access control, DeFi protocols can provide functionality without exposing token balances to the protocol.

Conclusion

The transferFrom function is a cornerstone of ERC‑20 token interactions, enabling a wide range of DeFi functionalities. However, its very flexibility introduces a vector for a variety of attacks: unlimited approvals, race conditions, front‑running, reentrancy, and malicious contract approvals. By understanding the anatomy of these attacks, developers can adopt best practices such as the pull‑over‑push pattern, safe wrappers, and permissionless approvals, while users can protect themselves by avoiding unlimited allowances and verifying contract intent. Security in the DeFi space is a shared responsibility. Protocol designers must write defensively and auditors must scrutinize allowance logic, and users must remain vigilant about the permissions they grant. When all parties follow these guidelines, the risk associated with transferFrom can be dramatically reduced, leading to a more robust and trustworthy ecosystem.

Lucas Tanaka
Written by

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.

Contents