Back to Blockchain & Web3

Module 3: Solidity Development

Master the programming language for Ethereum smart contracts

📝 What is Solidity?

Solidity is a programming language specifically designed for writing smart contracts on Ethereum. It looks similar to JavaScript but is designed for blockchain development.

Why Solidity Exists

Regular programming languages like JavaScript or Python weren't designed for blockchain. Solidity was created to handle the unique requirements of smart contracts:

  • • Handling cryptocurrency and value transfers
  • • Running on thousands of computers simultaneously
  • • Being immutable once deployed
  • • Optimizing for gas costs

Key Characteristics

Statically Typed

Variable types must be declared (like TypeScript)

Object-Oriented

Supports contracts, inheritance, and libraries

Compiled Language

Compiles to EVM bytecode before deployment

EVM-Specific

Designed specifically for Ethereum Virtual Machine

📚 Learn More:

🔢 Data Types in Solidity

Solidity has various data types to store different kinds of information. Let's explore the most common ones.

Value Types

Unsigned Integers (uint)

Positive whole numbers only (no negatives)

uint8 smallNumber = 255;        // 0 to 255
uint256 bigNumber = 1000000;    // 0 to 2^256-1 (default)
uint age = 25;                  // uint = uint256

Signed Integers (int)

Can be positive or negative

int8 temperature = -10;         // -128 to 127
int256 balance = -5000;         // Can be negative
int profit = 1000;              // int = int256

Boolean (bool)

True or false values

bool isActive = true;
bool hasAccess = false;
bool isOwner = msg.sender == owner;  // Comparison

Address

Ethereum addresses (20 bytes)

address owner = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb;
address payable recipient;      // Can receive ETH
recipient.transfer(1 ether);    // Send ETH

Bytes

Fixed-size byte arrays

bytes1 singleByte = 0xff;
bytes32 hash;                   // Common for hashes
bytes data = "Hello";           // Dynamic bytes

Reference Types

Arrays

Lists of elements

// Fixed size array
uint[5] fixedArray = [1, 2, 3, 4, 5];

// Dynamic array
uint[] dynamicArray;
dynamicArray.push(10);          // Add element
uint length = dynamicArray.length;

// Array of addresses
address[] public users;

Strings

Text data (expensive to store!)

string public name = "Alice";
string greeting = "Hello, World!";

// Note: Strings are expensive in gas
// Use bytes32 for short strings when possible

Structs

Custom data structures

// Define a struct
struct Person {
    string name;
    uint age;
    address wallet;
}

// Create an instance
Person public alice = Person("Alice", 25, 0x123...);

// Access properties
string memory aliceName = alice.name;

Mappings

Key-value pairs (like dictionaries/objects)

// Mapping from address to balance
mapping(address => uint) public balances;

// Set a value
balances[msg.sender] = 100;

// Get a value
uint myBalance = balances[msg.sender];

// Nested mapping
mapping(address => mapping(address => uint)) public allowances;

💡 Pro Tip: Gas Optimization

Use the smallest data type that fits your needs. uint8 uses less gas than uint256 when packed together. But for single variables, uint256 is often more gas-efficient due to EVM's 256-bit architecture.

⚡ Functions and Modifiers

Functions are the executable units of code in smart contracts. They define what actions your contract can perform.

Function Syntax

function functionName(parameters) visibility modifier returns (returnType) {
    // Function body
}

Function Types

View Functions

Read data but don't modify state (FREE - no gas cost when called externally)

function getBalance() public view returns (uint) {
    return balance;  // Just reading, not changing
}

Pure Functions

Don't read or modify state (FREE - no gas cost)

function add(uint a, uint b) public pure returns (uint) {
    return a + b;  // Pure calculation, no state access
}

Payable Functions

Can receive ETH

function deposit() public payable {
    balance += msg.value;  // msg.value = ETH sent
}

Custom Modifiers

Modifiers are reusable code that can be added to functions to change their behavior. Think of them as "guards" that check conditions before executing a function.

// Define a modifier
modifier onlyOwner() {
    require(msg.sender == owner, "Not the owner");
    _;  // Continue with function execution
}

// Use the modifier
function changeOwner(address newOwner) public onlyOwner {
    owner = newOwner;  // Only owner can call this
}

Common Modifiers:

  • onlyOwner - Restrict to contract owner
  • nonReentrant - Prevent reentrancy attacks
  • whenNotPaused - Only when contract is active

👁️ Visibility Specifiers

Visibility determines who can call a function or access a variable. This is crucial for security!

public

Anyone can call (external users, other contracts, internally)

function publicFunc() public {
    // Anyone can call
}

external

Only external calls (not from within contract)

function externalFunc() external {
    // Only external calls
}

internal

Only this contract and derived contracts

function internalFunc() internal {
    // Only within contract
}

private

Only this contract (not even derived contracts)

function privateFunc() private {
    // Most restrictive
}

🚨 Security Note:

"Private" doesn't mean secret! All data on the blockchain is visible. Private just means other contracts can't call the function. Never store passwords or secrets in smart contracts!

💾 Storage vs Memory vs Calldata

Understanding where data is stored is crucial for gas optimization and avoiding bugs.

Storage

Permanent data stored on the blockchain. Like your computer's hard drive. EXPENSIVE!

uint public balance;  // Stored permanently
mapping(address => uint) balances;  // Storage

Memory

Temporary data during function execution. Like RAM. Cheaper than storage.

function process(string memory name) public {
    // name exists only during function call
}

Calldata

Read-only temporary data for function parameters. Cheapest option!

function process(string calldata name) external {
    // name is read-only, saves gas
}

💡 Gas Optimization Tips:

  • • Use calldata for external function parameters
  • • Use memory for temporary variables in functions
  • • Minimize writes to storage - it's expensive!
  • • Read from storage once, store in memory if used multiple times

🎯 Complete Smart Contract Example

Let's put it all together with a simple but complete smart contract - a basic token.

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

/**
 * @title SimpleToken
 * @dev A basic ERC-20-like token contract
 */
contract SimpleToken {
    // State variables (stored permanently)
    string public name = "SimpleToken";
    string public symbol = "SIM";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    address public owner;
    
    // Mapping to track balances
    mapping(address => uint256) public balanceOf;
    
    // Mapping for allowances (for transfers on behalf)
    mapping(address => mapping(address => uint256)) public allowance;
    
    // Events
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    // Modifier to restrict functions to owner only
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }
    
    // Constructor - runs once when contract is deployed
    constructor(uint256 _initialSupply) {
        owner = msg.sender;
        totalSupply = _initialSupply * 10 ** uint256(decimals);
        balanceOf[msg.sender] = totalSupply;
    }
    
    /**
     * @dev Transfer tokens to another address
     * @param _to Recipient address
     * @param _value Amount to transfer
     */
    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(_to != address(0), "Invalid address");
        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        
        emit Transfer(msg.sender, _to, _value);
        return true;
    }
    
    /**
     * @dev Approve another address to spend tokens on your behalf
     * @param _spender Address authorized to spend
     * @param _value Amount they can spend
     */
    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }
    
    /**
     * @dev Transfer tokens from one address to another (requires approval)
     * @param _from Address to transfer from
     * @param _to Address to transfer to
     * @param _value Amount to transfer
     */
    function transferFrom(address _from, address _to, uint256 _value) 
        public 
        returns (bool success) 
    {
        require(_value <= balanceOf[_from], "Insufficient balance");
        require(_value <= allowance[_from][msg.sender], "Allowance exceeded");
        require(_to != address(0), "Invalid address");
        
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        
        emit Transfer(_from, _to, _value);
        return true;
    }
    
    /**
     * @dev Mint new tokens (only owner can do this)
     * @param _amount Amount of tokens to create
     */
    function mint(uint256 _amount) public onlyOwner {
        totalSupply += _amount;
        balanceOf[owner] += _amount;
        emit Transfer(address(0), owner, _amount);
    }
    
    /**
     * @dev Get balance of an address
     * @param _owner Address to check
     * @return balance The balance
     */
    function getBalance(address _owner) public view returns (uint256 balance) {
        return balanceOf[_owner];
    }
}

Key Concepts in This Contract:

✓ State Variables

name, symbol, balanceOf, etc.

✓ Mappings

Track balances and allowances

✓ Events

Transfer and Approval logging

✓ Modifiers

onlyOwner access control

✓ Functions

transfer, approve, mint, etc.

✓ Visibility

public, view functions

📚 Learn More:

🎯 What's Next?

You now know Solidity basics! In the next module, we'll learn about smart contract security - how to protect your contracts from common vulnerabilities and attacks.