Dynamic Invocation and Fallback Mechanism

Dynamic Calls and Fallback Mechanism

Learning Objectives

  • Master the syntax and use cases of call dynamic calls
  • Understand the calldata data structure (selector + parameter encoding)
  • Master the trigger mechanism of fallback / receive functions
  • Understand the differences between tx, msg, and block context variables

call Dynamic Calls

Basic Syntax

(bool success, bytes memory data) = <address>.call(bytes calldata);
  • call is a method of the address type, used to dynamically call functions of a target contract at runtime.
  • It returns two values: success indicates whether the call was successful, and data is the returned byte data.
  • The return value success must be checked. Ignoring the return value can lead to serious security issues – if the call fails, the program will not automatically revert but will continue to execute subsequent logic.

calldata Data Structure

calldata is binary encoded data passed to contract functions, consisting of two parts:

  1. First 4 bytes: Function selector
  2. selector = bytes4(keccak256("函数签名"))
  3. For example, bytes4(keccak256("setX(uint256)")) gets the selector for the setX function.
  4. Remaining bytes: ABI-encoded parameters

Generation and Decoding

  • Generate calldata: abi.encodeWithSignature(sig, params...)
  • Decode return values: abi.decode(bytes, (types...))

ABI Encoding/Decoding Example

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

contract AbiDecode {
    struct MyStruct {
        string name;
        uint[2] nums;
    }

    function encode(
        uint x,
        address addr,
        uint[] calldata arr,
        MyStruct calldata myStruct
    ) external pure returns (bytes memory) {
        return abi.encode(x, addr, arr, myStruct);
    }

    function decode(bytes calldata data) external pure returns (
        uint x, address addr, uint[] memory arr, MyStruct memory myStruct
    ) {
        (x, addr, arr, myStruct) = abi.decode(data, (uint, address, uint[], MyStruct));
    }
}

call Call Example

The following example demonstrates how to dynamically call a function of another contract using call:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;

contract Callee {
    uint256 public x;

    function setX(uint _x) public returns(uint) {
        x = _x;
        return x;
    }
}

contract Caller {
    uint public xx;
    address calleeAddress;

    constructor(address _callee) {
        calleeAddress = _callee;
    }

    function setCalleeX(uint _x) public {
        // 动态生成 calldata
        bytes memory cd = abi.encodeWithSignature("setX(uint256)", _x);
        (bool succ, bytes memory result) = calleeAddress.call(cd);
        if (!succ) {
            revert("call failed");
        }
        // 解码返回值
        (uint x) = abi.decode(result, (uint));
        xx = x;
    }
}

Key points:
- abi.encodeWithSignature automatically calculates the selector and encodes parameters.
- After calling, succ must be checked; otherwise, if it fails, the program will continue to execute, leading to inconsistent states.
- The returned result is of type bytes and needs to be restored to its specific type using abi.decode.


fallback Function

fallback is a special "fallback" function that is automatically triggered when the called function does not exist in the target contract.

Core Use Cases

  • Proxy Pattern: The core mechanism for upgradeable contracts, where all calls are forwarded to the logic contract via fallback.
  • Transfer Functionality: Plays an important role when receiving Ether (see next chapter for details).

Example

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;

contract Callee {
    uint256 public x;

    function setX(uint _x) public returns(uint) {
        x = _x;
        return x;
    }

    // 当调用不存在的函数时触发
    fallback() external {
        x = 100000000;
    }
}

Note: If the function signature is written incorrectly in Caller (e.g., writing "setY(uint256)" instead of "setX(uint256)"), the call will still "succeed" – but it will trigger the fallback function, and x will be set to 100 million. The fallback function is not for handling typos, but for specifically designed application scenarios (such as proxy patterns).


Context Variables: tx, msg, block

Relationship between Transaction and Block

  • Any contract function call is ultimately triggered by a transaction sent by an EOA (Externally Owned Account).
  • Calls between contracts form a call chain (A calls B, B calls C...).
  • The entire call chain shares the same transaction and is packaged in the same block.

Three Types of Context

Context Scope Key Attributes
tx Global, shared across the entire call chain tx.origin (address of the EOA that initiated the transaction)
msg Local, a new msg is generated for each cross-contract call msg.sender (direct caller), msg.value (attached ETH), msg.data (calldata)
block Current block information block.number, block.timestamp

Transaction and Block Relationship

Rules for Message Context Changes

  1. Inter-contract calls generate a new message (msg.sender becomes the calling contract's address).
  2. When directly called by an EOA, the message is a copy of the transaction (msg.sender == tx.origin).
  3. Internal contract calls (non-external) do not change the message.
  4. external and this will turn an internal call into an inter-contract call (generating a new message).

Example: Observing Changes in msg.sender and tx.origin

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;

contract Callee {
    uint256 public x;
    address public caller;      // msg.sender
    address public eoaaddress;  // tx.origin

    function setX(uint _x) public {
        caller = msg.sender;        // 直接调用者
        eoaaddress = tx.origin;     // 最初的 EOA
        x = _x;
    }
}

contract Caller {
    address calleeAddress;
    address public caller;
    address public eoaaddress;

    constructor(address _callee) {
        calleeAddress = _callee;
    }

    function setCalleeX(uint _x) public {
        caller = msg.sender;        // EOA 地址
        eoaaddress = tx.origin;     // EOA 地址
        Callee callee = Callee(calleeAddress);
        callee.setX(_x);
        // 在 Callee 中:msg.sender = Caller 地址,tx.origin = EOA 地址
    }
}

Call Chain Analysis (EOA -> Caller -> Callee):

Location msg.sender tx.origin
In Caller EOA Address EOA Address
In Callee Caller Contract Address EOA Address

external Keyword and Internal Calls

Calling an external function via this will result in an inter-contract call, generating a new message:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;

contract ExternalDemo {
    address public caller;

    function first() public {
        this.second(); // 通过 this 调用 external 函数,产生新的 message
    }

    function second() external {
        caller = msg.sender; // 这里 msg.sender 是 ExternalDemo 自己的地址!
    }
}

Design Principles

  1. Calls between internal contract functions should avoid generating new contexts (do not use this.xxx()).
  2. If an external function needs to be called internally, it should be changed to public, avoiding the use of the this keyword.
  3. external is only used for scenarios where only external calls are truly needed (saves gas because parameters are read directly from calldata).

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7494

(0)
Walker的头像Walker
上一篇 5 days ago
下一篇 1 day ago

Related Posts

EN
简体中文 繁體中文 English