Insights/Security

Smart Contract Security Checklist: From Day One to Launch

A practical smart contract security checklist: threat modelling, common vulnerabilities, testing, static analysis, audits, deployment and monitoring.

By METACODEBOX9 min readSecuritySmart contracts

Smart contracts run in one of the most adversarial environments in software. The code is public, anyone can call it, it often controls real value, and once deployed it’s hard to change. This smart contract security checklist follows a contract from the first design conversation to life after launch, so you can see where each safeguard fits.

Phase 1: Design and threat modelling

The cheapest vulnerability to fix is the one you designed out. Before writing code, write down:

  • Assets: what the contracts hold or control (tokens, permissions, accounting state).
  • Actors: users, administrators, keepers, other contracts, oracles, and what each is allowed to do.
  • Trust assumptions: which external contracts, price feeds and privileged roles you rely on, and what happens if each misbehaves.
  • Invariants: properties that must always hold, such as “total shares equal the sum of balances” or “a user can never withdraw more than they deposited plus earned yield”.
  • Upgradeability: whether the contracts can change after deployment, who can change them, and how users are protected from that power.

Keep the on-chain surface as small as the product allows, and prefer widely used, well-reviewed libraries (for example, OpenZeppelin’s) for standard building blocks like tokens and access control.

Phase 2: Design out the common vulnerability classes

Reentrancy

An external call can hand control to code that calls back into your contract before the first call finishes, while state is half-updated. Follow checks-effects-interactions (validate, update state, then make external calls), use reentrancy guards where appropriate, and remember the subtler forms: cross-function reentrancy and read-only reentrancy, where another protocol reads your state mid-update.

Access control

Missing or wrong permission checks remain one of the most damaging bug classes. Check every state-changing function: who is allowed to call it? Protect initialisers so they can only run once. Never use tx.origin for authorisation. Keep privileged roles few, documented and held by multisig wallets rather than single keys.

Oracle and price manipulation

A spot price read from an automated market maker can be moved within a single transaction, often with flash-loaned capital. Use robust oracle designs (time-weighted prices or decentralised oracle networks), check for stale or out-of-range values, and ask what an attacker could do if they controlled the price for one block.

Unchecked external calls and token quirks

Low-level calls return a success flag that’s easy to ignore. Check it. Not every ERC-20 token behaves the same: some don’t return a boolean, some charge fees on transfer, some can be paused or rebased. Use safe wrappers (such as SafeERC20) and don’t assume the amount received equals the amount sent.

Arithmetic, rounding and precision

Solidity 0.8 and later revert on overflow by default, but unchecked blocks, casts between types and fixed-point maths still need care. Decide the rounding direction deliberately (generally in the protocol’s favour), multiply before dividing to avoid precision loss, and watch for share-inflation attacks on vaults, where the first depositor manipulates the exchange rate.

Upgradeability risks

Proxy patterns add their own failure modes: storage layout collisions between versions, uninitialised implementation contracts, and the concentration of power in whoever can upgrade. Use established proxy implementations, validate storage layouts on every upgrade, and put upgrades behind a multisig and a timelock so users can see changes coming.

Also on the list

  • Front-running and MEV: assume pending transactions are visible; use slippage limits and commit-reveal where needed.
  • Signature replay: include nonces, deadlines and the chain ID (EIP-712 domains help).
  • Denial of service: avoid unbounded loops over user-controlled arrays, and avoid designs where one failing recipient blocks everyone.
  • Randomness: block values are not a secure source of randomness; use a verifiable source such as Chainlink VRF.

Phase 3: Testing every path

  • Unit tests for every function, including every revert path and every permission check.
  • Fuzz tests that throw large numbers of generated inputs at your functions (Foundry and Echidna are common tools).
  • Invariant tests that run random sequences of actions and assert your invariants still hold afterwards.
  • Fork tests against real network state when you integrate with external protocols or tokens.
  • Coverage as a signal of what’s untested, not as a target. High coverage with weak assertions proves little.

Phase 4: Static analysis and code review

Static analysis tools catch whole categories of mistakes automatically. Slither is fast enough to run on every commit; symbolic-execution tools like Mythril explore deeper paths. Triage every finding and record why ignored ones are safe.

Then get a human review from engineers who didn’t write the code, against the threat model and the specification, not just line by line. Many serious bugs are logic errors that look perfectly reasonable in isolation.

Phase 5: Independent audit

For contracts that will hold meaningful value, an independent audit is a valuable second line of defence. To get the most from one:

  • Freeze the scope and the code; audits of moving targets are far less useful.
  • Provide documentation, the threat model, invariants and a passing test suite.
  • Budget time to fix findings, and have the fixes re-reviewed.
  • Consider complementary layers, such as a second audit, a competitive audit or a bug bounty, for high-value systems.
An audit lowers risk. It doesn’t remove it.

An audit is a time-boxed review of a specific version of the code. It reduces the chance of serious bugs; it can’t guarantee there are none, and it doesn’t cover code changed afterwards or the systems around the contract.

Phase 6: Deployment and life after launch

  • Scripted, reproducible deployments, rehearsed on a testnet or fork first.
  • Verified source code on the block explorer so users can read what they interact with.
  • Admin roles on multisig wallets, with timelocks on sensitive actions and no keys left with deployer accounts.
  • Monitoring and alerting for unusual activity, large movements and privileged actions.
  • An incident-response plan: who can pause what, how decisions are made, and how users are told.
  • A bug bounty, so people who find issues have a reason to report them responsibly.

Prioritising when time is short

Every item on this list adds safety, and every team has limits. When you have to prioritise, start with the work that removes the most risk per hour:

  1. Shrink the scope. Every function you don’t ship is one you don’t have to secure. Defer features that aren’t needed at launch.
  2. Write the threat model and invariants. They take hours, not weeks, and they make every later step, from testing to audit, sharper.
  3. Test the money paths exhaustively. Deposits, withdrawals, liquidations and anything that moves value deserve fuzz and invariant tests before anything cosmetic gets attention.
  4. Lock down privileged access. Multisig wallets and timelocks on admin functions protect against the single-key compromise that no amount of code review can prevent.
  5. Limit exposure at launch. Deposit caps, gradual rollouts and the ability to pause new activity reduce the damage an undiscovered bug can do while confidence builds.

What you shouldn’t trade away is review of anything that holds user funds. If the timeline can’t accommodate it, the timeline is the thing to change.

The checklist

Security is built into how we approach smart contract development and DeFi development. Read about the three things to get right before you build a Web3 product, see our Web3 work, or talk to us about your contracts.

MCB

Written by

METACODEBOX

A product engineering studio for blockchain, AI and high-performance web products — from smart contracts to the interface your users touch.