Reentrancy
- External calls before state updates — attacker can re-enter before state changes
- Checks-Effects-Interactions pattern — validate, update state, THEN external call
ReentrancyGuardfrom OpenZeppelin — usenonReentrantmodifier on vulnerable functionstransfer()andsend()have 2300 gas limit — but don't rely on this for security
Integer Handling
- Solidity 0.8+ reverts on overflow — but
unchecked {}blocks bypass this - Division truncates toward zero —
5 / 2 = 2, no decimals - Use fixed-point math for precision — multiply before divide, or use libraries
type(uint256).maxfor max value — don't hardcode large numbers
Gas Gotchas
- Unbounded loops can exceed block gas limit — paginate or limit iterations
- Storage writes cost 20k gas — memory/calldata much cheaper
deleterefunds gas but has limits — refund capped, don't rely on it- Reading storage in loop — cache in memory variable first
Visibility and Access
- State variables default to
internal— notprivate, derived contracts see them privatedoesn't mean hidden — all blockchain data is public, just not accessible from other contractstx.originis original sender — usemsg.sender,tx.originenables phishing attacksexternalcan't be called internally — usepublicorthis.func()(wastes gas)
Ether Handling
payablerequired to receive ether — non-payable functions reject etherselfdestructsends ether bypassing fallback — contract can receive ether without receive function- Check return value of
send()— returns false on failure, doesn't revert call{value: x}("")preferred overtransfer()— forward all gas, check return value
Storage vs Memory
storagepersists,memoryis temporary — storage costs gas, memory doesn't persist- Structs/arrays parameter default to
memory— explicitstorageto modify state calldatafor external function inputs — read-only, cheaper than memory- Storage layout matters for upgrades — never reorder or remove storage variables
Upgradeable Contracts
- Constructors don't run in proxies — use
initialize()withinitializermodifier - Storage collision between proxy and impl — use EIP-1967 storage slots
- Never
selfdestructimplementation — breaks all proxies pointing to it delegatecalluses caller's storage — impl contract storage layout must match proxy
Common Mistakes
- Block timestamp can be manipulated slightly — don't use for randomness or precise timing
requirefor user errors,assertfor invariants — assert failures indicate bugs- String comparison with
==doesn't work — usekeccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)) - Events not indexed — first 3 params can be
indexedfor efficient filtering