Best Practices & Common Pitfalls
43.1 Best practices
Simplicity: small contracts, clear state machines, minimal privileged roles.
Use audited libraries (OpenZeppelin, Solady) instead of hand-rolled code.
Lock the compiler version for deployments; enable the optimizer consistently; read every warning.
Checks-Effects-Interactions everywhere, plus reentrancy guards around external calls.
Custom errors with useful arguments; events for every state change.
NatSpec on all public/external functions.
Test pyramid: unit → fuzz → invariant → fork → (formal verification for critical math).
≥ 95% branch coverage for critical code, but don't mistake coverage for correctness.
Static analysis in CI (Slither, Aderyn, solhint, forge fmt --check).
Admin keys in multisig + timelock, documented emergency procedures.
Upgrade discipline: storage layout checks, initializer protection, or no upgradeability at all.
Audit before mainnet, launch with caps/guarded launch, bug bounty after.
43.2 Common pitfalls
External call before state update CEI + nonReentrant tx.origin for authorization
Missing access control on mint/initialize/upgrade modifiers, _disableInitializers, _authorizeUpgrade Unchecked low-level call result silent failure, lost funds check success, bubble errors
Ignoring ERC-20 return values / non-standard tokens failed transfers counted as success SafeERC20, balance deltas Spot price as oracle Chainlink + checks, TWAP No staleness check on oracle stale prices exploited check updatedAt vs heartbeat Division before multiplication multiply first, Math.mulDiv Unsafe downcast uint128(x) DoS, out of gas pagination, pull pattern Push payments in loops one revert blocks all
abi.encodePacked with multiple dynamic args in hashes Signatures without nonce/chainId/contract EIP-712 + nonces + deadline Raw ecrecover malleability, address(0) signer
block.timestamp/prevrandao randomness Assuming address(this).balance is controlled forced Ether breaks invariants track balances internally code.length == 0 to detect EOAs false in constructors, EIP-7702 don't rely on it for security Storage layout change in upgrade append-only, ERC-7201, plugin validation delegatecall to untrusted address never; whitelist Payable multicall with msg.value non-payable multicall or track value Strings for revert reasons everywhere larger bytecode, more gas Wrong decimals assumption (6 vs 18) 10^12 mispricing read decimals(), normalize Hardcoded gas (transfer, send) breaks smart wallets call + guards
Deploying to a chain without your EVM features broken bytecode (PUSH0, TSTORE) set evmVersion per chain Private keys in repos/scripts keystores, hardware wallets Leaving console.log/test code wasted gas, unexpected behavior clean before deploy