Skip to main content

Smart Contract Security & Design Patterns

Smart contracts are immutable, public, adversarially-called programs holding money. Bugs are exploited within blocks, often by bots, and cannot be hot-patched. Losses exceed $10B cumulatively (Ronin, Poly Network, Wormhole, The DAO, Euler, Nomad).

Hello Contract (Beginner)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Counter {
uint256 public count; // auto-generated getter
event Incremented(address indexed by, uint256 newCount);

function increment() external {
unchecked { count += 1; } // safe: cannot realistically overflow uint256
emit Incremented(msg.sender, count);
}
}

Solidity >= 0.8 reverts on arithmetic overflow by default; unchecked opts out for gas.

1. Reentrancy

An external call hands control to attacker code which re-enters before state is updated.

// VULNERABLE
function withdraw() external {
uint256 amt = balances[msg.sender];
(bool ok, ) = msg.sender.call{value: amt}(""); // attacker's receive() re-enters here
require(ok, "send failed");
balances[msg.sender] = 0; // too late
}

Fixes (layer them):

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract Vault is ReentrancyGuard {
mapping(address => uint256) public balances;

function withdraw() external nonReentrant {
uint256 amt = balances[msg.sender];
balances[msg.sender] = 0; // EFFECTS before INTERACTIONS
(bool ok, ) = msg.sender.call{value: amt}("");
require(ok, "send failed");
}
}
  • Checks-Effects-Interactions ordering.
  • nonReentrant mutex (EIP-1153 transient-storage version is cheaper).
  • Cross-function and cross-contract and read-only reentrancy (a view function read mid-update by another protocol - Curve/Balancer incidents) are not fixed by a single-function guard.
  • Pull-over-push payments: let users withdraw instead of looping call.

2. Access Control & Initialisation

BugExampleFix
Missing modifierfunction setOwner() publiconlyOwner / AccessControl roles
Uninitialised proxyImplementation left uninitialised, attacker calls initialize and selfdestruct/delegatecalls (Parity wallet 2017 froze ~$150M)_disableInitializers() in constructor
tx.originPhishing contract relays user txUse msg.sender
Single EOA adminKey compromise (Ronin: 5 of 9 validators)Multisig + timelock + role separation

3. Oracle & Price Manipulation

Using a spot price from a low-liquidity pool as truth is exploitable with a flash loan (borrow, skew pool, call victim, restore, repay - one transaction).

DefenceDetail
TWAP (Uniswap v3 oracle)Time-weighted over >= 30 min; costs capital over multiple blocks
Chainlink feedsCheck updatedAt staleness, answeredInRound, min/max bounds, sequencer-uptime feed on L2
Multiple sources + deviation circuit breakerPause on > X% divergence
Don't price LP tokens naivelyUse fair-value formulas (sqrt(x*y) based)

4. Signatures & Replay

  • Use EIP-712 typed data including chainId, verifyingContract, nonce, deadline.
  • Track used nonces/hashes; reject malleable s (use OpenZeppelin ECDSA).
  • Beware ecrecover returning address(0).
  • ERC-1271 for contract-wallet signature validation.

5. Upgradeable Proxies

User -> Proxy (storage lives here) --delegatecall--> Implementation (logic only)
PatternNotes
Transparent proxyAdmin calls go to proxy; others delegated. Extra gas per call (admin check)
UUPS (ERC-1822)Upgrade logic in implementation (_authorizeUpgrade); cheaper; bricked if you upgrade to an implementation lacking upgrade function
BeaconMany proxies share one implementation address
Diamond (EIP-2535)Multiple facets; escapes 24 KB limit; complex

Rules: never reorder or change types of existing storage variables; append only; use ERC-7201 namespaced storage or __gap; never selfdestruct/delegatecall to untrusted code from an implementation; put upgrades behind timelock + multisig; consider immutability for the core and upgradeability only where truly needed.

6. Other Classic Vulnerabilities

VulnerabilityDescriptionMitigation
Front-running / sandwichMempool-visible txs reordered by botsSlippage limits, private RPC (Flashbots Protect), commit-reveal, batch auctions
Unchecked low-level callIgnoring call return valueCheck ok, use SafeERC20
DoS by revert / gas griefingLooping over unbounded array; one failing receiver blocks allPull payments, pagination
Rounding / precisionShare inflation attack on ERC-4626 first depositVirtual shares/offset, dead shares minted at deploy
Delegatecall to user inputExecutes arbitrary code in your storage contextWhitelist targets
Timestamp dependenceValidator drift ~ secondsDon't use for fine-grained logic
Flash-loan governance attackBorrow voting power in one tx (Beanstalk $182M)Snapshot-based voting (getPastVotes), timelock
Bridge key / verification flawsForged messages, compromised validatorsLight-client verification, rate limits, multi-proof

7. Secure Development Workflow

Spec & invariants -> Foundry unit tests -> Fuzz + invariant tests -> Static analysis (Slither)
-> Formal verification (Certora/Halmos) for critical math -> 2+ independent audits
-> Bug bounty (Immunefi) -> Staged rollout with TVL caps -> Monitoring + pause guardian
// Foundry invariant test: total supply always equals sum of balances
function invariant_supplyEqualsBalances() public view {
assertEq(token.totalSupply(), handler.ghostSumOfBalances());
}

Design for failure: circuit breakers (pausable, with narrow scope), rate limits on withdrawals, TVL caps, and on-chain/off-chain monitoring (Forta, OpenZeppelin Defender, Tenderly alerts).

Senior Review Checklist

  • Every external call is last, or guarded, and its result handled
  • No spot-price oracles; staleness and bounds validated
  • Admin powers minimised, behind timelock/multisig, events emitted on change
  • Storage layout checked with forge inspect ... storage-layout across upgrades
  • ERC-20 integrations tolerate fee-on-transfer, no-return tokens, and 6/8/18 decimals
  • Invariants written and fuzzed; coverage of failure branches
  • Emergency response runbook: who can pause, who signs, how users are notified

Next: Web3 Architecture.

πŸ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%