🔒 Secure Function Patterns: Access Control & Validation

Implement modifiers, require checks, and safe external calls

Previous
Gas & Optimization

🛡️ Security Patterns

External calls are the #1 source of vulnerabilities in smart contracts. Learn how to protect your contracts from common attacks.

🎮 Interactive: Reentrancy Attack Visualizer

Watch how a reentrancy attack drains a vulnerable contract

Step 1 of 5
ATTACK IN PROGRESS
User Calls Withdraw
Victim contract receives withdraw request
Contract Balance Before
10 ETH
Contract Balance After
10 ETH
Code Executed:
victim.withdraw(1 ether)

🎮 Interactive: Vulnerability Explorer

Explore common vulnerabilities and their fixes

🔄
Reentrancy Attack
Risk: Critical
External call allows attacker to re-enter function before state updates
❌ Vulnerable Code
function withdraw(uint amount) external {
    // ❌ Check user balance
    require(balances[msg.sender] >= amount);
    
    // ❌ Send ETH first (DANGER!)
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
    
    // ❌ Update balance AFTER sending (too late!)
    balances[msg.sender] -= amount;
}
✅ Secure Code
bool private locked;

function withdraw(uint amount) external {
    // ✅ Reentrancy guard
    require(!locked, "No reentrant calls");
    locked = true;
    
    // ✅ Checks
    require(balances[msg.sender] >= amount);
    
    // ✅ Effects (update state FIRST)
    balances[msg.sender] -= amount;
    
    // ✅ Interactions (external call LAST)
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
    
    locked = false;
}
Prevention Techniques:
Use reentrancy guards (OpenZeppelin ReentrancyGuard)
Follow checks-effects-interactions pattern
Update state before external calls
Use pull over push payment pattern

🎯 Checks-Effects-Interactions Pattern

The golden rule for secure external calls: always update state BEFORE making external calls.

function secureWithdraw(uint amount) external {
    // 1️⃣ CHECKS: Validate conditions
    require(balances[msg.sender] >= amount, "Insufficient balance");
    require(amount > 0, "Invalid amount");
    
    // 2️⃣ EFFECTS: Update state FIRST
    balances[msg.sender] -= amount;
    emit Withdrawal(msg.sender, amount);
    
    // 3️⃣ INTERACTIONS: External calls LAST
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

// Even if attacker re-enters, balance is already updated!
Step 1 (Checks): Validate all requirements
Step 2 (Effects): Update all state variables
Step 3 (Interactions): Make external calls last

🛡️ OpenZeppelin Security Tools

ReentrancyGuard
Prevents reentrancy attacks with a simple modifier
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyContract is ReentrancyGuard {
    function withdraw() external nonReentrant {
        // Protected from reentrancy!
    }
}
SafeERC20
Safely handles ERC20 tokens that don't return bool
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

using SafeERC20 for IERC20;

function transferTokens(address token, address to, uint amount) external {
    IERC20(token).safeTransfer(to, amount);  // Reverts on failure
}
Address.sendValue
Safe ETH transfers with proper error handling
import "@openzeppelin/contracts/utils/Address.sol";

using Address for address payable;

function sendETH(address payable recipient, uint amount) external {
    recipient.sendValue(amount);  // Reverts on failure
}

⚠️ Security Checklist

!
Always use reentrancy guards for functions with external calls
!
Check all return values from external calls
!
Follow checks-effects-interactions pattern religiously
!
Use pull over push for payments to avoid DoS
!
Test with malicious contracts that revert or reenter
!
Get security audits before mainnet deployment