SCWE-103: ERC20 Approval Double-Spend (Allowance Race)
Stable Version v1.0
This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).
Relationships¶
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization (Race Condition)
https://cwe.mitre.org/data/definitions/362.html
Description¶
Changing an ERC20 allowance from value X to Y with a single approve call allows a spender to front-run and spend X before the change, then spend Y after, effectively double-spending. Integrations that do not use increaseAllowance/decreaseAllowance are exposed.
Remediation¶
- Follow the allowance reset pattern: set allowance to
0, then set the new value. - Prefer
increaseAllowance/decreaseAllowanceor EIP-2612permitwith nonces. - For critical flows, pull tokens via
transferFromafter verifying allowance updates.
Examples¶
Vulnerable¶
pragma solidity ^0.8.0;
interface IERC20 { function approve(address,uint256) external returns (bool); }
contract DApp {
IERC20 public token;
function changeSpender(address spender, uint256 newAmount) external {
token.approve(spender, newAmount); // can be front-run
}
}
Fixed¶
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract DApp {
using SafeERC20 for IERC20;
IERC20 public token;
function changeSpender(address spender, uint256 newAmount) external {
token.safeApprove(spender, 0); // reset first; SafeERC20 handles non-standard tokens (e.g. USDT)
token.safeApprove(spender, newAmount);
}
}