Smart Contract Security: The Checklist We Run Before Mainnet
Fix the cheap classes of bug before the audit
Auditor time is expensive and should be spent on logic and economics, not on findings a linter catches. Before submitting code, run static analysis, resolve every compiler warning, enforce the checks-effects-interactions ordering on anything that makes external calls, and ensure every external call's return value is handled. Arriving at an audit with a clean static analysis report typically produces a much more useful audit, because the reviewers get straight to the parts that require human judgement.
Access control is where real losses come from
Reentrancy gets the headlines; misconfigured privileges cause more actual losses. Enumerate every privileged function and write down who can call it, how that role is assigned, how it is revoked, and what happens if the key is compromised. Single-key ownership of an upgrade function on a contract holding user funds is not an acceptable production posture — use a multi-signature wallet with a timelock, and publish the addresses. Test the revocation path, because nobody ever does until they need it.
Upgradeability is a trade-off, not a feature
Proxy patterns let you fix bugs and also let a compromised admin key replace your logic entirely. If you use them, be explicit: which contracts are upgradeable, who controls upgrades, what timelock protects users, and how storage layout changes are validated between versions. Storage collisions during upgrade are a classic and devastating failure. If a contract genuinely never needs to change, deploying it immutable removes a whole risk category and is worth considering more often than it is.
Oracles and price manipulation
Any contract that reads an external price needs to survive that price being manipulated within a single transaction. Spot prices from an on-chain pool are manipulable with borrowed capital. Use time-weighted averages or established oracle networks, sanity-check values against bounds, and define what the contract does when the oracle is stale or unavailable — reverting is usually safer than proceeding on last-known data. Test these paths explicitly; they are rarely exercised in normal operation.
Test the way an attacker would
Unit tests confirm the code does what you meant. Fuzzing and invariant testing look for inputs you did not imagine, and they routinely find edge cases that human review misses — total supply invariants, accounting that must never go negative, balances that must sum correctly. Fork the mainnet state and run your test suite against real conditions and real token behaviour, including tokens with transfer fees or unusual decimals, which break naive integrations constantly.
Plan the incident response before launch
Decide in advance who can pause the system, how quickly they can be reached, how users will be told, and what the recovery process looks like. Publish a security contact and consider a bug bounty from day one — researchers will look at your code whether or not you invite them, and a bounty gives them a reason to report rather than exploit. Monitoring for anomalous activity on your own contracts should exist before there is anything to steal.
Have a project that needs this kind of thinking?
Let's talk through what you're building — free consultation, no commitment.
Get in Touch