Skip to main content

Chapter 4: Defensive Design & OpenZeppelin

To mitigate vulnerabilities, the Ethereum community has developed standardized, peer-reviewed libraries. OpenZeppelin is the gold standard for secure smart contract development.

4.1 The Checks-Effects-Interactions Pattern

This is the fundamental design pattern to prevent Reentrancy. Functions should be structured in this strict order:

  1. Checks: Validate inputs and conditions (e.g., require(balance > 0)).
  2. Effects: Update internal state variables (e.g., balances[msg.sender] = 0).
  3. Interactions: Call external contracts or transfer Ether.

Secure Implementation

function withdrawSecure() public {
uint256 balance = balances[msg.sender];

// 1. Checks
require(balance > 0, "Insufficient balance");

// 2. Effects
balances[msg.sender] = 0;

// 3. Interactions
(bool success, ) = msg.sender.call{value: balance}("");
require(success, "Transfer failed");
}

4.2 OpenZeppelin ReentrancyGuard

For complex functions where Checks-Effects-Interactions is difficult, use a mutex lock. OpenZeppelin provides the nonReentrant modifier.

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureBank is ReentrancyGuard {
// ...
function withdraw() public nonReentrant {
// Function cannot be called again until this execution finishes
}
}

4.3 Access Control (Ownable and AccessControl)

Ownable

For simple contracts, Ownable restricts function execution to a single owner.

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

contract AdminContract is Ownable {
function restrictedAction() public onlyOwner {
// Only the owner can execute this
}
}

AccessControl (Role-Based)

For complex dApps, use role-based access control.

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

contract ComplexSystem is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

constructor() {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

function mint() public onlyRole(MINTER_ROLE) {
// Minting logic
}
}

4.4 Emergency Pause Switches (Pausable)

In the event a vulnerability is discovered post-deployment, having a "circuit breaker" is critical. It allows admins to freeze the contract while a fix or migration is prepared.

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract PausableToken is Pausable, Ownable {
function pause() public onlyOwner {
_pause();
}

function unpause() public onlyOwner {
_unpause();
}

function transfer(address to, uint256 amount) public whenNotPaused {
// Transfer logic
}
}
Share this guide