hazehaze

The smart contract pre-launch checklist

· 4 min read · by haze

Most contract losses are not exotic. They are a missing modifier, a rounding direction, a signature that can be used twice, or an owner key sitting on a laptop. This is the list i run before anything i write goes to mainnet. It is not a replacement for an audit, but it catches the mistakes that are cheap to fix today and very expensive next week.

Access control

  • List every external and public function that changes state. For each one, write down who is allowed to call it. If the answer is "anyone", make sure that is on purpose.
  • Check initializers. An upgradeable contract whose initialize can be called by anyone, or called twice, is an open door. Use initializer / reinitializer and call _disableInitializers() in the implementation's constructor.
  • Two-step ownership transfers (Ownable2Step) stop a typo from handing the contract to an address nobody controls.
  • Admin roles should end up on a multisig before launch, not on the deployer key.

External calls and reentrancy

  • Follow checks, effects, interactions: update your own state before you send tokens or ETH, or call anything you don't control.
  • Remember the hidden callbacks: ERC-721 safeTransferFrom, ERC-1155 transfers and ERC-777 style hooks all call back into the receiver.
  • Add nonReentrant where state is shared across functions. Read-only reentrancy matters too if other protocols read your view functions mid-call.
  • Use low-level call for sending ETH, check the return value, and never assume a fixed gas stipend is enough.

Math, rounding and precision

  • Decide the rounding direction for every division and make it favor the protocol: round down what users receive, round up what they owe.
  • Multiply before you divide. Watch for decimals mismatches between tokens (6 vs 18).
  • Vault share math is the classic case: an empty ERC-4626 vault can be inflated by the first depositor. Use virtual shares and assets, or seed the vault at deploy.
  • unchecked blocks are fine for gas, but only where you can prove the value cannot overflow.

Prices and oracles

  • Never price anything from a spot pool balance in the same transaction; flash loans move it for free.
  • With Chainlink-style feeds, check updatedAt for staleness, check the answer is positive, and know the feed's decimals.
  • Have a plan for when the oracle stops: pause, fall back, or refuse to act. Not "keep going with the last price".

Signatures

  • Every signed message needs a nonce, a deadline and the chain id. EIP-712 with a proper domain separator gives you the last two for free.
  • Use a vetted ECDSA library: it rejects malleable signatures and the zero address that ecrecover returns on bad input.
  • If smart wallets will sign, support ERC-1271 (isValidSignature) instead of assuming every signer is an EOA.

Tokens you don't control

  • Some tokens take a fee on transfer, some rebase, some return no boolean, some block certain addresses. Use SafeERC20 and measure balances before and after instead of trusting the amount argument.
  • If your contract accepts any token, decide which of these you support and write it down.

Tests that earn their keep

  • Unit tests for every function and every revert path.
  • Fuzz tests for anything with math. Foundry fuzzes any test function that takes arguments.
  • Invariant tests for the things that must always be true: total shares match total assets, the sum of balances equals supply, nobody can withdraw more than they put in.
  • Fork tests against mainnet state if you integrate with other protocols. Their behavior is the one that ships, not your mock's.
forge test --fuzz-runs 10000
forge coverage --report summary
forge snapshot

Gas, after it is correct

  • Pack storage: variables that are read together should share a slot.
  • Cache storage reads in memory inside loops, and bound every loop that a user can grow.
  • Keep a forge snapshot in the repo so every change shows its gas cost in review.

Deploy day

  • Script the deploy (forge script) and run it on a fork first, with the exact constructor arguments.
  • Verify the source on the explorer in the same run.
  • Move ownership and roles to the multisig, then check on-chain that the deployer has no powers left.
  • Pause and upgrade switches: know who can flip them, how fast, and write the runbook before you need it.
  • Start with conservative limits (caps, rate limits) and raise them once real usage looks normal.

The short version

If you only have an hour: list who can call what, check every external call for reentrancy, check every division for rounding, check signatures for replay, and make sure the keys that control the contract are not on one laptop.

If you want a second pair of eyes on all of this, the 48-hour pre-launch check is exactly this list run on your code, with tests and fixes added to your repo.

more notes