🔍 View, Pure & Payable: Function Types Explained

Understand which functions read state, modify state, or accept Ether

Previous
Introduction

🔀 Understanding Call Types

Let's dive deep into how different call types work at the EVM level. Understanding these mechanics is crucial for writing efficient contracts.

🎮 Interactive: External Call Flow Visualizer

Watch how execution flows when Contract A calls Contract B

Step 1 of 4
Contract A: Start
1
2
3
4
Contract A: Start
Contract A prepares to call Contract B
Code:
contractB.setValue(42)
Call Stack:
msg.sender: User
Contract A Context
What's happening: Execution begins in Contract A. Current context has User as msg.sender.

🎮 Interactive: Call Type Comparison

Compare different call types side-by-side

CALL vs DELEGATECALL

Aspect📡 Regular CALL🎭 DELEGATECALL
msg.senderCalling contract addressOriginal caller (preserved)
Storage UsedCalled contract's storageCalling contract's storage
Code ExecutedCalled contract's codeCalled contract's code
Use CaseNormal external interactionsProxy patterns, libraries
Gas Cost~2,600 base + execution~2,600 base + execution

📡 External Calls Deep Dive

contract TokenVault {
    IERC20 public token;
    
    // External call to another contract
    function deposit(uint amount) external {
        // CALL opcode used here
        bool success = token.transferFrom(msg.sender, address(this), amount);
        require(success, "Transfer failed");
    }
    
    // External call via this keyword
    function checkBalance() external view returns (uint) {
        // Forces external call even within same contract
        return this.getBalance();
    }
    
    // Will be called externally via this.getBalance()
    function getBalance() public view returns (uint) {
        return token.balanceOf(address(this));
    }
}
Line 7: External call to token contract—uses CALL opcode
Line 13: Using this forces external call to same contract
Why this matters: External calls are expensive but necessary for cross-contract interaction

🎭 Delegatecall: Proxy Pattern

contract Proxy {
    address public implementation;
    uint public value;
    
    // Delegatecall preserves Proxy's storage
    fallback() external payable {
        address impl = implementation;
        assembly {
            // Copy calldata
            calldatacopy(0, 0, calldatasize())
            
            // DELEGATECALL to implementation
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            
            // Copy return data
            returndatacopy(0, 0, returndatasize())
            
            // Return or revert
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

contract Implementation {
    address public implementation;  // Same storage slot as Proxy!
    uint public value;              // Same storage slot as Proxy!
    
    function setValue(uint _value) external {
        value = _value;  // Updates PROXY's storage, not Implementation's!
    }
}
Key Point: Implementation code runs in Proxy's storage context
Storage Layout: Must match exactly between Proxy and Implementation
msg.sender: Preserved—remains original caller, not Proxy address
Use Case: Upgradeable contracts, minimize deployment costs

🔒 Staticcall: Safe Reads

contract Oracle {
    // View function—automatically uses STATICCALL when called externally
    function getPrice() external view returns (uint) {
        return 1500 * 1e18;  // Cannot modify state
    }
}

contract PriceConsumer {
    Oracle oracle;
    
    function checkPrice() external view returns (uint) {
        // STATICCALL ensures oracle cannot modify state
        return oracle.getPrice();
    }
    
    // This would fail—view function cannot call non-view
    function badExample() external view returns (uint) {
        // oracle.updatePrice();  // Compilation error!
        return 0;
    }
}
STATICCALL: Like CALL but reverts if target tries to modify state
When: Automatically used for view/pure external function calls
Benefit: Guarantees read-only access—prevents malicious state changes
Gas: ~1,600 base cost (cheaper than CALL's 2,600)

⚡ Quick Reference

functionName()
Internal call—JUMP opcode, same context
Gas: ~24-100
contract.func()
External call—CALL opcode, new context
Gas: ~2,600+
delegatecall
Caller's storage, target's code
Gas: ~2,600+
staticcall
View/pure external calls only
Gas: ~1,600+