2 min read

A pre-audit checklist for ERC20 and DeFi contracts

The checks I run before any token or DeFi contract goes to audit or mainnet: access control, reentrancy, token quirks, oracles, upgrades and launch operations.

An audit is far more useful when the obvious problems are already gone. This is the checklist I go through before a token or DeFi contract goes to an auditor or to mainnet. It won’t catch everything, but it removes the mistakes that keep showing up in post-mortems.

1. Access control

  • List every function that changes state, and write down who is allowed to call it.
  • Admin functions (mint, pause, setFee, upgradeTo, withdraw) are behind onlyOwner or a role, and the owner is a multisig, not a single hot wallet.
  • Initializers can only run once, and the implementation contract can’t be initialised by a stranger.
  • Ownership transfer is two-step, so a typo can’t hand the contract to a dead address.

2. Reentrancy and external calls

  • State changes happen before external calls (checks-effects-interactions).
  • Functions that send ETH or call untrusted tokens use a reentrancy guard.
  • Watch for read-only reentrancy: other contracts reading your state in the middle of an operation.
  • Any call return value is checked.

3. Token behaviour you didn’t expect

  • Use SafeERC20 for transfers: some tokens don’t return a boolean, others return false instead of reverting.
  • Fee-on-transfer and rebasing tokens: measure the balance difference instead of trusting the amount argument.
  • Tokens with hooks (ERC777-style) can call back into your contract.
  • Decimals differ: never assume 18.

4. Prices and oracles

  • Never use a DEX pool’s spot price as an oracle. It can be moved within a single transaction, often with a flash loan.
  • Use a robust oracle or a time-weighted price, check how fresh the data is, and handle the oracle being unavailable.

5. Math and limits

  • Solidity 0.8+ reverts on overflow, but unchecked blocks don’t. Review every one.
  • Rounding goes in the protocol’s favour, especially in share and vault math.
  • Divide last, multiply first, and test with tiny and huge amounts.
  • Loops over user-controlled arrays have a bound, or they become a gas-based denial of service.

6. Upgradeability

  • Storage layout is append-only between versions.
  • Upgrade rights live behind a multisig and, ideally, a timelock.
  • Upgrades are tested end-to-end: deploy, write state, upgrade, check the state. More on patterns in Diamond vs transparent proxy.

7. Testing

  • Unit tests for every external function, including the failure paths.
  • Fuzz tests for math and invariants (“total shares always match total assets”).
  • A fork test against mainnet state for anything that touches real DEXs or tokens.

8. Launch operations

  • Deployment is scripted and repeatable; constructor arguments are reviewed.
  • Contracts are verified on the explorer right after deployment.
  • Ownership is transferred to the multisig as part of the script, not “later”.
  • You have a pause or incident plan, and you know who can execute it.

Before you call the auditor

If you can answer “yes” to every line above, your audit time goes to the deep, protocol-specific issues instead of the basics. If several lines are a “not sure”, that’s exactly what a focused review is for.

Sources