Understanding Approval Vulnerabilities in Ethereum Tokens
Approvals are a cornerstone of token functionality on Ethereum, enabling users to grant third-party addresses—be it externally owned accounts (EOAs) or smart contracts—the ability to move funds on their behalf. This feature has revolutionized systems like NFT marketplaces and DeFi applications, allowing for seamless fund transfers when trades are executed or other conditions are met. However, the convenience of approvals comes with significant risks, such as unlimited approvals and frontrunning attacks.
The Danger of Approval Frontrunning
Approval frontrunning is another critical vulnerability in the approval system. When a user submits an approval transaction followed by another transaction to adjust or revoke that approval, they unintentionally create a window for attackers to exploit. Here’s a detailed scenario:
- Initial Approval: Alice approves a decentralized exchange (DEX) to spend 10 000 of her ERC-20 tokens.
- New Approval: Later, Alice decides to adjust her approval to 1 000 tokens and sends another approval transaction.
- Attacker's Opportunity: Before the second transaction is mined, the DEX (or a malicious actor controlling the DEX's address) sees the pending approval in the mempool.
- Exploit Execution: The DEX (or malicious actor) quickly submits a
transferFrom
transaction to withdraw the initially approved 10 000 tokens from Alice’s wallet before the new approval is mined. - Resulting Loss: If the DEX's (or malicious actor's) transaction is mined before Alice’s new approval, the malicious actor can transfer the initially approved 10 000 tokens. Then, when Alice's new approval is mined, the DEX has the authorization to transfer an additional 1 000 tokens, resulting in a total unauthorized transfer of 11 000 tokens.
This vulnerability arises because the ERC-20 token's internal _approve
function directly sets the spender’s allowance without considering pending transactions.
function _authorize(address owner, address spender, uint256 amount, bool logEvent) internal virtual {
if (owner == address(0)) {
revert InvalidAuthorizer(address(0));
}
if (spender == address(0)) {
revert InvalidSpender(address(0));
}
_allowances[owner][spender] = amount;
if (logEvent) {
emit Authorization(owner, spender, amount);
}
}
In contrast, the _utilizeAllowance
function only checks the current allowance against the transfer amount specified in the transferFrom
call, lacking a mechanism to account for prior transfers.
function _utilizeAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < amount) {
revert InsufficientAllowance(spender, currentAllowance, amount);
}
unchecked {
_authorize(owner, spender, currentAllowance - amount, false);
}
}
}
Best Practices for Secure Approvals
To counteract frontrunning, developers should rethink how allowances are managed. Instead of directly setting new values with approve
calls, use incremental functions like safeIncreaseAllowance
and safeDecreaseAllowance
from OpenZeppelin’s SafeERC20 library. These functions adjust the current allowance by a specified amount, reducing the risk window.
Another significant risk, albeit indirectly related to smart contracts, is "approval phishing." This occurs when attackers manipulate front-end code to reroute approvals to their addresses instead of the intended contract addresses.
Key Security Measures
- Limit Allowances: Only approve the necessary amount for immediate transactions. This minimizes potential losses if an approval is exploited.
- Two-Step Transfers and Locking Periods: Implement mechanisms that require a two-step process or temporary locks on approvals, adding an extra layer of security.
- Regular Review of Approvals: Users should periodically review and revoke outdated or unnecessary approvals. Tools like revoke.cash can help manage approvals efficiently.
By adopting these best practices, developers can mitigate the risks associated with token approvals, ensuring a safer and more secure blockchain ecosystem for users.
Conclusion
Approvals are essential for the functionality of many Ethereum-based applications, but they come with inherent risks. Understanding and addressing vulnerabilities such as unlimited approvals and frontrunning attacks is crucial for maintaining security. By implementing thoughtful security measures and encouraging users to manage their approvals proactively, we can significantly enhance the safety and reliability of decentralized applications.
Additional Resources
For more information on managing approval vulnerabilities and best practices in smart contract security, check out the following resources:
- Solidity Official Documentation - ERC-20 Standard: Learn more about the ERC-20 token standard, including key concepts and code structure.
- OpenZeppelin SafeERC20 Library: The OpenZeppelin library provides safe functions like
safeIncreaseAllowance
andsafeDecreaseAllowance
, which help reduce risks associated with directapprove
calls. - revoke.cash: A user-friendly tool for viewing and managing all approvals to reduce the likelihood of unauthorized token transfers.
- Ethereum Smart Contract Best Practices: An in-depth guide covering security patterns, design principles, and best practices for smart contract development.
- Trail of Bits Blog: This blog by security experts at Trail of Bits provides insights into blockchain security, common vulnerabilities, and prevention techniques.
- Etherscan Token Approvals: A feature on Etherscan for checking and managing token approvals across Ethereum accounts.
These resources are valuable for developers aiming to understand and address potential risks in Ethereum token approvals and enhance smart contract security.