Previous Module
Cross-chain Bridge Visual

✅ Secure Patterns: Checks-Effects-Interactions

Learn battle-tested patterns for secure contract design

Protect your dApp from common vulnerabilities

✅ Secure Design Patterns

Knowing vulnerabilities isn't enough—you need battle-tested patterns to prevent them. This section covers 12 secure patterns used by production protocols: access control (Ownable, RBAC, two-step transfers), state management (checks-effects-interactions, pull payments, rate limiting), upgradability (transparent proxies, storage gaps, immutable patterns), and emergency controls (pausable, circuit breakers, time locks). Each pattern includes complete code examples, use cases, benefits, and trade-offs. These aren't theoretical—they're patterns from Uniswap, Aave, Compound, and OpenZeppelin contracts securing billions in production.

🎮 Interactive: Pattern Library

Browse secure patterns by category. Each includes production-ready code, use cases, benefits, and trade-offs.

🔐

Access Control

Restrict function access to authorized users

Ownable Pattern
Use Case

Single admin control

contract Ownable {
  address public owner;
  
  constructor() { owner = msg.sender; }
  
  modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
  }
  
  function withdraw() external onlyOwner {
    payable(owner).transfer(address(this).balance);
  }
  
  function transferOwnership(address newOwner) external onlyOwner {
    require(newOwner != address(0));
    owner = newOwner;
  }
}
✓ Benefits
  • Simple, gas-efficient
  • Clear ownership transfer
  • Standard pattern
⚠️ Trade-offs
  • Single point of failure
  • If owner key lost, contract locked
Role-Based Access Control (RBAC)
Use Case

Multiple permission levels

import "@openzeppelin/contracts/access/AccessControl.sol";

contract RBACExample is AccessControl {
  bytes32 public constant ADMIN_ROLE = keccak256("ADMIN");
  bytes32 public constant MINTER_ROLE = keccak256("MINTER");
  
  constructor() {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
  }
  
  function mint(address to, uint amount) external onlyRole(MINTER_ROLE) {
    // Only minters can mint
  }
  
  function pause() external onlyRole(ADMIN_ROLE) {
    // Only admins can pause
  }
}
✓ Benefits
  • Flexible permissions
  • Multiple admins
  • Granular control
⚠️ Trade-offs
  • More complex
  • Higher gas costs
  • Role management overhead
Two-Step Ownership Transfer
Use Case

Prevent accidental ownership loss

contract TwoStepOwnable {
  address public owner;
  address public pendingOwner;
  
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
  
  function transferOwnership(address newOwner) external onlyOwner {
    pendingOwner = newOwner;
  }
  
  function acceptOwnership() external {
    require(msg.sender == pendingOwner);
    owner = pendingOwner;
    pendingOwner = address(0);
  }
}
✓ Benefits
  • Prevents typo mistakes
  • New owner must confirm
  • Reversible before acceptance
⚠️ Trade-offs
  • Requires two transactions
  • Pending state complexity

🏗️ Production Examples

Uniswap V3: Uses Ownable + checks-effects-interactions + pull payments for fee collection. No reentrancy despite complex math.
Aave V3: RBAC for multiple admin roles, pausable for emergencies, transparent proxy for upgradability. 3 audits + $1M bug bounty.
Compound: Time locks (2-day delay) for governance changes. Community can exit before malicious proposals execute.
OpenZeppelin Contracts: Industry-standard implementations of all these patterns. Used by 90% of DeFi protocols.

⚖️ Pattern Selection Guide

Small Projects: Ownable + Pausable + checks-effects-interactions. Simple and effective.
Medium Projects: Add RBAC, pull payments, ReentrancyGuard. More users = more complexity.
Large DeFi: Full suite—upgradability, circuit breakers, time locks, multiple audits. Billions at stake.
NFTs/Tokens: Ownable, pausable, rate limiting. Focus on preventing minting exploits.
DAOs/Governance: Time locks mandatory. RBAC for multiple roles. Emergency pause powers.