⚡ TECH BLOG
Home
Blog
Tags
About
⚡

Powered by Next.js 15 & Modern Web Tech ⚡

Back to Home

Solidity Smart Contract Basics: A Beginner's Guide

December 15, 2022
solidityethereumblockchainsmart-contracts
Solidity Smart Contract Basics: A Beginner's Guide

Solidity Smart Contract Basics: A Beginner's Guide

Solidity is a statically-typed programming language designed for developing smart contracts on the Ethereum blockchain. This guide covers the fundamentals you need to start writing your own smart contracts.

What is a Smart Contract?

A smart contract is a self-executing program stored on the blockchain that runs when predetermined conditions are met. They are:

  • Immutable - Cannot be changed once deployed
  • Distributed - Runs across all nodes in the network
  • Deterministic - Same input always produces same output

Setting Up the Development Environment

Remix IDE

The easiest way to start is with Remix IDE:

  1. Visit remix.ethereum.org
  2. Create a new file with .sol extension
  3. Start coding!

Local Development

# Install Node.js, then:
npm install -g hardhat

# Create a new project
npx hardhat init

# Install OpenZeppelin (for secure contract libraries)
npm install @openzeppelin/contracts

Your First Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    string public message;

    constructor() {
        message = "Hello, World!";
    }

    function setMessage(string memory _message) public {
        message = _message;
    }

    function getMessage() public view returns (string memory) {
        return message;
    }
}

Solidity Basics

Data Types

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DataTypes {
    // Value Types
    bool public isTrue = true;
    int256 public myInt = -1;
    uint256 public myUint = 1;
    address public myAddress = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e;
    bytes32 public myBytes32 = "Hello";
    
    // Reference Types
    string public myString = "Hello World";
    bytes public myBytes = "Hello";
    uint256[] public myArray = [1, 2, 3];
    
    // Mappings
    mapping(address => uint256) public balances;
    
    // Structs
    struct User {
        string name;
        uint256 age;
        address wallet;
    }
    
    User public user;
    
    // Enums
    enum Status { Pending, Active, Completed }
    Status public currentStatus;
}

Variables and State

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Variables {
    // State variables (stored on blockchain)
    uint256 public stateVariable = 100;
    
    function example() public pure returns (uint256) {
        // Local variables (stored in memory)
        uint256 localVar = 50;
        
        // Local variables are deleted after function execution
        return localVar;
    }
    
    // Constants (computed at compile time)
    uint256 public constant MY_CONSTANT = 1000;
    
    // Immutable (set once in constructor)
    address public immutable OWNER;
    
    constructor() {
        OWNER = msg.sender;
    }
}

Functions

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Functions {
    uint256 private count;
    
    // View function - reads state, doesn't modify
    function getCount() public view returns (uint256) {
        return count;
    }
    
    // Pure function - doesn't read or modify state
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }
    
    // Function that modifies state
    function increment() public {
        count += 1;
    }
    
    // Payable function - can receive ETH
    function deposit() public payable {
        // msg.value contains the sent ETH amount
    }
    
    // Internal function - only callable within contract
    function _internalFunction() internal returns (uint256) {
        return count;
    }
    
    // External function - only callable from outside
    function externalFunction() external returns (uint256) {
        return count;
    }
}

Control Structures

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ControlStructures {
    function exampleIf(uint256 x) public pure returns (string memory) {
        if (x > 10) {
            return "Greater than 10";
        } else if (x == 10) {
            return "Equal to 10";
        } else {
            return "Less than 10";
        }
    }
    
    function exampleLoop(uint256 n) public pure returns (uint256) {
        uint256 sum = 0;
        
        for (uint256 i = 1; i <= n; i++) {
            sum += i;
        }
        
        return sum;
    }
    
    function exampleWhile(uint256 n) public pure returns (uint256) {
        uint256 sum = 0;
        uint256 i = 1;
        
        while (i <= n) {
            sum += i;
            i++;
        }
        
        return sum;
    }
}

Arrays and Mappings

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ArraysAndMappings {
    // Arrays
    uint256[] public dynamicArray;
    uint256[10] public fixedArray;
    
    function addToArray(uint256 value) public {
        dynamicArray.push(value);
    }
    
    function getArrayLength() public view returns (uint256) {
        return dynamicArray.length;
    }
    
    function removeFromArray() public {
        require(dynamicArray.length > 0, "Array is empty");
        dynamicArray.pop();
    }
    
    // Mappings
    mapping(address => uint256) public balances;
    mapping(address => mapping(address => bool)) public isApproved;
    
    function setBalance(uint256 amount) public {
        balances[msg.sender] = amount;
    }
    
    function getBalance(address user) public view returns (uint256) {
        return balances[user];
    }
}

Structs

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Structs {
    struct Book {
        string title;
        string author;
        uint256 pages;
        bool isAvailable;
    }
    
    Book public myBook;
    Book[] public books;
    mapping(uint256 => Book) public bookById;
    
    function createBook(
        string memory _title,
        string memory _author,
        uint256 _pages
    ) public {
        // Method 1: Named parameters
        myBook = Book({
            title: _title,
            author: _author,
            pages: _pages,
            isAvailable: true
        });
        
        // Method 2: Positional parameters
        books.push(Book(_title, _author, _pages, true));
    }
    
    function updateBookAvailability(uint256 index, bool available) public {
        books[index].isAvailable = available;
    }
}

Error Handling

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ErrorHandling {
    uint256 public value;
    
    // require - use for input validation
    function setValue(uint256 _value) public {
        require(_value > 0, "Value must be greater than 0");
        value = _value;
    }
    
    // revert - use for complex conditions
    function complexValidation(uint256 _value) public {
        if (_value == 0) {
            revert("Value cannot be zero");
        }
        if (_value > 100) {
            revert("Value cannot exceed 100");
        }
        value = _value;
    }
    
    // assert - use for invariants
    function assertExample() public view {
        assert(value >= 0); // Should always be true
    }
    
    // Custom errors (cheaper gas)
    error InsufficientBalance(uint256 available, uint256 required);
    
    function customError(uint256 amount) public view {
        if (amount > value) {
            revert InsufficientBalance(value, amount);
        }
    }
}

Events

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Events {
    uint256 public count;
    
    event CountChanged(uint256 oldValue, uint256 newValue, address changer);
    event Transfer(address indexed from, address indexed to, uint256 amount);
    
    function increment() public {
        uint256 oldValue = count;
        count += 1;
        
        emit CountChanged(oldValue, count, msg.sender);
    }
    
    function transfer(address to, uint256 amount) public {
        // ... transfer logic ...
        emit Transfer(msg.sender, to, amount);
    }
}

Inheritance

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Ownable {
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }
    
    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

contract MyContract is Ownable {
    uint256 private value;
    
    event ValueChanged(uint256 newValue);
    
    function setValue(uint256 _value) public onlyOwner {
        value = _value;
        emit ValueChanged(_value);
    }
    
    function getValue() public view returns (uint256) {
        return value;
    }
}

Interface and Abstract Contracts

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Interface - only function signatures
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
}

// Abstract contract - can have implemented functions
abstract contract Animal {
    function makeSound() public virtual returns (string memory);
    
    function sleep() public pure returns (string memory) {
        return "Zzz...";
    }
}

contract Dog is Animal {
    function makeSound() public pure override returns (string memory) {
        return "Woof!";
    }
}

Real-World Example: Simple Token

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleToken {
    string public name;
    string public symbol;
    uint8 public decimals;
    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,
        uint8 _decimals,
        uint256 _totalSupply
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        totalSupply = _totalSupply * 10 ** uint256(_decimals);
        balanceOf[msg.sender] = totalSupply;
    }
    
    function transfer(address to, uint256 value) public returns (bool) {
        require(balanceOf[msg.sender] >= value, "Insufficient balance");
        require(to != address(0), "Invalid recipient");
        
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
        
        emit Transfer(msg.sender, to, value);
        return true;
    }
    
    function approve(address spender, uint256 value) public returns (bool) {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }
    
    function transferFrom(
        address from,
        address to,
        uint256 value
    ) public returns (bool) {
        require(balanceOf[from] >= value, "Insufficient balance");
        require(allowance[from][msg.sender] >= value, "Insufficient allowance");
        require(to != address(0), "Invalid recipient");
        
        balanceOf[from] -= value;
        balanceOf[to] += value;
        allowance[from][msg.sender] -= value;
        
        emit Transfer(from, to, value);
        return true;
    }
}

Gas Optimization Tips

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasOptimization {
    // Use uint256 instead of smaller types (unless packing)
    uint256 public value; // Better than uint8 for single variable
    
    // Pack variables in structs
    struct Packed {
        uint128 a; // 16 bytes
        uint128 b; // 16 bytes
        // Total: 32 bytes (one storage slot)
    }
    
    // Use calldata instead of memory for external functions
    function processData(string calldata data) external pure returns (bytes32) {
        return keccak256(bytes(data));
    }
    
    // Use custom errors instead of strings
    error InvalidValue();
    
    function checkValue(uint256 v) public pure {
        if (v == 0) revert InvalidValue();
    }
    
    // Cache array length in loops
    function sumArray(uint256[] memory arr) public pure returns (uint256) {
        uint256 total = 0;
        uint256 len = arr.length; // Cache length
        
        for (uint256 i = 0; i < len; i++) {
            total += arr[i];
        }
        
        return total;
    }
}

Security Best Practices

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SecurityBestPractices {
    address public owner;
    mapping(address => uint256) public balances;
    
    // Use ReentrancyGuard pattern
    bool private locked;
    
    modifier nonReentrant() {
        require(!locked, "Reentrant call");
        locked = true;
        _;
        locked = false;
    }
    
    // Checks-Effects-Interactions pattern
    function withdraw(uint256 amount) public nonReentrant {
        // 1. Checks
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        // 2. Effects
        balances[msg.sender] -= amount;
        
        // 3. Interactions
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
    
    // Use pull payment pattern
    mapping(address => uint256) public pendingWithdrawals;
    
    function depositFor(address user) public payable {
        pendingWithdrawals[user] += msg.value;
    }
    
    function withdrawPending() public {
        uint256 amount = pendingWithdrawals[msg.sender];
        require(amount > 0, "No pending withdrawal");
        
        pendingWithdrawals[msg.sender] = 0;
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

Conclusion

Solidity is a powerful language for writing smart contracts on Ethereum. By understanding these fundamentals and following best practices, you can create secure and efficient decentralized applications.

Key Takeaways

  • Always use the latest stable Solidity version
  • Follow the Checks-Effects-Interactions pattern
  • Use OpenZeppelin for standard implementations
  • Test thoroughly before deploying
  • Consider gas optimization

Start building your smart contracts today and join the decentralized future!

Share:

💬 Comments