Smart Contracts: Complete Beginner's Guide
Vending Machine vs Smart Contract
text
VENDING MACHINE:
1. You select snack ($1)
2. You insert $1
3. Machine checks money is real
4. Machine gives you snack
5. Machine can't change its mind
SMART CONTRACT:
1. You request service
2. You send cryptocurrency
3. Contract verifies payment
4. Contract executes automatically
5. No one can stop it once started๐๏ธ Building Blocks of Smart Contracts
1. Variables = Memory Storage
solidity
// Like Excel cells that store data
contract SimpleStorage {
uint256 public favoriteNumber; // Stores a number
string public favoriteText; // Stores text
address public owner; // Stores wallet address
}2. Functions = Actions
solidity
contract SimpleBank {
mapping(address => uint256) public balances;
// ACTION: Deposit money
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// ACTION: Check balance
function getBalance() public view returns (uint256) {
return balances[msg.sender];
}
}3. Key Programming Concepts
Visibility: Who can call it?
solidity
function anyoneCanCall() public { ... } // ๐ Anyone
function onlyOwnerCanCall() private { ... } // ๐ This contract only
function otherContracts() internal { ... } // ๐ฅ This + child contracts
function externalCall() external { ... } // ๐ Other contracts onlyState Modifiers: What does it do?
solidity
function changeState() public { ... } // โ๏ธ Writes to blockchain
function justReading() public view { ... } // ๐ Reads only (free)
function doMath() public pure { ... } // ๐งฎ Math only (free)๐ Writing Your First Smart Contract
Example 1: Simple Piggy Bank
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PiggyBank {
address public owner;
uint256 public totalSavings;
// Constructor - runs when contract is created
constructor() {
owner = msg.sender; // Set creator as owner
}
// Anyone can add money to the piggy bank
function deposit() public payable {
totalSavings += msg.value;
}
// Only owner can break the piggy bank
function withdraw() public {
require(msg.sender == owner, "Only owner can withdraw!");
payable(owner).transfer(address(this).balance);
totalSavings = 0;
}
// Check how much is in piggy bank
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}Example 2: Simple Voting System
solidity
contract SimpleVoting {
struct Candidate {
string name;
uint256 voteCount;
}
mapping(address => bool) public hasVoted;
mapping(uint256 => Candidate) public candidates;
uint256 public candidatesCount;
// Add a candidate to vote for
function addCandidate(string memory _name) public {
candidatesCount++;
candidates[candidatesCount] = Candidate(_name, 0);
}
// Vote for a candidate (can only vote once)
function vote(uint256 _candidateId) public {
require(!hasVoted[msg.sender], "You already voted!");
require(_candidateId > 0 && _candidateId <= candidatesCount, "Invalid candidate");
hasVoted[msg.sender] = true;
candidates[_candidateId].voteCount++;
}
}๐ Security Fundamentals
The Golden Rule: "Don't Trust, Verify"
โ DANGEROUS Pattern:
solidity
function withdrawAll() public {
// ๐จ Anyone can call this and steal all money!
payable(msg.sender).transfer(address(this).balance);
}โ SECURE Pattern:
solidity
function withdrawAll() public {
// โ
Only owner can withdraw
require(msg.sender == owner, "Only owner can withdraw");
payable(owner).transfer(address(this).balance);
}Common Security Patterns
1. Access Control
solidity
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // Continue with function
}
function changeOwner(address newOwner) public onlyOwner {
owner = newOwner;
}2. Input Validation
solidity
function transfer(address to, uint256 amount) public {
require(to != address(0), "Cannot send to zero address"); // โ
require(amount > 0, "Amount must be positive"); // โ
require(balances[msg.sender] >= amount, "Insufficient funds"); // โ
balances[msg.sender] -= amount;
balances[to] += amount;
}3. Re-entrancy Protection
solidity
// โ VULNERABLE
function withdraw() public {
uint256 amount = balances[msg.sender];
(bool success, ) = msg.sender.call{value: amount}(""); // External call FIRST
balances[msg.sender] = 0; // Update balance LATER ๐จ
}
// โ
SECURE
bool private locked;
function withdraw() public {
require(!locked, "Re-entrancy detected");
locked = true; // Lock before external call
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // Update balance FIRST โ
(bool success, ) = msg.sender.call{value: amount}(""); // External call LAST โ
locked = false;
}๐ฐ Money Handling in Smart Contracts
Sending & Receiving Ether
Receiving Ether:
solidity
// Method 1: payable function
function deposit() public payable {
// msg.value contains the sent ETH
balances[msg.sender] += msg.value;
}
// Method 2: receive function (default)
receive() external payable {
balances[msg.sender] += msg.value;
}Sending Ether:
solidity
function sendEther(address payable recipient, uint256 amount) public {
require(address(this).balance >= amount, "Insufficient contract balance");
// Three ways to send:
recipient.transfer(amount); // Limited to 2300 gas, throws on fail
recipient.send(amount); // Limited to 2300 gas, returns bool
(bool success, ) = recipient.call{value: amount}(""); // All gas, returns bool
}๐ฏ Real-World Examples
Example 3: Escrow Service
solidity
contract Escrow {
address public buyer;
address public seller;
address public arbiter;
uint256 public amount;
bool public released;
constructor(address _seller, address _arbiter) payable {
buyer = msg.sender;
seller = _seller;
arbiter = _arbiter;
amount = msg.value;
}
// Buyer or arbiter can release funds to seller
function releaseToSeller() public {
require(msg.sender == buyer || msg.sender == arbiter, "Not authorized");
require(!released, "Already released");
released = true;
payable(seller).transfer(amount);
}
// Buyer or arbiter can refund to buyer
function refundToBuyer() public {
require(msg.sender == buyer || msg.sender == arbiter, "Not authorized");
require(!released, "Already released");
released = true;
payable(buyer).transfer(amount);
}
}Example 4: Time-Locked Wallet
solidity
contract TimeLockedWallet {
address public owner;
uint256 public unlockTime;
constructor(uint256 _unlockTime) payable {
owner = msg.sender;
unlockTime = _unlockTime;
}
function withdraw() public {
require(msg.sender == owner, "Not owner");
require(block.timestamp >= unlockTime, "Funds still locked");
require(address(this).balance > 0, "No funds available");
payable(owner).transfer(address(this).balance);
}
function timeUntilUnlock() public view returns (uint256) {
if (block.timestamp >= unlockTime) return 0;
return unlockTime - block.timestamp;
}
}๐จ Common Vulnerabilities & Fixes
1. Integer Overflow/Underflow
solidity
// โ VULNERABLE
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
balances[msg.sender] -= amount; // Could underflow!
balances[to] += amount; // Could overflow!
}
// โ
SECURE (Solidity 0.8+ has built-in protection)
function transfer(address to, uint256 amount) public {
// SafeMath is now built-in
balances[msg.sender] -= amount; // Auto-reverts on underflow
balances[to] += amount; // Auto-reverts on overflow
}2. Front-Running Attacks
solidity
// โ VULNERABLE to miners seeing your transaction
function buyTokenAtPrice(uint256 price) public {
require(currentPrice == price, "Price changed");
// Someone could see this and buy before you
}
// โ
MORE SECURE (use commitments)
mapping(address => bytes32) public commitments;
function commitToBuy(bytes32 commitment) public {
commitments[msg.sender] = commitment;
}
function revealBuy(uint256 price, bytes32 secret) public {
require(keccak256(abi.encodePacked(price, secret)) == commitments[msg.sender], "Invalid commitment");
require(currentPrice == price, "Price changed");
// Execute trade
delete commitments[msg.sender];
}๐ ๏ธ Development Best Practices
1. Use Established Patterns
solidity
// Pull over Push payments (avoid forcing transfers)
function claimRefund() public {
uint256 amount = refunds[msg.sender];
refunds[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}2. Event Logging for Transparency
solidity
event Deposit(address indexed user, uint256 amount);
event Withdrawal(address indexed user, uint256 amount);
function deposit() public payable {
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value); // Log to blockchain
}
function withdraw(uint256 amount) public {
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount); // Log to blockchain
}3. Upgradeability Considerations
solidity
// Separate data and logic
contract UserStorage {
mapping(address => uint256) public balances;
address public logicContract;
modifier onlyLogic() {
require(msg.sender == logicContract, "Not authorized");
_;
}
function setBalance(address user, uint256 amount) public onlyLogic {
balances[user] = amount;
}
}
contract UserLogic {
UserStorage public storage;
function deposit() public payable {
uint256 newBalance = storage.balances(msg.sender) + msg.value;
storage.setBalance(msg.sender, newBalance);
}
}๐ Learning Path
Beginner โ Intermediate โ Advanced
Stage 1: Basics (2โ4 weeks)
- Simple storage contracts
- Basic money transfers
- Function modifiers
- Events and logging
Stage 2: Security (4โ6 weeks)
- Common vulnerability patterns
- Testing with Foundry/Hardhat
- Gas optimization
- Access control patterns
Stage 3: Advanced (8+ weeks)
- DeFi protocols (AMMs, lending)
- Upgradeability patterns
- Cross-contract interactions
- Formal verification
Essential Tools
- Remix IDE โ Browser-based development
- Hardhat/Foundry โ Local development frameworks
- Etherscan โ Block explorer
- Metamask โ Wallet for testing
- OpenZeppelin โ Secure contract libraries
๐ Key Takeaways
- Smart contracts are automated agreements that run exactly as programmed
- Security is paramount โ bugs can lead to permanent fund loss
- Test thoroughly โ use multiple testing approaches
- Start simple โ master basics before complex DeFi protocols
- Learn from audits โ study real-world vulnerabilities and fixes
Remember: In blockchain, code is law โ once deployed, it cannot be changed. Always prioritize security over features!