Protect your contracts from vulnerabilities and attacks
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.
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)
Assume your code will be attacked. Write defensively, test thoroughly, and get audited before deploying anything that handles significant value.
The most famous smart contract vulnerability. It allows an attacker to repeatedly call a function before the first call finishes, draining funds.
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 - 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;
}
}// 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!
}
}
}// ✅ 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");
}
}When a number exceeds its maximum value, it wraps around to zero (overflow). When it goes below zero, it wraps to the maximum (underflow).
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!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;
}
}Ownable contractOpenZeppelin provides secure, audited smart contract libraries. Don't reinvent the wheel - use their tested implementations!
Basic access control with owner
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
function restricted() public onlyOwner {
// Only owner can call
}
}Prevents reentrancy attacks
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MyContract is ReentrancyGuard {
function withdraw() public nonReentrant {
// Protected from reentrancy
}
}Emergency stop mechanism
import "@openzeppelin/contracts/security/Pausable.sol";
contract MyContract is Pausable {
function transfer() public whenNotPaused {
// Can be paused in emergency
}
}Standard token implementation
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 1000000);
}
}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
You now understand smart contract security! In the next module, we'll explore DeFi (Decentralized Finance) - building financial applications on blockchain.