🔒 Secure Function Patterns: Access Control & Validation
Implement modifiers, require checks, and safe external calls
Your Progress
0 / 5 completed←
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