Lesson 21 of 25
Contract Security
Common vulnerabilities, audit checklists, and defensive programming practices.
AdvancedInteger Overflow (Pre-0.8)
Before Solidity 0.8, arithmetic silently overflowed. Now it reverts by default. Use
unchecked blocks ONLY when you are certain overflow cannot occur.
Solidity
// In Solidity < 0.8: uint8(255) + 1 == 0 (overflow!)
// In Solidity >= 0.8: reverts automatically
// Using unchecked for gas savings when safe:
function safeIncrement(uint256 i) public pure returns (uint256) {
unchecked { return i + 1; } // safe — i cannot be uint256 max in context
}
Access Control Vulnerabilities
Missing or incorrect access control is the #1 cause of hacks. Always verify who can call sensitive functions.
Solidity
// ❌ VULNERABLE — anyone can become owner!
contract VulnerableOwnable {
address public owner;
function setOwner(address newOwner) public {
owner = newOwner; // no access check!
}
}
// ✅ SECURE
contract SecureOwnable {
address public owner;
event OwnershipTransferred(address indexed prev, address indexed next);
constructor() { owner = msg.sender; }
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
Front-Running
Miners (and MEV bots) can see pending transactions and insert their own before yours. Avoid designs where the order of transactions gives an unfair advantage.
Solidity
// ❌ Vulnerable to front-running
// An attacker sees your commitHash and submits the same answer first
contract GuessingGame {
bytes32 public commitHash;
function guess(string memory answer) public {
if (keccak256(bytes(answer)) == commitHash) {
payable(msg.sender).transfer(address(this).balance);
}
}
}
// ✅ Commit-reveal scheme
contract SafeGame {
mapping(address => bytes32) public commits;
// Phase 1: commit a hash of (answer + salt)
function commit(bytes32 hash) public {
commits[msg.sender] = hash;
}
// Phase 2: reveal after commit period
function reveal(string memory answer, bytes32 salt) public {
bytes32 hash = keccak256(abi.encodePacked(answer, salt));
require(commits[msg.sender] == hash, "Hash mismatch");
// process answer
}
}