Smart Contract Audit Checklist: 25 Things I Check Before Mainnet
SoliditySecurityAudit

Smart Contract Audit Checklist: 25 Things I Check Before Mainnet

A practical 25-point checklist I run through on every Solidity contract before mainnet deployment — covering reentrancy, access control, gas, oracles, upgradeability, and the boring stuff that prevents 80% of incidents.

HJ
Hassan Javed
February 2026
13 min read

Why a checklist

I've shipped over 30 Solidity contracts to mainnet across Ethereum, BNB Chain, Polygon, and a few L2s. None of them have been exploited. That's not luck — it's a checklist.

This isn't a substitute for a real third-party audit. For anything holding meaningful TVL, hire a firm (Trail of Bits, OpenZeppelin, Code4rena contests, etc.). What this checklist does is catch the 80% of issues that even pre-audit you should have already eliminated, so the auditors can focus on the harder 20%.

I run through this on every project before deployment. Sometimes I find something. Sometimes I don't. Either way, it's faster than the alternative.

The checklist

Access control (1-5)

1. Every privileged function has explicit access control. Every onlyOwner, onlyAdmin, onlyRole modifier is present and tested with a non-privileged caller in the test suite.

2. Owner is renounced or set to a multisig before mainnet. A single EOA owner on a contract holding $1M+ TVL is a single point of failure. Use Gnosis Safe (now Safe{Wallet}) with at least 2-of-3 signers.

3. No tx.origin in access control. Always msg.sender. tx.origin is exploitable via phishing contracts.

4. Role-based access uses OpenZeppelin AccessControl, not custom mappings. Custom role logic is where the foot-guns hide.

5. Admin transfer is two-step, not one-step. Use Ownable2Step or equivalent. Single-step admin transfers have caused multiple incidents (one fat-finger and the contract is bricked).

Reentrancy and state machines (6-9)

6. Every external call to an unknown contract is followed by no state changes. Checks-Effects-Interactions, always. Or use OpenZeppelin's ReentrancyGuard.

7. Reentrancy guard is on every public payable function and every function that calls external contracts. Not just the obvious ones.

8. State machine transitions are guarded. A function that should only run once has an explicit flag and require(!alreadyRan) check.

9. No assumption that external contracts are honest. Treat every external call as adversarial. ERC-20 transfers can revert, return false, or behave non-standardly (USDT doesn't return bool). Use SafeERC20.

Arithmetic and rounding (10-12)

10. Solidity 0.8+ for built-in overflow protection. If you're on 0.7 or below, use SafeMath everywhere.

11. Division before multiplication is forbidden. (a / b) * c rounds to zero for small a. Always (a * c) / b.

12. Rounding direction is explicit. When dividing, decide whether to round up or down based on what favors the protocol vs the user. Implicit rounding has cost protocols millions in invariant breaks.

Token-specific (13-16)

13. ERC-20 returns are checked. Use SafeERC20.safeTransfer and safeTransferFrom, not raw transfer.

14. Fee-on-transfer tokens handled if relevant. If your contract deals with arbitrary ERC-20s, expect some to take fees on transfer. Compute received amount via balance diff, not transferred amount.

15. Approve race is mitigated. Use safeIncreaseAllowance / safeDecreaseAllowance instead of raw approve to avoid the front-running attack.

16. Rebasing tokens not used as accounting tokens. AAVE-style aTokens, Lido stETH, etc., rebase. If your contract assumes balance is constant between writes, rebasing breaks invariants.

Oracles and external data (17-19)

17. No single-block price feeds. Always TWAP (time-weighted) over at least 30 minutes for spot prices. Single-block prices are flash-loan exploitable.

18. Chainlink feeds checked for staleness. Read updatedAt from the latest round and revert if older than the heartbeat interval.

19. Oracle failure has a fallback. If the primary oracle reverts, the contract either pauses (preferred) or falls back to a secondary oracle. Don't return zero or stale data.

Upgradeability (20-21)

20. Storage layout is preserved across upgrades. No reordering, deleting, or changing types of storage variables. Use OpenZeppelin's upgrade plugin to verify.

21. Initializer can only run once. Use initializer modifier. Confirm the implementation contract itself has its initializer disabled in the constructor.

Gas and DoS (22-23)

22. Unbounded loops are forbidden. No iterating over an array that users can grow indefinitely. If you must, paginate or batch.

23. External calls in loops are gas bombs. Each iteration adds 2300+ gas. A loop with 100 token transfers can OOG (out-of-gas).

Operational (24-25)

24. Verified source on Etherscan / equivalent. Always. Unverified contracts erode user trust and make audits harder.

25. Emergency pause is wired to a multisig. Even if you don't expect to use it, Pausable with a multisig admin is a 1-day-of-work safety net that has saved several protocols I work with.

The "after deployment" checklist

This checklist is for pre-deployment. There's a separate one for post-deployment that includes:

Verified source on the explorer
Etherscan tag with project name
Read-only frontend before any state-changing UI
24-hour quiet period before linking from main app
Monitoring (Tenderly alerts, Forta agents)

But that's a separate post.

How this fits into a project

When I take on a Web3 engagement at Hassan Javed, the audit checklist is part of my deliverable. Clients get the contract code, the test suite (>90% coverage), the deployment scripts, and a signed-off audit checklist showing each of these 25 items either ✅ done or 🟡 with a clear reason for skipping.

If you're a Pakistani team or international client looking for production-grade Solidity work, contact me — happy to walk through how this checklist applies to your specific project.

TL;DR for engineers

Print this list, run through it twice on every contract, and you'll catch most pre-audit issues yourself. Your auditor will thank you (and probably charge you less). Your users will benefit from contracts that don't get exploited.

Security is a checklist sport. Don't try to remember everything — just don't skip the list.

Related Reads

You might also like