VRF7
HomeGuides › How to Audit an ERC-20 Token Contract

How to Audit an ERC-20 Token Contract

Updated 2026-06-18 · VRF7 Security Guides

Auditing an ERC-20 token contract is not a single check — it is a structured process that moves from interface compliance, through economic controls, to centralization risks and known vulnerability classes. This guide walks you through each layer in the order a reviewer should approach it, with concrete code examples and the tools best suited to each task.

Start With ERC-20 Standard Compliance

Before evaluating security, confirm that the contract actually implements ERC-20 correctly. The standard is defined in EIP-20 and requires nine functions and two events. Missing or misimplemented members break composability with DEXs, bridges, and wallets in ways that may also create exploitable edge cases.

A common non-compliance pattern is a transfer that emits no event on zero-value transfers, or one that silently returns false instead of reverting. Integrating contracts that check the return value will mis-account balances. Slither's erc20-interface detector flags these deviations automatically.

Mint and Burn Controls

Unlimited or inadequately gated minting is the most direct path to value destruction in an ERC-20. Audit every code path that increases totalSupply.

Questions to answer

Vulnerable pattern

// No cap, callable by any address that has been granted MINTER_ROLE
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
    _mint(to, amount);
}

// Role can be granted by DEFAULT_ADMIN_ROLE — a single EOA at deployment
function grantRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
    super.grantRole(role, account);
}

Safer pattern

uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10 ** 18;

function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
    require(totalSupply() + amount <= MAX_SUPPLY, "Cap exceeded");
    _mint(to, amount);
}

The cap must be enforced inside _mint or the calling function — not just documented. OpenZeppelin's ERC20Capped extension handles this correctly if you do not override _mint downstream.

Fee-on-Transfer Pitfalls

Fee-on-transfer tokens deduct a percentage from every transfer, meaning the recipient receives less than the amount argument passed to transfer. This is a deliberate design choice in many tokenomics models, but it introduces a class of integration bugs that can drain funds from AMM pools, lending protocols, and bridges.

What to check in the contract itself

// Dangerous: owner can change fee to any value at will
function setTransferFee(uint256 newFee) external onlyOwner {
    transferFee = newFee; // no upper-bound check
}

// Safer: cap and timelock
function setTransferFee(uint256 newFee) external onlyOwner {
    require(newFee <= 500, "Max 5%"); // 500 basis points
    transferFee = newFee;
}

If you are auditing a protocol that integrates a fee-on-transfer token rather than the token itself, ensure the protocol accounts for the received amount rather than the sent amount. Failing to do so is a well-documented class of AMM and vault exploits.

Blacklist and Pause: Centralization Risks

Many ERC-20 tokens include address blacklisting (blocking specific addresses from sending or receiving) and pause functionality (halting all transfers). These features are legitimate compliance tools in some contexts — stablecoins like USDC use them — but they introduce significant centralization risk that should be disclosed and scoped tightly.

Red flags

Slither's centralization-risk and Aderyn's privilege escalation detectors both surface these patterns. When you run an automated scan against a token contract, cross-referencing findings from multiple tools makes it far easier to distinguish intentional admin controls from accidental privilege overreach.

For a broader checklist of centralization and ownership issues before a token goes live, see the Is My Token Safe? A Pre-Launch Security Checklist.

The Approval Race Condition (ERC-20 Allowance Attack)

The original ERC-20 approve function has a well-known race condition. If Alice approves Bob for 100 tokens, then changes the approval to 50 tokens, Bob can observe the pending transaction and front-run it: spending the original 100 tokens before the new approval confirms, then spending the 50 tokens after — netting 150 total.

Mitigations to look for

// Vulnerable: direct re-approval
function approve(address spender, uint256 amount) public returns (bool) {
    _approve(msg.sender, spender, amount);
    return true;
}

// Safer: require zero-reset first (friction mitigation)
function approve(address spender, uint256 amount) public returns (bool) {
    require(
        amount == 0 || allowance(msg.sender, spender) == 0,
        "Reset to zero first"
    );
    _approve(msg.sender, spender, amount);
    return true;
}

Integer Arithmetic and Overflow Checks

Since Solidity 0.8.0, arithmetic overflow and underflow revert by default, which eliminates the classic balance-wrap attacks that plagued earlier tokens. However, there are still arithmetic issues to watch for:

For a deeper treatment of this vulnerability class, see Integer Overflow and Underflow in Smart Contracts.

Tooling: What to Run and Why

No single tool covers the entire attack surface of an ERC-20 contract. A useful audit pipeline combines static analysis, linting, symbolic execution, and fuzzing.

Recommended tool stack

VRF7 runs all of these tools in parallel against a submitted contract and maps each finding to the originating tool, giving you a consolidated report without needing to configure each tool individually. That said, automated tooling — including this pipeline — surfaces deterministic and pattern-matched issues. It does not replace the judgment a human auditor applies to business logic, tokenomics incentive design, or novel attack vectors. For guidance on where automated scanning ends and manual review begins, see Automated Scanners vs Manual Audits: What Is the Difference?.

Checklist Summary

  1. Verify all nine ERC-20 functions and two events are correctly implemented and emit expected data.
  2. Identify every code path that calls _mint; confirm access controls, role management, and hard supply caps.
  3. Review fee-on-transfer logic: rate changeability, exemption lists, rounding behavior.
  4. Assess blacklist and pause controls: who holds the keys, whether a timelock exists, and whether the features can be weaponized.
  5. Check for approval race conditions; prefer EIP-2612 permit for modern deployments.
  6. Audit all unchecked blocks and verify fee division does not accumulate material rounding errors.
  7. Run the tool stack above; treat automated findings as a starting point for manual review, not a final verdict.

Scan your contract before you ship

Run an automated, transparent security scan — seven industry tools in parallel, every finding labeled with its source tool. It is not a substitute for a full manual audit, but it is a fast first line of defense.

Scan a contract

Frequently asked questions

What is the most dangerous vulnerability in a typical ERC-20 token contract?

Unrestricted or poorly gated minting is the most directly destructive vulnerability because it allows an attacker or malicious insider to inflate supply and drain value from all existing holders. Close behind it are owner-controlled fee rates that can be set to 100% — effectively a honeypot that prevents any holder from selling. Both issues require reviewing every privilege escalation path in the contract, not just the obvious mint function.

Does a fee-on-transfer token violate the ERC-20 standard?

Technically yes: EIP-20 states that the recipient must receive exactly the amount specified in the transfer call. Fee-on-transfer tokens violate this guarantee, which is why integrating protocols must explicitly handle the discrepancy by measuring the balance change rather than trusting the amount argument. The token itself may function correctly within its own ecosystem, but the non-standard behavior must be clearly documented and accounted for by any protocol that accepts the token.

Can automated tools fully audit an ERC-20 token contract?

No. Automated tools — including static analyzers like Slither and Aderyn, symbolic execution tools like Mythril, and fuzzers like Echidna — are effective at detecting known vulnerability patterns, interface violations, and arithmetic edge cases. They cannot evaluate whether the tokenomics design is sound, whether access controls make sense for the project's trust model, or whether a novel business logic combination creates an unexpected exploit path. Automated scanning is a necessary first step, not a complete substitute for manual review.

What is the ERC-20 approval race condition and how serious is it?

The approval race condition allows a spender to observe a pending approval change transaction in the mempool and front-run it, spending the old allowance before the new one is confirmed and then spending the new allowance afterward — potentially extracting far more than the token owner intended. In practice the risk is highest when a user is changing a large allowance for a contract they do not fully trust. The cleanest mitigation is EIP-2612 permit, which combines approval and action in a single atomic transaction signed off-chain, eliminating the window for front-running.

How do blacklist and pause features create centralization risk in ERC-20 tokens?

Blacklist and pause mechanisms give a privileged address the power to freeze any wallet or halt all transfers indefinitely. If that privileged address is a single externally owned account rather than a multisig with a timelock, the entire token economy depends on that one key not being lost, stolen, or misused. Attackers who compromise the owner key can blacklist the largest holders or the Uniswap liquidity pool before draining value. Auditors should confirm these roles are held by a well-secured multisig and that any pause has a bounded duration enforced on-chain.