Smart Contract Patterns: The Building Blocks of Blockchain Magic 🏗️
Imagine you’re building with LEGO blocks. Each block has a special purpose. Some blocks are just for looking. Some blocks need coins to work. Some blocks catch things when they fall. Let’s discover all the amazing patterns that make smart contracts powerful!
The Big Picture: What Are Contract Patterns?
Think of a smart contract like a magical vending machine. It has different parts that do different jobs:
- Some parts just show you the snacks (View functions)
- Some parts need your coins (Payable functions)
- Some parts catch coins that fall randomly (Fallback functions)
- Some parts are security guards (Modifiers)
- Some parts inherit superpowers from parent machines (Inheritance)
- Some parts follow a rulebook (Interfaces)
- Some parts share tools with other machines (Libraries)
- Every machine has a unique address (Contract Address Derivation)
1. View Functions: The Window Shoppers đź‘€
What is a View Function?
A view function is like looking through a store window. You can SEE everything inside, but you can’t touch or change anything. And best of all? It’s FREE! No gas needed.
Simple Example
contract PiggyBank {
uint public savings = 100;
// VIEW: Just looking, not touching
function checkBalance()
public view returns (uint)
{
return savings;
}
}
Why It’s Special
| Feature | View Function | Regular Function |
|---|---|---|
| Reads data | âś… Yes | âś… Yes |
| Changes data | ❌ No | ✅ Yes |
| Costs gas | ❌ Free! | ⛽ Yes |
Real-World Analogy
Like checking your piggy bank without opening it. You can shake it, peek through the slot, count the sounds—but you don’t add or remove any coins.
graph TD A["You"] -->|"checkBalance#40;#41;"| B["Contract"] B -->|"Returns: 100"| A C["No Gas Needed!"] --> B
2. Payable Functions: The Cash Registers đź’°
What is a Payable Function?
A payable function is like a piggy bank slot. It can receive money (ETH). Without the payable keyword, the contract will reject any money you send!
Simple Example
contract PiggyBank {
// PAYABLE: Can receive money
function deposit()
public payable
{
// ETH automatically added to
// contract balance
}
function getBalance()
public view returns (uint)
{
return address(this).balance;
}
}
The Magic Word
If you forget payable, sending ETH will fail:
// ❌ This REJECTS money
function broken() public { }
// âś… This ACCEPTS money
function working() public payable { }
Real-World Analogy
A payable function is like a vending machine’s coin slot. No slot = no way to insert coins!
3. Fallback Functions: The Safety Nets 🪢
What is a Fallback Function?
When someone sends ETH or calls a function that doesn’t exist, the fallback function catches it—like a safety net for a trapeze artist!
Two Types
There are TWO special functions:
contract SafetyNet {
// Catches ETH with no data
receive() external payable {
// Someone sent ETH directly
}
// Catches everything else
fallback() external payable {
// Unknown function called OR
// ETH sent with data
}
}
The Decision Tree
graph TD A["ETH Received"] --> B{Has Data?} B -->|No| C{receive exists?} C -->|Yes| D["receive"] C -->|No| E["fallback"] B -->|Yes| E
Simple Example
contract CatchAll {
event Received(
address sender,
uint amount
);
receive() external payable {
emit Received(
msg.sender,
msg.value
);
}
fallback() external payable {
// Handle unknown calls
}
}
4. Modifier Patterns: The Security Guards 🛡️
What is a Modifier?
A modifier is like a security guard at a door. Before you enter the room (run the function), the guard checks if you’re allowed!
Simple Example
contract SecureVault {
address public owner;
modifier onlyOwner() {
require(
msg.sender == owner,
"Not the owner!"
);
_; // Continue to function
}
function openVault()
public onlyOwner
{
// Only owner gets here
}
}
Common Patterns
// Check if caller is owner
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// Check if not paused
modifier whenNotPaused() {
require(!paused);
_;
}
// Check minimum payment
modifier costs(uint price) {
require(msg.value >= price);
_;
}
The Underscore Magic
The _; symbol is WHERE the function code runs:
graph TD A["Call Function"] --> B["Run Modifier Code"] B --> C{Checks Pass?} C -->|Yes| D["_ runs function"] C -->|No| E["Transaction Reverts"]
5. Inheritance: Family Superpowers 👨‍👩‍👧
What is Inheritance?
Like a child inheriting traits from parents! A contract can inherit functions and variables from another contract using is.
Simple Example
// Parent contract
contract Animal {
function speak() public pure
virtual returns (string memory)
{
return "...";
}
}
// Child inherits from Animal
contract Dog is Animal {
function speak() public pure
override returns (string memory)
{
return "Woof!";
}
}
Key Words
| Keyword | Meaning |
|---|---|
is |
Inherit from |
virtual |
Can be overridden |
override |
Replaces parent |
Multiple Inheritance
contract SuperDog is Animal, Pet {
// Has powers from BOTH!
}
graph TD A["Animal"] --> C["SuperDog"] B["Pet"] --> C
6. Interface Contracts: The Rule Books 📜
What is an Interface?
An interface is like a job description. It lists WHAT functions must exist, but not HOW they work. Any contract that follows the interface MUST implement all functions!
Simple Example
// The rulebook (interface)
interface IGreeter {
function greet()
external view returns (string memory);
}
// Following the rules
contract HelloWorld is IGreeter {
function greet()
external pure returns (string memory)
{
return "Hello!";
}
}
Interface Rules
âś… Only function signatures
❌ No function bodies
❌ No state variables
❌ No constructors
âś… All functions are external
Why Use Interfaces?
graph TD A["Interface IToken"] --> B["TokenA"] A --> C["TokenB"] A --> D["TokenC"] E["Your Contract"] -->|"Works with ANY"| A
Different tokens, same interface = your code works with ALL of them!
7. Library Contracts: The Shared Toolbox đź§°
What is a Library?
A library is like a shared toolbox that many contracts can use. It contains reusable functions that don’t need their own storage.
Simple Example
// The toolbox
library Math {
function add(uint a, uint b)
internal pure returns (uint)
{
return a + b;
}
}
// Using the toolbox
contract Calculator {
using Math for uint;
function calculate() public pure
returns (uint)
{
uint x = 5;
return x.add(3); // Returns 8
}
}
Library vs Contract
| Feature | Library | Contract |
|---|---|---|
| Storage | ❌ None | ✅ Has own |
| ETH | ❌ Can’t hold | ✅ Can hold |
| Inherit | ❌ No | ✅ Yes |
| Deploy | Once, share | Each instance |
The using Magic
using Math for uint;
// Now ALL uints have .add()!
uint result = (5).add(3);
8. Contract Address Derivation: The Unique Address 📍
How Addresses Are Born
Every contract has a unique address. But HOW is it created? Two methods:
Method 1: CREATE (Normal Deployment)
Address = keccak256(
deployer_address + nonce
)
The address depends on WHO deploys and HOW MANY times they’ve deployed before.
// Each deployment = new address
// because nonce increases
Contract1 → 0xABC...
Contract2 → 0xDEF...
Method 2: CREATE2 (Predictable)
Address = keccak256(
0xFF +
deployer +
salt +
bytecode_hash
)
Same inputs = SAME address every time!
Simple Example
contract Factory {
function deploy(bytes32 salt)
public returns (address)
{
Child c = new Child{salt: salt}();
return address(c);
}
}
Why CREATE2 Matters
graph TD A["Know Address BEFORE Deploy"] --> B["Users Can Send ETH"] B --> C["Contract Deploys Later"] C --> D["Collects the ETH!"]
Putting It All Together 🎯
Here’s a complete contract using ALL patterns:
// Interface (rulebook)
interface IVault {
function deposit() external payable;
function withdraw() external;
}
// Library (shared tools)
library SafeMath {
function add(uint a, uint b)
internal pure returns (uint)
{
return a + b;
}
}
// Base contract (parent)
contract Ownable {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
}
// Main contract
contract Vault is Ownable, IVault {
using SafeMath for uint;
// View function
function getBalance()
public view returns (uint)
{
return address(this).balance;
}
// Payable function
function deposit()
external payable override
{}
// Modifier protected
function withdraw()
external override onlyOwner
{
payable(owner).transfer(
address(this).balance
);
}
// Fallback functions
receive() external payable {}
fallback() external payable {}
}
Quick Reference Table
| Pattern | Purpose | Keyword |
|---|---|---|
| View | Read-only, free | view |
| Payable | Accept ETH | payable |
| Fallback | Catch unknown | fallback() |
| Receive | Catch plain ETH | receive() |
| Modifier | Pre-check rules | modifier |
| Inheritance | Get parent powers | is |
| Interface | Define rules | interface |
| Library | Share tools | library |
| CREATE2 | Predict address | salt |
You Did It! 🎉
You now understand the 8 essential patterns of smart contracts:
- View functions - Free peeking
- Payable functions - Accept money
- Fallback functions - Safety nets
- Modifiers - Security guards
- Inheritance - Family superpowers
- Interfaces - Rule books
- Libraries - Shared toolboxes
- Address derivation - Unique locations
These patterns are the LEGO blocks of blockchain development. Mix and match them to build anything you can imagine!
Remember: Every smart contract you see uses these patterns. Now YOU know their secrets!
