メインコンテンツまでスキップ

4: Introduction to Smart Contracts

1. What Are Smart Contracts?

The Paradox: Neither Smart Nor Contracts

Despite their name, smart contracts are neither smart nor contracts in the traditional sense. This paradoxical statement helps us understand what they truly are:

  • Not "smart": They are simple programs that execute predetermined logic without artificial intelligence or learning capabilities
  • Not "contracts": They are not legal documents but rather self-executing code stored on a blockchain

Definition and Core Concept

A smart contract is a user-defined program that runs on top of a blockchain. More formally, smart contracts are:

Autonomous agents that live inside the blockchain execution environment, always executing specific code when triggered by a message or transaction, with direct control over their own cryptocurrency balance and persistent storage.

Think of smart contracts as vending machines: you insert coins (cryptocurrency), select an option (call a function), and receive a predetermined output (tokens, services, or state changes) without requiring a trusted intermediary.

Historical Context: Nick Szabo's Vision (1994)

Nick Szabo conceptualized smart contracts long before blockchain technology existed:

"A smart contract is a computerized transaction protocol that executes the terms of a contract. The general objectives are to satisfy common contractual conditions (such as payment terms, liens, confidentiality, and even enforcement), minimize exceptions both malicious and accidental, and minimize the need for trusted intermediaries."

The key innovation that Satoshi Nakamoto's Bitcoin introduced in 2009 was the underlying blockchain technology as a tool for distributed consensus. While Bitcoin primarily focused on currency, it demonstrated that blockchain could support more complex applications.

3. Understanding Smart Contracts from a Developer's Perspective

The Programming Model

Smart contracts operate on a simple yet powerful model:

Contract Classes and Objects

  • Contract Class: Defines the program code and storage variables
  • Contract Object: An instance of the class living on the blockchain at a specific address

Core Components

Storage Fields Persistent variables stored in the contract's state. These survive across function calls and transactions.

balances: mapping(address => uint256)
owner: address
totalSupply: uint256

Functions/Methods Code that can be invoked to read or update the contract state.

transfer(address to, uint256 amount)
getBalance(address account) returns (uint256)

Access Control Use require() statements to enforce authorization rules. Transactions that fail these checks are reverted.

require(msg.sender == owner, "Only owner can call this");

Example: Domain Name Registry

Let's examine a simple domain name registry to understand the contract model:

Storage Structure

domains: mapping(string => address)

Registration Function

function registerDomain(string memory name) public {
require(domains[name] == address(0), "Domain already registered");
domains[name] = msg.sender;
}

Lookup Function

function lookupDomain(string memory name) public view returns (address) {
return domains[name];
}

Account Model in Ethereum

Ethereum uses an account model rather than Bitcoin's UTXO model. There are two types of accounts:

Externally Owned Accounts (EOAs)

  • Controlled by private keys
  • Have an ether balance
  • Can send transactions
  • Have no code

Contract Accounts

  • Controlled by their contract code
  • Have an ether balance
  • Have contract code and storage
  • Execute code when receiving messages/transactions

Each Ethereum account contains:

  1. Nonce: Transaction counter (prevents replay attacks)
  2. Balance: Current ether balance
  3. Contract Code: For contract accounts only
  4. Storage: Key-value store for persistent data

4. Ethereum Programming Basics

Introduction to Solidity

Solidity is a high-level, statically-typed programming language designed for writing smart contracts on Ethereum. It compiles to Ethereum Virtual Machine (EVM) bytecode.

Solidity Source Code → Solidity Compiler → EVM Bytecode → Deployed to Blockchain

Data Types

Integers

uint256 totalSupply;    // Unsigned 256-bit integer (default)
uint8 decimals; // Unsigned 8-bit integer
int256 balance; // Signed 256-bit integer

Note: Solidity is statically typed like Java, C, or Rust, unlike Python or JavaScript.

Addresses

address owner;                    // 20-byte Ethereum address
address payable recipient; // Can receive Ether

Mappings (Hash Tables)

mapping(address => uint256) public balances;
mapping(string => address) private domains;

Important:

  • Every key initially maps to zero
  • No built-in way to query length or iterate over non-zero elements
  • Must maintain separate data structures for enumeration

Arrays

uint256[10] fixedArray;              // Fixed size
uint256[] dynamicArray; // Dynamic size (more expensive)
address[] public voters; // Storage array (persists)

Strings and Bytes

bytes32 hash;                        // Fixed size (returned by hash functions)
bytes memory data; // Dynamic byte array
string memory name; // UTF-8 string

Function Structure

function transfer(address to, uint256 amount)
public // Visibility modifier
returns (bool success) // Return type
{
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}

Function Components

  1. Name: transfer
  2. Arguments: (address to, uint256 amount)
  3. Visibility: public, private, internal, external
  4. Mutability: pure, view, payable
  5. Returns: returns (bool success)

Visibility Modifiers

For Functions

  • public: Callable by anyone, internally or externally
  • private: Only callable within the contract
  • internal: Callable within the contract and derived contracts
  • external: Only callable from outside the contract

For Variables

  • public: Automatically creates a getter function
  • private: Only accessible within the contract
  • internal: Accessible in the contract and derived contracts

Security Note: private variables are not truly secret! All blockchain data is public. The private keyword only restricts access from other contracts.

Mutability Modifiers

// pure: Does not read or modify state
function add(uint a, uint b) public pure returns (uint) {
return a + b;
}

// view: Reads state but does not modify it
function getBalance(address account) public view returns (uint) {
return balances[account];
}

// payable: Can receive Ether
function deposit() public payable {
balances[msg.sender] += msg.value;
}

// (no modifier): Can read and modify state
function transfer(address to, uint amount) public {
balances[msg.sender] -= amount;
balances[to] += amount;
}

Constructors

Constructors are invoked once when the contract is deployed:

constructor(string memory name, string memory symbol) {
tokenName = name;
tokenSymbol = symbol;
owner = msg.sender;
}

Working with Ether

Ether is the native cryptocurrency of Ethereum. Contracts can hold and transfer Ether:

// Receiving Ether
function deposit() public payable {
// msg.value contains the Ether sent
balance[msg.sender] += msg.value;
}

// Sending Ether
function withdraw(uint amount) public {
require(balance[msg.sender] >= amount);
balance[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}

// Check contract's Ether balance
uint contractBalance = address(this).balance;

Ether Units:

  • 1 Ether = 10^18 Wei
  • 1 Gwei = 10^9 Wei (commonly used for gas prices)
  • 1 Finney = 10^15 Wei
  • 1 Szabo = 10^12 Wei

Blockchain Metadata

Contracts can access current blockchain state:

block.timestamp     // Current block timestamp (Unix time)
block.number // Current block number
block.difficulty // Current block difficulty
msg.sender // Address that called the function
msg.value // Amount of Ether sent (in Wei)
tx.origin // Original sender of the transaction

Security Warning: Using block.timestamp for critical logic can be manipulated by miners within ~15 minutes. Never use it as a source of randomness.

Events

Events allow contracts to log information that external applications can listen to:

// Declare the event
event Transfer(address indexed from, address indexed to, uint256 value);

// Emit the event
function transfer(address to, uint256 amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
balances[to] += amount;

emit Transfer(msg.sender, to, amount);
}

Use Cases:

  • Notifying user interfaces of state changes
  • Creating searchable transaction logs
  • Cheaper than storing data in contract storage

Interacting with Other Contracts

Contracts can call functions of other contracts:

// Define interface
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}

// Call another contract
function sendTokens(address tokenAddress, address recipient, uint256 amount) public {
IERC20 token = IERC20(tokenAddress);
require(token.transfer(recipient, amount), "Transfer failed");
}

5. Case Study: The Dutch Auction

Understanding Dutch Auctions

A Dutch Auction is a price discovery mechanism where:

  1. The seller sets a high initial "Buy Now" price
  2. The price automatically decreases over time
  3. The first buyer to accept the current price wins
  4. The auction ends immediately upon purchase

This mechanism was famously used by CryptoKitties, one of the first major NFT projects on Ethereum.

Why Dutch Auctions?

Advantages:

  • Efficiency: Auction ends as soon as a buyer is found
  • Price Discovery: Market determines fair value
  • No Bidding Wars: Buyers don't compete with each other
  • Gas Efficiency: Only one transaction needed (compared to English auctions with multiple bids)

Implementation in Solidity

pragma solidity ^0.8.0;

contract DutchAuction {
address payable public seller;
uint256 public startingPrice;
uint256 public priceDecrement;
uint256 public startTime;
uint256 public decrementInterval; // Time between price drops
bool public ended;

constructor(
uint256 _startingPrice,
uint256 _priceDecrement,
uint256 _decrementInterval
) {
seller = payable(msg.sender);
startingPrice = _startingPrice;
priceDecrement = _priceDecrement;
decrementInterval = _decrementInterval;
startTime = block.timestamp;
ended = false;
}

// Calculate current price based on time elapsed
function getCurrentPrice() public view returns (uint256) {
if (ended) return 0;

uint256 timeElapsed = block.timestamp - startTime;
uint256 periods = timeElapsed / decrementInterval;
uint256 discount = periods * priceDecrement;

if (discount >= startingPrice) {
return 0; // Minimum price reached
}

return startingPrice - discount;
}

// Buy at current price
function buy() public payable {
require(!ended, "Auction has ended");

uint256 currentPrice = getCurrentPrice();
require(msg.value >= currentPrice, "Payment insufficient");

// End auction
ended = true;

// Transfer payment to seller
seller.transfer(currentPrice);

// Refund excess payment
if (msg.value > currentPrice) {
payable(msg.sender).transfer(msg.value - currentPrice);
}

// Transfer asset to buyer (in real implementation)
// This would interact with an NFT contract
}
}

Key Design Patterns

Time-Based State Changes

uint256 timeElapsed = block.timestamp - startTime;
uint256 periods = timeElapsed / decrementInterval;

The price automatically decreases based on elapsed time without requiring external updates.

Safe Ether Handling

// Accept payment
msg.value >= currentPrice

// Transfer to seller
seller.transfer(currentPrice);

// Refund excess
payable(msg.sender).transfer(msg.value - currentPrice);

State Management

bool public ended;
require(!ended, "Auction has ended");
ended = true;

Prevents double-spending by tracking auction state.

Potential Improvements

Issues with the Basic Implementation:

  1. No Asset Transfer: Real auction needs to interact with NFT contract
  2. No Cancellation: Seller cannot cancel auction
  3. Frontrunning: Miners can see pending transactions and buy before others
  4. No Minimum Price: Auction could reach zero

Enhanced Version:

contract EnhancedDutchAuction {
IERC721 public nftContract;
uint256 public tokenId;
uint256 public minimumPrice;

// ... previous code ...

function getCurrentPrice() public view returns (uint256) {
// ... price calculation ...

// Enforce minimum price
if (discount >= startingPrice - minimumPrice) {
return minimumPrice;
}

return startingPrice - discount;
}

function buy() public payable {
// ... previous checks ...

// Transfer NFT to buyer
nftContract.transferFrom(address(this), msg.sender, tokenId);

// ... payment handling ...
}

function cancelAuction() public {
require(msg.sender == seller, "Only seller can cancel");
require(!ended, "Already ended");

ended = true;
nftContract.transferFrom(address(this), seller, tokenId);
}
}

Legal contracts require:

  1. Offer and Acceptance
  • One party proposes terms
  • Another party accepts those terms
  1. Consideration
  • Something of value exchanged between parties
  • "Quid pro quo" - something for something
  1. Mutual Agreement (Mutuality)
  • Both parties understand and agree to terms
  • Meeting of the minds
  1. Legality and Capacity
  • Contract purpose must be legal
  • Parties must have legal capacity to contract

Let's analyze a token sale smart contract:

contract TokenSale {
mapping(address => uint256) public tokens;
uint256 public pricePerToken = 0.01 ether;

function buyTokens() public payable {
require(msg.value > 0, "Must send Ether");

uint256 tokenAmount = msg.value / pricePerToken;
tokens[msg.sender] += tokenAmount;
}
}

Offer and Acceptance:

  • Offer: Contract deployed with terms (price, availability)
  • Acceptance: User signs and submits transaction
  • Digital signature proves intent

Consideration:

  • Buyer provides: Ether (cryptocurrency)
  • Seller provides: Tokens (digital assets)
  • Exchange is atomic and automatic

Mutuality:

  • Smart contract code is typically published
  • Ethereum explorers allow anyone to inspect code
  • Users can verify contract logic before interacting

Capacity and Legality:

  • Execution automatically carries out transfer
  • No trust in counterparty required
  • However: code bugs can violate intent
  • Legal status varies by jurisdiction

Smart Contracts Through Szabo's Lens

Nick Szabo's original vision emphasized:

Minimizing Need for Trusted Intermediaries

Traditional contracts often require:

  • Escrow agents
  • Payment processors
  • Courts for enforcement

Smart contracts eliminate intermediaries through:

  • Code-based enforcement
  • Cryptographic proof
  • Blockchain immutability

Minimizing Exceptions

Malicious Exceptions: Prevented by:

  • Transparent code execution
  • Immutable deployment
  • Cryptographic security

Accidental Exceptions: Reduced by:

  • Deterministic execution
  • Formal verification (optional)
  • Automated testing

Reducing Transaction Costs

  • Fraud Loss: Cryptographic guarantees prevent many fraud types
  • Arbitration: Code eliminates disputes about execution
  • Enforcement: Automatic and costless

Limitations and Gaps

The Oracle Problem

Smart contracts cannot directly access external data:

// This doesn't exist in reality:
uint price = ethereum.getPrice("ETH/USD");

Solution requires oracles - trusted external data sources, reintroducing trust.

Code vs Intent

  • Code executes exactly as written, not as intended
  • Bugs can have catastrophic consequences
  • No "rollback" button without governance

Famous Example: The DAO hack (2016)

  • Bug in DAO smart contract
  • Attacker drained 3.6 million ETH
  • Required Ethereum hard fork to resolve
  • Smart contracts may not be legally binding in all jurisdictions
  • Jurisdiction for disputes unclear
  • Enforcement beyond blockchain difficult

Immutability vs Flexibility

Traditional contracts can be:

  • Amended by mutual agreement
  • Interpreted by courts
  • Enforced selectively

Smart contracts are:

  • Immutable once deployed (usually)
  • Literal in execution
  • Cannot adapt to unforeseen circumstances

Hybrid Approaches

Ricardian Contracts: Combine:

  • Human-readable legal prose
  • Machine-readable code
  • Cryptographic signatures

Legal Wrappers: Some jurisdictions now recognize:

  • Smart contracts as legally binding
  • Legal agreements that reference smart contract addresses
  • Multi-sig contracts for governance

7. Fungible and Non-Fungible Tokens

Understanding Token Standards

Tokens are smart contracts that function as digital assets. Ethereum has standardized interfaces that enable interoperability across the ecosystem.

Fungible vs Non-Fungible

Fungible Tokens (ERC-20):

  • Interchangeable units
  • Each unit identical to every other
  • Can be summed and divided
  • Examples: currencies, commodities, shares

Non-Fungible Tokens (ERC-721):

  • Unique items with distinct IDs
  • Each token has individual attributes
  • Cannot be subdivided
  • Examples: art, collectibles, real estate deeds

ERC-20: Fungible Token Standard

ERC-20 defines six core functions that every fungible token should implement:

Basic Functionality

// Get total token supply
function totalSupply() public view returns (uint256);

// Get balance of an account
function balanceOf(address account) public view returns (uint256);

// Transfer tokens to another address
function transfer(address to, uint256 amount) public returns (bool);

Approval Mechanism

// Approve another address to spend tokens on your behalf
function approve(address spender, uint256 amount) public returns (bool);

// Check how much a spender is allowed to spend
function allowance(address owner, address spender) public view returns (uint256);

// Transfer tokens from another address (requires approval)
function transferFrom(address from, address to, uint256 amount) public returns (bool);

Complete ERC-20 Implementation

pragma solidity ^0.8.0;

contract ERC20Token {
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;

mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply * 10**uint256(decimals);
balanceOf[msg.sender] = totalSupply;
}

function transfer(address to, uint256 amount) public returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");

balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;

emit Transfer(msg.sender, to, amount);
return true;
}

function approve(address spender, uint256 amount) public returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) public returns (bool) {
require(balanceOf[from] >= amount, "Insufficient balance");
require(allowance[from][msg.sender] >= amount, "Allowance exceeded");

balanceOf[from] -= amount;
balanceOf[to] += amount;
allowance[from][msg.sender] -= amount;

emit Transfer(from, to, amount);
return true;
}
}

The Approval Pattern

The approval mechanism enables powerful composability:

Example: Decentralized Exchange

// Step 1: User approves DEX to spend tokens
token.approve(dexAddress, 1000);

// Step 2: DEX can execute trades on user's behalf
dex.trade(tokenA, tokenB, amount);

This pattern allows:

  • Automated trading
  • Limit orders
  • Complex DeFi protocols
  • Without giving full custody

ERC-721: Non-Fungible Token Standard

ERC-721 defines unique tokens with individual identities:

interface IERC721 {
// Get owner of a specific token
function ownerOf(uint256 tokenId) external view returns (address);

// Transfer a specific token
function transferFrom(address from, address to, uint256 tokenId) external;

// Approve someone to transfer a specific token
function approve(address to, uint256 tokenId) external;

// Get approved address for a token
function getApproved(uint256 tokenId) external view returns (address);

// Count of tokens owned by an address
function balanceOf(address owner) external view returns (uint256);
}

Key Differences from ERC-20

FeatureERC-20ERC-721
IdentityFungible (any token = any other)Unique (each has tokenId)
TransferAmount-basedID-based
ApprovalAmount for spenderSpecific token or all tokens
DivisibilityCan be fractionalIndivisible

Token Metadata

NFTs often include additional metadata:

function tokenURI(uint256 tokenId) public view returns (string memory);

This typically returns a URL to JSON with:

{
"name": "CryptoKitty #1234",
"description": "A cute digital cat",
"image": "ipfs://QmX...",
"attributes": [
{"trait_type": "Color", "value": "Orange"},
{"trait_type": "Generation", "value": 5}
]
}

Why Standardization Matters

Using standard interfaces enables:

  1. Wallet Support: MetaMask, hardware wallets automatically support any ERC-20/721
  2. Exchange Listings: DEXs can list any standard token
  3. DeFi Composability: Protocols can integrate any token without custom code
  4. User Confidence: Standard interfaces are well-audited
  5. Network Effects: More tools support standard tokens

8. Gas and Transaction Costs

Understanding Gas

Gas is the unit measuring computational work on Ethereum. Every operation costs gas:

Transaction Fee = Gas Used × Gas Price
  • Gas Used: Number of computational steps
  • Gas Price: Price per gas unit (in Gwei)

Why Gas Exists

Preventing Denial-of-Service

Without gas:

// Infinite loop that could shut down the network
function attack() public {
while(true) {
// Do nothing forever
}
}

With gas:

  • Loop consumes gas for each iteration
  • Transaction fails when gas runs out
  • Attacker still pays for computation used

Aligning Incentives

  • Miners prioritize higher gas price transactions
  • Users pay proportionally to resources consumed
  • Network remains economically sustainable

Gas Costs by Operation

Different operations cost different amounts of gas:

OperationGas CostReason
Addition3Simple arithmetic
Multiplication5More complex
Storage write20,000Permanent storage is expensive
Storage read200Reading state
Contract creation32,000Base cost plus code
Transaction21,000Base fee for any transaction
Log (event)375 + 375/topicCheaper than storage

Example: Analyzing Gas Usage

function expensiveFunction() public {
uint256 x = 5; // SSTORE: ~20,000 gas
uint256 y = x + 10; // ADD: 3 gas
uint256 z = x * y; // MUL: 5 gas

for(uint i = 0; i < 10; i++) {
storage[i] = i; // 10 × SSTORE: ~200,000 gas
}
}

Total: ~220,000 gas

At 50 Gwei gas price:

  • Cost: 220,000 × 50 = 11,000,000 Gwei = 0.011 ETH
  • At $2,000/ETH: ~$22 transaction cost

Gas Limit and Gas Price

Every transaction specifies:

Transaction {
gasLimit: 250000, // Maximum gas willing to consume
gasPrice: 50 gwei, // Price per gas unit
...
}

What Happens During Execution?

Scenario 1: Normal Execution

  • Gas limit: 250,000
  • Gas used: 220,000
  • Refund: 30,000 gas
  • Cost: 220,000 × 50 Gwei

Scenario 2: Out of Gas

  • Gas limit: 200,000
  • Gas used: 200,000 (then stopped)
  • Changes: Reverted
  • Cost: Still pay 200,000 × 50 Gwei

Key Point: You pay for gas consumed even if transaction fails!

Gas Optimization Strategies

1. Use Appropriate Data Types

// Bad: Wastes storage
struct User {
uint256 age; // 256 bits for a number 0-100
uint256 balance;
}

// Good: Pack variables
struct User {
uint8 age; // 8 bits sufficient for age
uint248 balance; // Fits in same storage slot
}

2. Minimize Storage Operations

// Bad: Multiple storage writes
function badIncrement() public {
counter = counter + 1;
counter = counter + 1;
counter = counter + 1;
}

// Good: Single storage write
function goodIncrement() public {
uint256 temp = counter + 3;
counter = temp;
}

3. Use Events Instead of Storage

// Bad: Store entire history
uint256[] public history;
function record(uint256 value) public {
history.push(value); // Expensive!
}

// Good: Emit events
event Recorded(uint256 value);
function record(uint256 value) public {
emit Recorded(value); // Much cheaper!
}

4. Short-Circuit Evaluation

// Put cheaper checks first
require(msg.sender == owner, "Not owner"); // Cheap
require(balanceOf[owner] > 1000 ether, "Low balance"); // Expensive

5. Use view and pure

// Doesn't consume gas when called externally
function getBalance(address account) public view returns (uint256) {
return balances[account];
}

EIP-1559: Modern Gas Pricing

Since London hard fork (August 2021), Ethereum uses EIP-1559:

Transaction Fee = (Base Fee + Priority Fee) × Gas Used
  • Base Fee: Algorithmically determined, burned
  • Priority Fee: Tip to miners, set by user
  • Max Fee: Maximum total fee willing to pay

Benefits:

  • More predictable fees
  • Burns ETH (deflationary pressure)
  • Better user experience

Block Gas Limit

Each block has a maximum gas limit (~30 million gas):

Block Gas Limit ≈ 30,000,000 gas
Average Transaction ≈ 21,000-200,000 gas
Transactions per Block ≈ 150-1,400

This limit:

  • Prevents blocks from being too large
  • Ensures reasonable validation times
  • Adapts to network conditions

9. Security Considerations and Best Practices

Common Vulnerabilities

Reentrancy Attacks

The most famous smart contract vulnerability:

Vulnerable Code:

contract VulnerableBank {
mapping(address => uint256) public balances;

function withdraw() public {
uint256 amount = balances[msg.sender];

// DANGER: External call before state update
(bool success, ) = msg.sender.call{value: amount}("");
require(success);

balances[msg.sender] = 0;
}
}

Attack:

contract Attacker {
VulnerableBank bank;

constructor(address _bank) {
bank = VulnerableBank(_bank);
}

function attack() external payable {
bank.deposit{value: msg.value}();
bank.withdraw();
}

// Reenters withdraw before balance is zeroed
receive() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw();
}
}
}

Fix: Checks-Effects-Interactions Pattern:

function withdraw() public {
uint256 amount = balances[msg.sender];

// Update state BEFORE external call
balances[msg.sender] = 0;

(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}

Or use ReentrancyGuard:

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

contract SafeBank is ReentrancyGuard {
function withdraw() public nonReentrant {
// ... withdrawal logic ...
}
}

Integer Overflow/Underflow

Before Solidity 0.8.0:

uint8 max = 255;
max = max + 1; // Wraps to 0!

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

Modern Solidity (≥0.8.0): Automatic overflow checking

uint8 max = 255;
max = max + 1; // Reverts with error!

For older versions, use SafeMath:

using SafeMath for uint256;

uint256 result = a.add(b); // Safe addition

Access Control Failures

Vulnerable:

function withdraw() public {
// Anyone can withdraw!
payable(owner).transfer(address(this).balance);
}

Fixed:

function withdraw() public {
require(msg.sender == owner, "Not authorized");
payable(owner).transfer(address(this).balance);
}

Better: Use OpenZeppelin's Ownable:

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

contract MyContract is Ownable {
function withdraw() public onlyOwner {
payable(owner()).transfer(address(this).balance);
}
}

Front-Running

Transactions in mempool are visible before execution:

// Vulnerable to front-running
function buyTokens() public payable {
uint256 price = getOraclePrice(); // Attacker can see this
uint256 amount = msg.value / price;
tokens[msg.sender] += amount;
}

Mitigations:

  • Commit-reveal schemes
  • Batch auctions
  • Private mempools (Flashbots)
  • Minimum execution delays

Timestamp Manipulation

// BAD: Miners can manipulate timestamp ~15 minutes
function expireAuction() public {
require(block.timestamp > auctionEnd, "Not ended");
// ... finalize auction ...
}

Better:

  • Use block numbers instead
  • Accept ~15 minute uncertainty
  • Use VRF for randomness needs

Best Practices

1. Follow Checks-Effects-Interactions Pattern

function transfer(address to, uint256 amount) public {
// 1. Checks
require(balances[msg.sender] >= amount, "Insufficient balance");
require(to != address(0), "Invalid recipient");

// 2. Effects (update state)
balances[msg.sender] -= amount;
balances[to] += amount;

// 3. Interactions (external calls)
emit Transfer(msg.sender, to, amount);
if (isContract(to)) {
// Call external contract last
}
}

2. Use Well-Audited Libraries

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

contract MyToken is ERC20, Ownable, Pausable {
// Leverage battle-tested code
}

3. Implement Circuit Breakers

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

function emergencyPause() public onlyOwner {
_pause();
}
}

4. Rate Limiting

mapping(address => uint256) public lastWithdrawal;
uint256 public constant WITHDRAWAL_DELAY = 1 days;

function withdraw(uint256 amount) public {
require(
block.timestamp >= lastWithdrawal[msg.sender] + WITHDRAWAL_DELAY,
"Withdrawal on cooldown"
);

lastWithdrawal[msg.sender] = block.timestamp;
// ... proceed with withdrawal ...
}

5. Pull Over Push Payments

Bad (Push):

function distribute() public {
for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]); // Can fail!
}
}

Good (Pull):

mapping(address => uint256) public pendingWithdrawals;

function recordPayout(address recipient, uint256 amount) internal {
pendingWithdrawals[recipient] += amount;
}

function withdraw() public {
uint256 amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}

6. Comprehensive Testing

// test/MyContract.test.js
describe("MyContract", function() {
it("Should transfer tokens correctly", async function() {
// Test normal operation
});

it("Should revert on insufficient balance", async function() {
// Test failure cases
});

it("Should handle reentrancy attacks", async function() {
// Test security
});
});

7. Formal Verification

For critical contracts, use formal verification tools:

  • Certora: Prove properties mathematically
  • K Framework: Specify and verify semantics
  • SMTChecker: Built into Solidity compiler

The Security Mindset

Assume Everything Can Fail:

  • External calls can revert
  • Users can be malicious
  • Miners can manipulate transactions
  • Code will have bugs

Defense in Depth:

contract SecureVault {
// Multiple layers of security
modifier onlyOwner() { require(msg.sender == owner); _; }
modifier whenNotPaused() { require(!paused); _; }
modifier nonReentrant() { require(!locked); locked = true; _; locked = false; }
modifier validAmount(uint256 amount) { require(amount > 0 && amount <= maxAmount); _; }

function withdraw(uint256 amount)
public
onlyOwner
whenNotPaused
nonReentrant
validAmount(amount)
{
// Multiple checks before executing
}
}

10. Conclusion and Looking Forward

What We've Learned

In this chapter, we've covered the fundamental concepts of smart contracts:

  1. Core Concepts: Smart contracts are autonomous programs on blockchains that execute predefined logic without intermediaries

  2. Evolution: From Bitcoin's limited scripting to Ethereum's Turing-complete platform

  3. Programming Model: Accounts, storage, functions, and composability

  4. Solidity Basics: Data types, functions, modifiers, and Ethereum-specific features

  5. Practical Applications: Dutch auctions, tokens, and real-world use cases

  6. Economics: Gas costs, optimization strategies, and fee mechanisms

  7. Security: Common vulnerabilities and best practices for safe contract development

The Smart Contract Revolution

Smart contracts represent a fundamental shift in how we coordinate economic activity:

Traditional Systems:

Agreement → Trust Intermediary → Execute → Dispute → Arbitrate

Smart Contract Systems:

Agreement → Deploy Contract → Automatic Execution → Cryptographic Guarantee

Current Limitations and Future Directions

Scalability Challenges

  • Ethereum mainnet: ~15-30 TPS
  • Every node processes every transaction
  • State growth is unbounded

Solutions in Development:

  • Layer 2: Rollups, State Channels
  • Sharding: Parallel processing
  • Layer 1 Improvements: EIP-4844 (Proto-Danksharding)

Privacy Limitations

  • All transaction data is public
  • All contract state is visible
  • Limited privacy for commercial applications

Emerging Solutions:

  • Zero-Knowledge Proofs: Privacy-preserving transactions
  • TEEs: Confidential computing
  • Private Smart Contracts: Secret Network, Aztec

Oracle Problem

Smart contracts cannot directly access external data

Current Approaches:

  • Chainlink: Decentralized oracle networks
  • Band Protocol: Data aggregation
  • API3: First-party oracles
  • UMA: Optimistic oracles

User Experience

  • Private key management is difficult
  • Gas fees are confusing
  • Irreversible transactions are scary

Improvements Underway:

  • Account Abstraction: ERC-4337
  • Social Recovery: Multi-sig and guardians
  • Meta-Transactions: Gasless transactions
  • Better Wallets: Improved UX

Real-World Impact

Smart contracts are already transforming:

DeFi (Decentralized Finance):

  • $50B+ Total Value Locked
  • Lending, borrowing, trading without banks
  • Automated market makers
  • Yield farming and liquidity provision

NFTs and Digital Ownership:

  • $40B+ in sales (2021-2023)
  • Digital art, collectibles, gaming
  • Provenance and authenticity
  • Creator royalties

DAOs (Decentralized Autonomous Organizations):

  • Transparent governance
  • Community-owned protocols
  • Collective investment
  • On-chain voting

Supply Chain:

  • Tracking provenance
  • Automated logistics
  • Escrow and payments
  • Compliance verification

Identity and Credentials:

  • Self-sovereign identity
  • Verifiable credentials
  • Educational certificates
  • Professional licenses

The Path Forward

For Developers

  1. Learn the Fundamentals: Understand blockchain concepts deeply
  2. Master Solidity: Practice writing secure, efficient contracts
  3. Study Security: Learn from past exploits
  4. Use Standards: Leverage ERC-20, ERC-721, ERC-4626, etc.
  5. Join the Community: Contribute to open-source projects

For the Ecosystem

  1. Better Tools: Improved development environments
  2. Formal Verification: Mathematical proof of correctness
  3. Auditing Standards: Professional security review processes
  4. Education: Training the next generation of blockchain developers
  5. Regulation: Clarity on legal status and compliance

Final Thoughts

Smart contracts are still in their infancy. We're at a similar stage to the early internet in the 1990s—the potential is clear, but the infrastructure is still maturing.

The vision of programmable, trustless, global coordination is becoming reality. Whether it's:

  • Financial inclusion for the unbanked
  • Censorship-resistant publishing
  • Permissionless innovation for entrepreneurs
  • Transparent governance for organizations

Smart contracts provide the building blocks for a more open, transparent, and accessible digital economy.

As we move into subsequent chapters, we'll explore how these building blocks combine to create the sophisticated DeFi protocols that are revolutionizing finance: automated market makers, lending protocols, synthetic assets, and more.

The revolution is just beginning.


Additional Resources

Official Documentation

Development Tools

Learning Resources

Security

Community


Exercises

Beginner

  1. Deploy a simple storage contract that can set and get a value
  2. Create an ERC-20 token with your own name and symbol
  3. Implement a simple voting contract
  4. Write a contract that tracks ownership of digital assets

Intermediate

  1. Build a Dutch auction contract for NFTs
  2. Create a multisignature wallet requiring 2-of-3 approval
  3. Implement a time-locked token vesting schedule
  4. Write a decentralized raffle/lottery contract

Advanced

  1. Create a lending pool with interest accrual
  2. Build an automated market maker (constant product)
  3. Implement a DAO with proposal and voting mechanisms
  4. Design a flash loan attack detector

Next Chapter:

In the next chapter, we'll explore how smart contracts enable peer-to-peer trading without centralized intermediaries, introducing concepts like liquidity pools, constant product market makers, and impermanent loss.