Solidity Advanced
Mastering advanced Solidity patterns is essential for writing secure, gas-efficient smart contracts. This lesson covers inheritance, error handling, gas optimization, and common design patterns.
Inheritance and Interfaces
// Base contract
contract Ownable {
address public owner;
constructor() { owner = msg.sender; }
modifier onlyOwner() {
require(msg.sender == owner); _;
}
}
// Inheritance
contract Token is Ownable {
string public name;
function rename(string memory _name) public onlyOwner {
name = _name;
}
}
// Interface (no implementation)
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address) external view returns (uint);
}
// Abstract contract (partial implementation)
abstract contract Base {
function hook() internal virtual;
function execute() public { hook(); }
}
Error Handling
// require — reverts with message
require(amount > 0, "Amount must be positive");
// revert — unconditional revert
revert("Operation not allowed");
// assert — checks invariants (uses remaining gas)
assert(totalSupply == sum(balances));
// Custom errors (cheaper than revert strings)
error InsufficientBalance(uint requested, uint available);
function withdraw(uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance(amount, balances[msg.sender]);
// ...
}
Gas Optimization Tips
1. Use calldata instead of memory for read-only parameters
function process calldata bytes data) external;
2. Cache storage variables in memory
uint _count = count; // One SLOAD
_count += 1; // Work in memory
count = _count; // One SSTORE
3. Use unchecked for safe arithmetic
unchecked { i++; } // Saves ~100 gas per op
4. Pack variables into single storage slots
uint128 a; uint128 b; // Packed in one slot
5. Use custom errors instead of require strings
error Foo(); // Cheaper than revert("Foo")
6. Short-circuit conditions
if (a != 0 && b / a > 10) // Check cheap first
Security Patterns
// Reentrancy Guard
bool private locked;
modifier nonReentrant() {
require(!locked);
locked = true;
_;
locked = false;
}
// Checks-Effects-Interactions Pattern
function withdraw() public nonReentrant {
uint amount = balances[msg.sender]; // Check
balances[msg.sender] = 0; // Effect
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok); // Interaction
}
// Pull over Push (payee withdraws)
mapping(address => uint) public pending;
function claim() public {
uint amount = pending[msg.sender];
pending[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}