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 only

State 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

  1. Remix IDE โ€” Browser-based development
  2. Hardhat/Foundry โ€” Local development frameworks
  3. Etherscan โ€” Block explorer
  4. Metamask โ€” Wallet for testing
  5. OpenZeppelin โ€” Secure contract libraries

๐ŸŽ“ Key Takeaways

  1. Smart contracts are automated agreements that run exactly as programmed
  2. Security is paramount โ€” bugs can lead to permanent fund loss
  3. Test thoroughly โ€” use multiple testing approaches
  4. Start simple โ€” master basics before complex DeFi protocols
  5. 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!