Key Principle
On an immutable, adversarial platform, every rule below exists because a deployed bug is permanent, irreversible loss. "Complexity is the enemy of security" (Chapter 9): write less code, reuse audited code, and fail safe.
Why This Matters
These are the one-line imperatives the book repeats across every chapter. Each compresses a costly real-world failure into a check you can apply before deploying. Where dating matters it is flagged inline; the heuristics survive even where specific tooling moved on (Solidity 0.8+ checked math, EIP-1559 fees, EIP-6780 SELFDESTRUCT, The Merge).
Security
- Authenticate with
msg.sender, nevertx.origin—tx.originlets a malign intermediary contract inherit the victim's authority. (Chapter 9/13) - Apply checks-effects-interactions: make external calls the last operation, after every state change — the structural fix for reentrancy (the DAO). (Chapter 9)
- Never send ether before updating your own state, or the recipient's fallback can re-enter and drain against a stale check; add a mutex if needed. (Chapter 9)
- Route arithmetic through SafeMath pre-0.8 — EVM integers wrap silently with no revert: "Trying to store 256 into a uint8 will result in 0." (Chapter 9)
- Never derive randomness from block variables (blockhash, timestamp, number) — Ethereum is deterministic with no entropy and miners can bias them; use commit–reveal, RANDAO, or a randomness oracle. (Chapter 9)
- Never base logic on
this.balance— an attacker can force ether in viaselfdestruct; track deposits in your ownpayable-incremented variable. (Chapter 9) - Maximize reuse of trusted code and "Don't roll your own crypto." (Chapter 9)
- Minimize complexity — "Every single line of code you add expands the attack surface." (Chapter 9/10)
- Always declare explicit visibility on every function; never rely on the
publicdefault. (Chapter 9) - Never mark a variable
privateto hide a secret — all storage is public; visibility only controls callability. (Chapter 7) - Add a time-delay safeguard on high-value withdrawals — the DAO's 28-day delay was the only window any response could be built in. (Appendix A)
Keys & Wallets
- Generate keys only from a CSPRNG with sufficient entropy; never write your own RNG. (Chapter 4)
- Build any new wallet as HD + mnemonic — BIP-39 (words), BIP-32 (tree), BIP-44 — and avoid nondeterministic "JBOK" wallets. (Chapter 5)
- Treat the mnemonic as the wallet: write it on paper, store it securely, "Never store it electronically." (Chapter 5)
- Always harden the level-1 children of master keys — a leaked child key + parent chain code "can be used to deduce the parent private key." (Chapter 5)
- Deploy only the
xpubto online servers to generate receive addresses with zero spending capability; keep thexprvoffline. (Chapter 5) - Plan a recovery path for any BIP-39 passphrase and never store it beside the seed — "there are no 'wrong' passphrases." (Chapter 5)
- Validate addresses at the UI with EIP-55 checksums — raw Ethereum addresses carry no built-in checksum and a typo burns funds. (Chapter 4)
- Never use real-money keys for testing. (Chapter 10)
Transactions & Gas
- Always check the return value of
send()and low-levelcall()— both returnfalseon error instead of reverting. (Chapter 7/9) - Prefer
transfer()oversend()/call()—transferreverts on failure (fails safe). (Chapter 7/9)[DATED 2018: the 2300-gas stipend breaks with proxy/multisig recipients; modern practice preferscall+ reentrancy guard.] - Always over-provision
gasLimitfor contract calls — gas "can be estimated but cannot be determined with accuracy." EOA-to-EOA transfer is fixed at 21,000 gas. (Chapter 7) - Minimize computation and bound loops/external calls — gas spent before an out-of-gas exception is charged and not refunded though state reverts. (Chapter 7/13)
- Never submit a transaction whose own gas requirement exceeds the block gas limit — it can never be mined. (Chapter 13)
- Use unit literals (
0.1 ether,wei,1 weeks) instead of raw numeric literals. (Chapter 7) - Always specify
toexplicitly; the protocol does no recipient validation, so a wrong address burns the ether. (Chapter 6) - Sign offline: separate signing from transmission so unlocked keys never sit on an internet-connected box. (Chapter 6)
- Add EIP-155 replay protection (chain ID in the signed hash) so a tx can't be replayed on a fork/testnet. (Chapter 6 / Appendix B)
Contract Design (Solidity/Vyper)
- Favor the withdrawal (pull) pattern over push payments — one failed transfer then corrupts only that user's interaction. (Chapter 9)
- Never use an unbounded loop over a user-growable structure — gas can exceed the block gas limit and brick the function permanently. (Chapter 9)
- Use
require()to gate inputs andassert()to test invariants; userevert(with a message), not the obsoletethrow. (Chapter 7) - Mark functions intended to receive ether
payable; non-payable functions reject incoming payments. (Chapter 7) - Use the nameless
constructorkeyword — a function named after the contract that's mistyped silently becomes a public, seizable method (Rubixi). (Chapter 7/9) - Build stateless libraries with the
librarykeyword to remove persistent storage and forbidselfdestruct(the Parity bricking). (Chapter 9) - Treat
DELEGATECALLas a footgun — the callee runs against your storage slots by position, not variable name. (Chapter 9/13) - Multiply before you divide; do internal math in high-precision uint256 — integer division truncates silently. (Chapter 9)
- Write access-control once as a function
modifierand apply it consistently. (Chapter 7) - Pin the compiler version with a
pragmato guard against silent mis-compilation. (Chapter 7) - Integration-test composed contracts, not just each inherited unit. (Appendix C/D)
- Reconcile immutability with maintainability via the upgradeable proxy pattern. (Appendix C/D)
Tokens
- Follow an existing standard — ERC-20 (fungible), ERC-721 (non-fungible) — so every wallet works "without any wallet upgrade or effort on your part." (Chapter 10 / Appendix B)
- Use audited implementations (OpenZeppelin) and compose crowdsales from audited mixins (
Refundable,Capped,Timed,Minted). (Chapter 10 / Appendix C/D) - Never reduce an existing
approveallowance in one call — it is replaced, not diffed, and is front-runnable; set to 0 first or useincreaseApproval/decreaseApproval. (Chapter 9) - Never
transfertokens to a contract with no token-receiving logic — "The MET sent to Faucet is stuck, forever"; pull viaapprove+transferFrom. (Chapter 10) - Reject ether in a token-handling contract with a reverting fallback. (Chapter 10)
- Remember ERC-20
decimalschanges display only, not storage — a mismatch silently shifts the order of magnitude. (Chapter 10) - Adopt a token only if the application genuinely needs it — never to disguise a securities offering: "If it walks like a duck and quacks like a duck, it's a duck." (Chapter 10)
Oracles & DApps
- Never trust on-chain external data just because all nodes agree on it — "Such data simply cannot be trusted... we have just deferred the problem." (Chapter 11)
- Design every oracle to minimize imported trust; every authentication scheme merely relocates trust. (Chapter 11)
- Prefer decentralized oracles over a single trusted source — a single oracle is a single point of failure. (Chapter 11)
- Always authenticate the oracle callback:
require(msg.sender == oraclize_cbAddress()). (Chapter 11) - Choose the oracle pattern by data size/frequency/gas: immediate-read, publish–subscribe, or request–response. (Chapter 11)
- Make a DApp as decentralized as its least-decentralized component — never serve "unstoppable" contracts behind a takedownable host. (Chapter 12)
- Push bulky data off-chain to content-addressable storage (IPFS/Swarm) and attach an ENS
contentrecord. (Chapter 12) - For a true DApp, ship no privileged/specialized accounts. (Chapter 12)
- Operate ENS on fixed 32-byte namehash nodes, never raw strings; normalize names with UTS #46. (Chapter 11)
Architecture & Consensus
- Keep EVM execution totally deterministic — randomness and external data enter only via signed-transaction payloads. (Chapter 11)
- Audit before you deploy — deployment is a one-way door: "in smart contracts, bugs literally cost money." (Chapter 7)
- Reuse Bitcoin's battle-tested primitives (secp256k1, libsecp256k1) — don't reinvent crypto. (Chapter 4)
- Keep
SSTORE/storage writes minimal — persistent storage is the dominant gas cost because every node keeps it forever. (Chapter 13) - Rely on gas, not code inspection, to guarantee halting — the EVM is quasi-Turing-complete, capped by available gas. (Chapter 13)
- Treat multisig as application-layer (a wallet contract) and as high-stakes code. (Chapter 6)
- Never fork without whole-community consensus; distrust low-turnout, whale-weighted "community consensus." (Appendix A/B)
- Interrogate any "better consensus" claim with five questions — who can change the past, the future, at what cost, how decentralized, who will know: "no algorithm can optimize across all dimensions of the problem of decentralized consensus." (Chapter 14)
Key Quotes
"Complexity is the enemy of security. Every single line of code you add expands the attack surface of your contract and could represent a vulnerability lying in wait." — Antonopoulos & Wood, Chapter 9
"It is good practice for any code that performs external calls to unknown addresses to be the last operation... known as the checks-effects-interactions pattern." — Antonopoulos & Wood, Chapter 9
"If you lose your private keys, you lose access to your funds and contracts. No one can help you regain access — your funds will be locked forever." — Antonopoulos & Wood, Chapter 2
Rules of Thumb
This entire file is the rules-of-thumb collection; see the grouped sections above.
Related References
- Smart Contract Vulnerability Catalog - the failures behind the Security rules
- Secure Design Patterns - pull payments, modifiers, proxy pattern
- Implementation Playbook — Build, Test, Deploy - build/test/deploy workflow
- Consensus — Rules Without Rulers (PoW, PoS, Forks) - the five-question consensus framework