Back to Blockchain & Web3

Module 4: Smart Contract Security

Protect your contracts from vulnerabilities and attacks

🚨 Why Security is Critical

Smart contracts handle real money and are immutable once deployed. A single bug can lead to millions of dollars lost, and you can't just "patch" it like regular software.

Famous Hacks:

The DAO Hack (2016)

$60 million stolen due to reentrancy vulnerability

Parity Wallet Hack (2017)

$150 million frozen due to library self-destruct

Poly Network Hack (2021)

$600 million stolen (later returned)

⚠️ Key Principle:

Assume your code will be attacked. Write defensively, test thoroughly, and get audited before deploying anything that handles significant value.

Reentrancy Attack

The most famous smart contract vulnerability. It allows an attacker to repeatedly call a function before the first call finishes, draining funds.

Simple Analogy: ATM Glitch

Imagine an ATM that gives you money before updating your balance. You could withdraw $100, and while it's dispensing cash, quickly request another $100. The ATM checks your balance (still shows $100), gives you more money, and repeats until empty.

Vulnerable Code

// ❌ VULNERABLE CODE - DO NOT USE
contract VulnerableBank {
    mapping(address => uint) public balances;
    
    function withdraw() public {
        uint amount = balances[msg.sender];
        
        // DANGER: External call before state update
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success);
        
        // Balance updated AFTER sending money
        balances[msg.sender] = 0;
    }
}

Attack Contract

// Attacker's contract
contract Attacker {
    VulnerableBank public bank;
    
    function attack() public payable {
        bank.deposit{value: 1 ether}();
        bank.withdraw();  // Start the attack
    }
    
    // This function is called when receiving ETH
    receive() external payable {
        if (address(bank).balance >= 1 ether) {
            bank.withdraw();  // Call withdraw again!
        }
    }
}

✅ Fixed Code

// ✅ SECURE CODE - Checks-Effects-Interactions Pattern
contract SecureBank {
    mapping(address => uint) public balances;
    
    function withdraw() public {
        uint amount = balances[msg.sender];
        require(amount > 0, "No balance");
        
        // Update state BEFORE external call
        balances[msg.sender] = 0;
        
        // External call happens last
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

🛡️ Prevention:

  • Checks-Effects-Interactions: Update state before external calls
  • ReentrancyGuard: Use OpenZeppelin's modifier
  • Pull over Push: Let users withdraw instead of sending automatically

Integer Overflow/Underflow

When a number exceeds its maximum value, it wraps around to zero (overflow). When it goes below zero, it wraps to the maximum (underflow).

Simple Analogy: Odometer

Like a car odometer that goes from 999,999 back to 000,000. In Solidity (before 0.8.0), if uint8 (max 255) reaches 256, it wraps to 0.

// Before Solidity 0.8.0 - VULNERABLE
uint8 balance = 255;
balance = balance + 1;  // Wraps to 0!

uint8 balance2 = 0;
balance2 = balance2 - 1;  // Wraps to 255!

// Solidity 0.8.0+ - SAFE (automatic checks)
uint8 balance = 255;
balance = balance + 1;  // Reverts with error!

🛡️ Prevention:

  • Use Solidity 0.8.0+: Built-in overflow/underflow checks
  • SafeMath library: For older versions
  • Use unchecked: Only when you're certain it's safe (gas optimization)

Access Control Issues

Failing to properly restrict who can call sensitive functions can lead to unauthorized access.

// ❌ VULNERABLE - Anyone can call
contract Vulnerable {
    address public owner;
    
    function changeOwner(address newOwner) public {
        owner = newOwner;  // No access control!
    }
}

// ✅ SECURE - Only owner can call
contract Secure {
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

🛡️ Best Practices:

  • • Use OpenZeppelin's Ownable contract
  • • Implement role-based access control (RBAC)
  • • Use modifiers for access checks
  • • Consider multi-sig for critical functions

🛡️ OpenZeppelin: Battle-Tested Security

OpenZeppelin provides secure, audited smart contract libraries. Don't reinvent the wheel - use their tested implementations!

Popular OpenZeppelin Contracts

Ownable

Basic access control with owner

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

contract MyContract is Ownable {
    function restricted() public onlyOwner {
        // Only owner can call
    }
}

ReentrancyGuard

Prevents reentrancy attacks

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

contract MyContract is ReentrancyGuard {
    function withdraw() public nonReentrant {
        // Protected from reentrancy
    }
}

Pausable

Emergency stop mechanism

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

contract MyContract is Pausable {
    function transfer() public whenNotPaused {
        // Can be paused in emergency
    }
}

ERC20

Standard token implementation

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 1000000);
    }
}

📚 Learn More:

✅ Security Best Practices

Use Latest Solidity

0.8.0+ has built-in overflow checks

Follow CEI Pattern

Checks-Effects-Interactions order

Use OpenZeppelin

Don't reinvent security primitives

Test Thoroughly

Unit tests, integration tests, fuzzing

Get Audited

Professional audit before mainnet

Use Multisig

For admin functions and treasury

Implement Pause

Emergency stop mechanism

Monitor Events

Watch for suspicious activity

🔍 Security Tools:

  • Slither: Static analysis tool
  • Mythril: Security analysis tool
  • Echidna: Fuzzing tool
  • Hardhat: Testing framework

📚 Learn More:

🎯 What's Next?

You now understand smart contract security! In the next module, we'll explore DeFi (Decentralized Finance) - building financial applications on blockchain.