Learning Objectives:

  • Master Solidity programming language fundamentals

  • Understand data types, variables, and control structures

  • Learn function declarations, modifiers, and error handling

  • Analyze contract structure, inheritance, and libraries


4.2.1: Introduction to Solidity

What is Solidity?

Solidity is a high-level, statically-typed, object-oriented programming language designed for writing smart contracts on Ethereum and other EVM-compatible blockchains.

 
Solidity Overview:

┌─────────────────────────────────────────────────────────────────────┐
│                    Solidity Characteristics                        │
│                                                                   │
│  • High-level language                                            │
│  • Statically typed                                               │
│  • Object-oriented                                                │
│  • Designed for EVM                                               │
│  • Influenced by C++, Python, JavaScript                         │
│  • File extension: .sol                                          │
│  • Compiles to EVM bytecode                                      │
│                                                                   │
└─────────────────────────────────────────────────────────────────────┘

Solidity Versioning:

Solidity uses semantic versioning and requires explicit version declarations.

Version Declaration:

pragma solidity ^0.8.0;
// ^0.8.0: Any version >=0.8.0 and <0.9.0

pragma solidity >=0.8.0 <0.9.0;
// Explicit range

pragma solidity 0.8.20;
// Exact version

Version Numbers:
- Major: Breaking changes (0.9.0)
- Minor: New features (0.8.20)
- Patch: Bug fixes (0.8.20)

Development Environment:

 
Tools and Frameworks:

1. Remix IDE:
   - Browser-based IDE
   - Quick prototyping
   - Built-in compiler
   - Deployment tools

2. Hardhat:
   - Node.js framework
   - Testing environment
   - Plugin system
   - Debugging tools

3. Foundry:
   - Rust-based framework
   - Fast testing
   - Built-in fuzzing
   - Command-line tools

4. Truffle:
   - Suite development
   - Migration tools
   - Testing framework
   - Network management

4.2.2: Basic Solidity Syntax

Contract Structure:

Basic Contract Structure:

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

contract MyContract {
    // State variables
    uint256 public myNumber;
    address public owner;

    // Events
    event NumberUpdated(uint256 newNumber);

    // Modifiers
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    // Constructor
    constructor() {
        owner = msg.sender;
    }

    // Functions
    function setNumber(uint256 _number) public onlyOwner {
        myNumber = _number;
        emit NumberUpdated(_number);
    }

    function getNumber() public view returns (uint256) {
        return myNumber;
    }
}

Comments:

 
Comment Types:

// Single-line comment

/*
   Multi-line comment
   Can span multiple lines
*/

/// Documentation comment (NatSpec)
/// @param _number The new number to set
/// @return The current number

4.2.3: Data Types

Value Types:

 
 
Type Description Example
bool Boolean value true, false
int Signed integer -10, 0, 5
uint Unsigned integer 0, 5, 10
address Ethereum address 0x742d…
address payable Address that can receive Ether 0x742d…
bytes Dynamic byte array “Hello”
bytes1-bytes32 Fixed byte array bytes32
string UTF-8 string “Hello”
enum User-defined enumeration Status {Pending, Active}
fixed/ufixed Fixed-point numbers (deprecated)

Integer Types:

 
Integer Ranges:

uint8: 0 to 2^8 - 1 (0 to 255)
uint16: 0 to 2^16 - 1
uint32: 0 to 2^32 - 1
uint64: 0 to 2^64 - 1
uint128: 0 to 2^128 - 1
uint256: 0 to 2^256 - 1 (default)

int8: -2^7 to 2^7 - 1
int16: -2^15 to 2^15 - 1
int32: -2^31 to 2^31 - 1
int64: -2^63 to 2^63 - 1
int128: -2^127 to 2^127 - 1
int256: -2^255 to 2^255 - 1 (default)

Gas Optimization:
- Use smaller types when possible
- Use uint256 for arithmetic (more efficient)
- Pack multiple variables in storage

Address Types:

Address Operations:

address addr = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e;

// Send Ether
addr.transfer(amount);          // 2300 gas, throws on failure
(bool success, ) = addr.call{value: amount}("");  // 63/64 gas forward

// Check balance
uint256 balance = addr.balance;

// Call function
(bool success, bytes memory data) = addr.call(abi.encodeWithSignature("func()"));

// Address payable (can receive Ether)
address payable payableAddr = payable(addr);

String and Bytes:

 
String vs Bytes:

string: UTF-8 encoded, dynamic length
bytes: Raw byte array, dynamic length
bytes32: Fixed-length byte array (32 bytes)

String Operations:
string greeting = "Hello, World!";
string concatenated = string.concat(greeting, " Welcome!");

Bytes Operations:
bytes memory data = hex"48656c6c6f";  // "Hello"
uint256 length = data.length;

Arrays:

 
Array Types:

// Fixed-size array
uint256[5] fixedArray;

// Dynamic array
uint256[] dynamicArray;

// Memory array
uint256[] memory memArray = new uint256[](5);

// Array methods
dynamicArray.push(10);          // Add element
dynamicArray.pop();             // Remove last
uint256 length = dynamicArray.length;

// Access
uint256 first = dynamicArray[0];

Structs:

Struct Definition:

struct Person {
    string name;
    uint256 age;
    address wallet;
}

// Usage
Person[] public people;
Person memory newPerson = Person("Alice", 30, 0x123...);

// Assign
people.push(newPerson);
people.push(Person("Bob", 25, 0x456...));

// Access
Person storage person = people[0];
string name = person.name;

Mappings:

Mapping Definition:

mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;

// Usage
balances[msg.sender] = 1000;
uint256 balance = balances[msg.sender];

// Mapping is like a dictionary
// Keys can be any type except complex types
// Values can be any type
// Not iterable (no length or enumeration)

Enums:

Enum Definition:

enum Status {
    Pending,
    Active,
    Inactive,
    Completed
}

// Usage
Status public currentStatus = Status.Pending;

function setActive() public {
    currentStatus = Status.Active;
}

function isPending() public view returns (bool) {
    return currentStatus == Status.Pending;
}

4.2.4: Variables and Scope

Variable Types:

Variable Types:

1. State Variables:
   - Stored on blockchain
   - Persistent between function calls
   - Expensive to modify

2. Local Variables:
   - Temporary within function
   - Stored in memory
   - No persistent storage

3. Global Variables:
   - Available in all functions
   - Provide blockchain data
   - msg, block, tx, etc.

4. Constants:
   - Immutable at compile-time
   - Not stored in storage
   - Lower gas cost

5. Immutable Variables:
   - Set at construction time
   - Cannot be changed
   - Lower gas cost

Global Variables:

Global Variables (msg):

msg.sender:  Caller address
msg.value:   Amount of Ether sent
msg.data:    Complete calldata
msg.sig:     Function signature (first 4 bytes)

Global Variables (block):

block.timestamp:       Current block timestamp
block.number:          Current block number
block.difficulty:      Current block difficulty
block.coinbase:        Block validator address
block.gaslimit:        Block gas limit
block.chainid:         Current chain ID

Global Variables (tx):

tx.gasprice:  Transaction gas price
tx.origin:    Original sender (can be different from msg.sender)

Other:

gasleft():    Remaining gas
now:          Alias for block.timestamp

4.2.5: Functions

Function Types:

 
Function Declaration:

function functionName(
    // Parameters
    uint256 param1,
    address param2
) 
    // Visibility
    public
    // Mutability
    view
    // Returns
    returns (uint256, address)
{
    // Function body
}

Visibility:
- public: Accessible from anywhere
- private: Only within contract
- internal: Within contract and derived contracts
- external: Only from external calls

Mutability Modifiers:

 
 
Modifier Description Gas Cost
view Reads state, no modification Low
pure No state access, no modification Low
payable Can receive Ether Higher
nonpayable Cannot receive Ether Default
default Can modify state High

Function Examples:

text
View Function:
function getBalance() public view returns (uint256) {
    return address(this).balance;
}

Pure Function:
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
}

Payable Function:
function deposit() public payable {
    // msg.value available
    emit Deposit(msg.sender, msg.value);
}

Multiple Returns:
function getData() public view returns (uint256, address, bool) {
    return (123, msg.sender, true);
}

4.2.6: Modifiers

Modifier Definition:

text
Modifier Structure:

modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;  // Function body executes here
}

modifier validAddress(address _addr) {
    require(_addr != address(0), "Invalid address");
    _;
}

modifier whenNotPaused() {
    require(!paused, "Contract paused");
    _;
}

Modifier Usage:

text
Using Modifiers:

contract MyContract {
    address public owner;
    bool public paused;
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier whenNotPaused() {
        require(!paused, "Paused");
        _;
    }
    
    // Function with modifiers
    function withdraw(uint256 amount) 
        public 
        onlyOwner 
        whenNotPaused 
    {
        // Function body
    }
    
    // Modifier with parameters
    modifier minimumAmount(uint256 minAmount) {
        require(msg.value >= minAmount, "Amount too low");
        _;
    }
}

4.2.7: Error Handling

Error Types:

text
Error Handling Methods:

1. require():
   - Check conditions
   - Refund remaining gas
   - Use for input validation

2. assert():
   - Check invariants
   - Consume all gas on failure
   - Use for internal errors

3. revert():
   - Manual revert
   - Refund remaining gas
   - Use for complex conditions

4. Custom Errors (Solidity 0.8.4+):
   - More gas efficient
   - Better debugging
   - Use for common errors

Implementation:

text
Error Handling Examples:

// Using require
function setValue(uint256 _value) public {
    require(_value > 0, "Value must be positive");
    value = _value;
}

// Using assert
function withdraw(uint256 amount) public {
    uint256 balance = balances[msg.sender];
    assert(balance >= amount);
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}

// Using revert
function transfer(address to, uint256 amount) public {
    if (amount > balances[msg.sender]) {
        revert("Insufficient balance");
    }
    // Transfer logic
}

// Custom errors
error InsufficientBalance(uint256 requested, uint256 available);
error Unauthorized(address caller);

function withdraw(uint256 amount) public {
    if (amount > balances[msg.sender]) {
        revert InsufficientBalance({
            requested: amount,
            available: balances[msg.sender]
        });
    }
    // Transfer logic
}

4.2.8: Events

Event Definition:

text
Event Declaration:

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

Event Fields:
- indexed: Up to 3 indexed fields
- Indexed fields can be filtered
- Non-indexed fields stored in data

Event Example:
contract ERC20 {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    function transfer(address to, uint256 value) public {
        // Logic
        emit Transfer(msg.sender, to, value);
    }
}

4.2.9: Inheritance

Inheritance Syntax:

text
Single Inheritance:

contract Base {
    uint256 public data;
    
    function setData(uint256 _data) public {
        data = _data;
    }
}

contract Derived is Base {
    function getData() public view returns (uint256) {
        return data;
    }
}

Multiple Inheritance:

text
Multiple Inheritance:

contract A {
    function foo() public virtual pure returns (string memory) {
        return "A";
    }
}

contract B {
    function foo() public virtual pure returns (string memory) {
        return "B";
    }
}

contract C is A, B {
    // Inheritance order: A then B
    // C.foo() returns "A"
    
    function foo() public override(A, B) pure returns (string memory) {
        return super.foo();  // Returns "A"
    }
}

Super Keyword:

text
Using Super:

contract Parent {
    uint256 public value;
    
    function setValue(uint256 _value) public virtual {
        value = _value;
    }
}

contract Child is Parent {
    function setValue(uint256 _value) public override {
        // Additional logic
        require(_value > 0, "Must be positive");
        super.setValue(_value);  // Call parent
    }
}

4.2.10: Libraries

Library Definition:

text
Library Syntax:

library Math {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }
    
    function subtract(uint256 a, uint256 b) internal pure returns (uint256) {
        require(a >= b, "Subtraction overflow");
        return a - b;
    }
}

Using Libraries:

text
Library Usage:

contract MyContract {
    using Math for uint256;
    
    function calculate(uint256 a, uint256 b) public pure returns (uint256) {
        return a.add(b);  // Using library method
    }
}

ADDITIONAL DEEP TECHNICAL NOTES:

1. Solidity Assembly (Yul)

text
Assembly Usage:

function assemblyExample() public pure returns (uint256) {
    uint256 result;
    assembly {
        // Yul assembly
        let x := 10
        let y := 20
        result := add(x, y)
        
        // Memory operations
        let free := mload(0x40)
        mstore(free, 0x12345678)
        result := mload(free)
    }
    return result;
}

2. Gas Optimization Techniques

text
Gas Optimization Tips:

1. Use uint256 for arithmetic:
   - Cheaper for calculations
   - Default type is uint256

2. Pack storage variables:
   - uint8, uint16, uint32, etc.
   - Max of 32 bytes per slot

3. Use short-circuit evaluation:
   - if (conditionA && conditionB) stops early

4. Use view/pure when possible:
   - No gas for reading

5. Use custom errors (0.8.4+):
   - Cheaper than require strings

6. Use events for logs:
   - Events are cheaper than storage

7. Avoid loops when possible:
   - Loops cost gas per iteration

8. Use external functions:
   - Cheaper than public (calldata vs memory)

9. Use calldata for read-only data:
   - Cheaper than memory

10. Use immutable variables:
    - Cheaper than storage

3. Common Solidity Patterns

text
Common Patterns:

1. Withdrawal Pattern:
   function withdraw() public {
       uint256 amount = balances[msg.sender];
       require(amount > 0, "No balance");
       balances[msg.sender] = 0;
       payable(msg.sender).transfer(amount);
   }

2. Checks-Effects-Interactions:
   function withdraw() public {
       // Checks
       require(amount > 0, "Invalid amount");
       
       // Effects
       balances[msg.sender] -= amount;
       
       // Interactions
       payable(msg.sender).transfer(amount);
   }

3. Restrict Access:
   modifier onlyOwner() {
       require(msg.sender == owner, "Not owner");
       _;
   }

4. Emergency Stop:
   modifier whenNotPaused() {
       require(!paused, "Paused");
       _;
   }

4. Solidity Security Considerations

text
Security Best Practices:

1. Reentrancy Protection:
   - Use checks-effects-interactions
   - Use reentrancy guard

2. Integer Overflow/Underflow:
   - Use SafeMath (pre-0.8.0)
   - Solidity 0.8.0+ has built-in checks

3. Access Control:
   - Use modifiers
   - Use Ownable pattern

4. Gas Limits:
   - Avoid unbounded loops
   - Use pagination

5. Randomness:
   - Use chainlink VRF
   - Avoid block hash

6. Front-running:
   - Use commit-reveal pattern
   - Use time locks

7. Denial of Service:
   - Avoid loops with external calls
   - Use withdrawal pattern