Learning Objectives:

  • Master advanced Solidity features and patterns

  • Understand contract interactions and delegates

  • Learn about gas optimization and security patterns


4.3.1: Advanced Data Structures

Nested Mappings:

text
Nested Mapping Example:

contract MultiLevelMapping {
    // Mapping of address to mapping of address to uint256
    mapping(address => mapping(address => uint256)) public allowances;
    
    function approve(address spender, uint256 amount) public {
        allowances[msg.sender][spender] = amount;
    }
    
    function getAllowance(address owner, address spender) 
        public 
        view 
        returns (uint256) 
    {
        return allowances[owner][spender];
    }
}

// Three-level mapping
mapping(address => mapping(address => mapping(uint256 => bool))) public permissions;

// Usage:
// permissions[user][contract][actionId] = true/false

Custom Structs with Mappings:

text
Struct with Nested Mapping:

struct User {
    string name;
    uint256 balance;
    mapping(uint256 => bool) permissions;
}

mapping(address => User) public users;

function addUser(address _address, string memory _name) public {
    users[_address].name = _name;
}

function grantPermission(address _user, uint256 _permissionId) public {
    users[_user].permissions[_permissionId] = true;
}

function hasPermission(address _user, uint256 _permissionId) 
    public 
    view 
    returns (bool) 
{
    return users[_user].permissions[_permissionId];
}

Arrays of Structs:

text
Struct Array:

struct Product {
    uint256 id;
    string name;
    uint256 price;
}

Product[] public products;
uint256 public productCount;

function addProduct(string memory _name, uint256 _price) public {
    productCount++;
    products.push(Product(productCount, _name, _price));
}

function getProduct(uint256 _id) public view returns (Product memory) {
    require(_id <= productCount, "Product not found");
    return products[_id - 1];  // 1-indexed
}

4.3.2: Function Overloading

text
Function Overloading:

contract OverloadExample {
    function process(uint256 _value) public pure returns (uint256) {
        return _value * 2;
    }
    
    function process(string memory _value) public pure returns (string memory) {
        return string.concat(_value, _value);
    }
    
    function process(address _addr) public view returns (uint256) {
        return _addr.balance;
    }
    
    // Different number of parameters
    function process(uint256 _a, uint256 _b) public pure returns (uint256) {
        return _a + _b;
    }
}

4.3.3: Fallback and Receive Functions

Receive Function:

text
Receive Function:
- Called when contract receives Ether without data
- Must be payable
- Cannot have arguments
- Cannot return anything

receive() external payable {
    // Handle plain Ether transfers
    emit Received(msg.sender, msg.value);
}

Fallback Function:

text
Fallback Function:
- Called when function doesn't exist
- Called when receive() doesn't exist
- Can be payable or non-payable

fallback() external payable {
    // Handle unknown function calls
    // Or forward to another contract
    (bool success, ) = address(forwardTo).call(msg.data);
    require(success, "Forward failed");
}

Complete Example:

text
Advanced Fallback/Receive:

contract AdvancedContract {
    address public forwardTarget;
    mapping(address => uint256) public balances;
    
    event Received(address indexed sender, uint256 amount);
    event Forwarded(address indexed target, bytes data);
    
    constructor(address _target) {
        forwardTarget = _target;
    }
    
    // Receive Ether
    receive() external payable {
        balances[msg.sender] += msg.value;
        emit Received(msg.sender, msg.value);
    }
    
    // Fallback for function calls
    fallback() external payable {
        // If target is set, forward call
        if (forwardTarget != address(0)) {
            (bool success, ) = forwardTarget.call{value: msg.value}(msg.data);
            require(success, "Forward failed");
            emit Forwarded(forwardTarget, msg.data);
        } else {
            // Handle as deposit
            balances[msg.sender] += msg.value;
            emit Received(msg.sender, msg.value);
        }
    }
}

4.3.4: Contract Interactions

Calling Other Contracts:

text
Contract Interaction Methods:

1. Direct Call:
   - Interface defined
   - Type-safe
   - Recommended

2. Low-level Call:
   - More flexible
   - Gas control
   - Error handling

3. Delegate Call:
   - Context preserved
   - Storage in caller
   - Proxy pattern

4. Static Call:
   - View-only
   - No state changes
   - Gas efficient

Interface Implementation:

text
Interface Example:

interface IToken {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract TokenUser {
    IToken public token;
    
    constructor(address _token) {
        token = IToken(_token);
    }
    
    function transferToken(address to, uint256 amount) public returns (bool) {
        return token.transfer(to, amount);
    }
    
    function checkBalance(address account) public view returns (uint256) {
        return token.balanceOf(account);
    }
}

Low-Level Calls:

text
Low-Level Call Example:

contract CallExample {
    address public target;
    
    constructor(address _target) {
        target = _target;
    }
    
    function callFunction(string memory _func, uint256 _value) public {
        // Encode function call
        bytes memory data = abi.encodeWithSignature(_func, _value);
        
        // Call with gas limit and value
        (bool success, bytes memory returnData) = target.call{gas: 100000, value: 0}(data);
        
        require(success, "Call failed");
        
        // Decode return value (if needed)
        uint256 result = abi.decode(returnData, (uint256));
    }
    
    function callWithValue(address payable _target, uint256 _amount) public {
        (bool success, ) = _target.call{value: _amount}("");
        require(success, "Transfer failed");
    }
}

Delegate Call (Proxy Pattern):

text
Delegate Call Example:

// Implementation contract
contract Implementation {
    uint256 public value;
    
    function setValue(uint256 _value) public {
        value = _value;
    }
}

// Proxy contract
contract Proxy {
    address public implementation;
    uint256 public value;  // Storage must match implementation
    
    constructor(address _implementation) {
        implementation = _implementation;
    }
    
    function setImplementation(address _implementation) public {
        implementation = _implementation;
    }
    
    function setValue(uint256 _value) public {
        // Delegate call to implementation
        (bool success, ) = implementation.delegatecall(
            abi.encodeWithSignature("setValue(uint256)", _value)
        );
        require(success, "Delegate call failed");
    }
}

4.3.5: Abstract Contracts and Interfaces

Abstract Contracts:

text
Abstract Contract:
- Contains incomplete implementation
- Cannot be deployed directly
- Must be inherited

abstract contract Animal {
    function makeSound() public virtual returns (string memory);
    
    function eat() public virtual {
        // Common implementation
    }
}

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

Interfaces:

text
Interface:
- No implementation
- Cannot have state variables
- Only function signatures
- No constructors

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address owner) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

4.3.6: Contract Factories

Factory Pattern:

text
Contract Factory:

contract ChildContract {
    address public owner;
    uint256 public number;
    
    constructor(address _owner, uint256 _number) {
        owner = _owner;
        number = _number;
    }
}

contract Factory {
    ChildContract[] public children;
    
    event ChildCreated(address indexed child, address indexed owner, uint256 number);
    
    function createChild(uint256 _number) public {
        ChildContract child = new ChildContract(msg.sender, _number);
        children.push(child);
        emit ChildCreated(address(child), msg.sender, _number);
    }
    
    function getChildrenCount() public view returns (uint256) {
        return children.length;
    }
}

CREATE2 (Deterministic Deployment):

text
CREATE2 Example:

contract Create2Example {
    event Deployed(address indexed addr);
    
    function deploy(bytes32 salt, uint256 number) public {
        // Create bytecode
        bytes memory bytecode = abi.encodePacked(
            type(ChildContract).creationCode,
            abi.encode(msg.sender, number)
        );
        
        address addr;
        assembly {
            addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
        }
        
        require(addr != address(0), "Creation failed");
        emit Deployed(addr);
    }
    
    function predictAddress(bytes32 salt) public view returns (address) {
        bytes memory bytecode = abi.encodePacked(
            type(ChildContract).creationCode,
            abi.encode(msg.sender, 0)
        );
        
        bytes32 hash = keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(bytecode)
        ));
        
        return address(uint160(uint256(hash)));
    }
}

4.3.7: Self-Destruct and Contract Lifecycle

Self-Destruct:

text
Self-Destruct Example:

contract Destructible {
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    function destroy(address payable _receiver) public {
        require(msg.sender == owner, "Not owner");
        selfdestruct(_receiver);
    }
}

// Warning: After selfdestruct, the contract is removed
// Existing code and storage are removed
// ETH is sent to the receiver
// No way to interact with the contract anymore

Contract Lifecycle:

text
Contract States:

1. Creation:
   - Constructor executed
   - Initial state set
   - Contract deployed

2. Active:
   - Functions can be called
   - State can be modified
   - Events emitted

3. Self-Destructed:
   - Code and storage removed
   - ETH sent to specified address
   - No further interactions possible

4. Paused (Emergency):
   - Functions disabled
   - Funds frozen
   - Can be reactivated

4.3.8: Upgradable Contracts

Proxy Pattern (UUPS):

text
UUPS (Universal Upgradeable Proxy Standard):

// Implementation contract
contract ImplementationV1 {
    uint256 public value;
    
    function upgradeTo(address newImplementation) external virtual {
        // UUPS upgrade logic
    }
    
    function setValue(uint256 _value) public virtual {
        value = _value;
    }
}

// Upgradeable Implementation
contract ImplementationV2 is ImplementationV1 {
    uint256 public newVariable;
    
    function setValue(uint256 _value) public override {
        value = _value * 2;  // New behavior
    }
    
    function setNewVariable(uint256 _value) public {
        newVariable = _value;
    }
}

// ERC-1967 Proxy
contract Proxy {
    bytes32 private constant IMPLEMENTATION_SLOT = 
        bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
    
    constructor(address _implementation) {
        _setImplementation(_implementation);
    }
    
    function _setImplementation(address _implementation) private {
        bytes32 slot = IMPLEMENTATION_SLOT;
        assembly {
            sstore(slot, _implementation)
        }
    }
    
    fallback() external payable {
        address impl;
        bytes32 slot = IMPLEMENTATION_SLOT;
        
        assembly {
            impl := sload(slot)
        }
        
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

4.3.9: Gas Optimization Patterns

Storage Packing:

text
Storage Packing Example:

contract StorageEfficient {
    // Bad: 3 storage slots
    uint256 a;      // Slot 0
    uint256 b;      // Slot 1
    uint256 c;      // Slot 2
    
    // Good: 2 storage slots (packed)
    uint128 a;      // Slot 0 (16 bytes)
    uint128 b;      // Slot 0 (16 bytes) - packed
    uint256 c;      // Slot 1
}

Memory vs Storage:

text
Memory vs Storage:

contract MemoryOptimization {
    struct Data {
        uint256 a;
        uint256 b;
        uint256 c;
    }
    
    Data[] public data;
    
    // Bad: Copies to storage
    function badUpdate(uint256 index) public {
        Data memory temp = data[index];  // Copy from storage
        temp.a = 1;
        data[index] = temp;              // Copy back to storage
    }
    
    // Good: Direct storage access
    function goodUpdate(uint256 index) public {
        Data storage temp = data[index];  // Reference to storage
        temp.a = 1;                        // Direct modification
    }
}

Short-Circuit Evaluation:

text
Short-Circuit:

function process(address user) public {
    // Bad: Always executes both conditions
    require(user != address(0) && user.balance > 0, "Invalid");
    
    // Good: Checks cheaper condition first
    require(user.balance > 0 && user != address(0), "Invalid");
}

ADDITIONAL DEEP TECHNICAL NOTES:

1. Solidity Assembly (Yul) Advanced

text
Advanced Assembly:

assembly {
    // Memory management
    let ptr := mload(0x40)
    
    // Memory allocation
    mstore(ptr, 0x12345678)
    
    // Get free memory pointer
    mstore(0x40, add(ptr, 0x20))
    
    // Return data
    return(ptr, 0x20)
    
    // Revert with custom error
    revert(ptr, 0x20)
}

2. Gas Optimization Tips

text
Advanced Gas Optimization:

1. Use immutable for constants:
   uint256 public constant MAX = 100;

2. Use unchecked for safe operations:
   unchecked { a = a + 1; }

3. Pack bools into uint256:
   uint256 public flags;  // Use bit operations

4. Use assembly for complex operations:
   assembly {
       result := add(a, b)
   }

5. Avoid redundant calculations:
   uint256 total = a + b + c;  // One calculation

6. Use external functions over public:
   function externalCall() external {}

Lesson