【Solidity】合约交互

Delegate Call

在当前合约通过 delegatecall 借用其他合约的方法,更新当前合约的状态变量

contract B {
    uint public num;
    address public sender;
    uint public value;

    function setVars(uint _num) public payable {
        num = _num;
        sender = msg.sender;
        value = msg.value;
    }
}

contract A {
    uint public num;
    address public sender;
    uint public value;

    function setVars1(address _contract, uint _num) public payable {
        // delegatecall 搭配 encodeWithSignature
        (bool success1, ) = _contract.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
        require(success1, "delegatecall 1 failed");
    }

    function setVars2(address _contract, uint _num) public payable {
        // delegatecall 搭配 encodeWithSelector
        (bool success2, ) = _contract.delegatecall(
            abi.encodeWithSelector(B.setVars.selector, _num)
        );
        require(success2, "delegatecall 2 failed");
    }
}
  1. 部署 B 合约

  2. 传入数字,设置以太币数量,调用 B 合约的 setVars 方法;可以看到 B 合约的 num 被更新为传入的数字、sender 为编辑器地址、value 为设置的以太币数量

  3. 部署 A 合约

  4. 传入 B 合约的地址、数字,设置以太币数量,调用 A 合约的 setVars 方法;可以看到 A 合约的 num 被更新为传入的数字、sender 为编辑器地址、value 为设置的以太币数量

使用 delegatecall 后,只要 B 合约更新、升级了,A 合约就能跟着升级 而无需更新代码,非常方便~



Multi Call

Multi Call 表示在单个交易中调用多个合约函数。

Multi Call 合约通过循环调用多个目标合约的函数,并将结果聚合返回。每个函数调用使用 staticcall,这是一种低级调用方式,不会改变区块链状态。

contract TestMultiCall {
    function func1() external view returns (uint, uint) {
        return (1, block.timestamp);
    }

    function func2() external view returns (uint, uint) {
        return (2, block.timestamp);
    }

    // 返回调用 func1 函数所需的编码数据
    function getFunc1Data() external pure returns (bytes memory) {
        // 使用 abi.encodeWithSelector 编码 func1 函数的选择器, 并返回编码数据
        return abi.encodeWithSelector(this.func1.selector);
    }

    // 返回调用 func2 函数所需的编码数据
    function getFunc2Data() external pure returns (bytes memory) {
        // 使用 abi.encodeWithSelector 编码 func2 函数的选择器, 并返回编码数据
        return abi.encodeWithSelector(this.func2.selector);
    }
}

contract MultiCall {
    function aggregate(
        address[] calldata targets, // 要调用的目标合约地址
        bytes[] calldata data // 每个目标合约的调用数据
    ) external view returns (bytes[] memory) {
        require(targets.length == data.length, "MultiCall: invalid input");
        bytes[] memory results = new bytes[](targets.length);
        for (uint i = 0; i < targets.length; i++) {
            (bool success, bytes memory result) = targets[i].staticcall(
                data[i]
            );
            require(success, "MultiCall: staticcall failed");
            results[i] = result;
        }
        return results;
    }
}
  1. 部署 TestMultiCall、MultiCall 合约

  2. 调用 TestMultiCall 的 getFunc1Data、getFunc2Data 方法,获取调用 func1、func2 函数所需的编码数据

  3. 调用 MultiCall 的 aggregate 方法,传入 ["TestMultiCall 合约地址", "TestMultiCall 合约地址"]["getFunc1Data 返回的编码数据", "getFunc2Data 返回的编码数据"],获取的调用结果应该为 [bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]

  4. 观察返回结果,可以看到 func1、func2 函数返回相同的时间戳



Multi Delegate Call

Multi Delegate Call 允许在单个交易中调用多个合约函数,并在调用过程中共享调用者的上下文。

contract MultiDelegateCall {
    function multiDelegateCall(
        bytes[] calldata data
    ) external payable returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(
                data[i]
            );
            require(success, "Delegate call failed");
            results[i] = result;
        }
    }
}

contract TestMultiDelegateCall is MultiDelegateCall {
    event Log(address caller, string funcName, uint value);

    function func1(uint x, uint y) external {
        emit Log(msg.sender, "func1", x + y);
    }

    function func2() external returns (uint) {
        emit Log(msg.sender, "func2", 123);
        return 123;
    }
}

contract Helper {
    function getFunc1Data(uint x, uint y) external pure returns (bytes memory) {
        return abi.encodeWithSelector(TestMultiDelegateCall.func1.selector, x, y);
    }

    function getFunc2Data() external pure returns (bytes memory) {
        return abi.encodeWithSelector(TestMultiDelegateCall.func2.selector);
    }
}
  1. 部署 Helper、TestMultiDelegateCall 合约

  2. 调用 Helper 的 getFunc1Data、getFunc2Data 方法,获取调用 func1、func2 函数所需的编码数据 func1Data、func2Data

  3. 调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,传入 ["func1Data", "func2Data"],获取调用结果,应该返回 [bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]

  4. 观察返回结果,可以看到 bytes 格式的 func1 的返回值0x,这是因为 func1 函数没有返回值

  5. 查看 TestMultiDelegateCall 的事件日志,可以看到 func1、func2 函数的调用者是编辑器地址


在传输以太时需要注意传输的数量,以免出现不合预期的情况:

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

contract MultiDelegateCall {
    function multiDelegateCall(
        bytes[] calldata data
    ) external payable returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(
                data[i]
            );
            require(success, "Delegate call failed");
            results[i] = result;
        }
    }
}

contract TestMultiDelegateCall is MultiDelegateCall {
    mapping(address => uint) public balanceOf;

    function mint() external payable {
        balanceOf[msg.sender] += msg.value;
    }
}

contract Helper {
    function getMintData() external pure returns (bytes memory) {
        return abi.encodeWithSelector(TestMultiDelegateCall.mint.selector);
    }
}
  1. 部署 Helper、TestMultiDelegateCall 合约

  2. 调用 Helper 的 getMintData 方法,获取调用 mint 函数所需的编码数据 mintData

  3. 调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,传入 ["mintData", "mintData", "mintData"],设置以太数为 1 wei

  4. 用编辑器地址查看 TestMultiDelegateCall 合约的 balanceOf,可以看到以太数为 3 wei



新建合约

我们可以编写一个工厂合约,用于创建其他合约:

contract Account {
    address public caller;
    address public creator;
    uint public value;

    constructor(address _creator) payable {
        caller = msg.sender;
        value = msg.value;
        creator = _creator;
    }
}

contract AccountFactory {
    Account[] public accounts;

    function createAccount(address _creator) public payable {
        // 通过 new 创建合约并传输以太币;  要求 msg.value >= 111
        Account newAccount = new Account{value: 111}(_creator);
        accounts.push(newAccount);
    }
}
  1. 部署 AccountFactory 合约

  2. 传入编辑器地址,设置以太币数量,调用 AccountFactory 合约的 createAccount 方法;查看 accounts 的第 1 个元素,可以看到新创建的 Account 合约地址

  3. 通过 Account 合约地址,将 Account 合约添加到 Remix 中

  4. 点开新创建的 Account 合约,可以看到 creator 为编辑器地址、caller 为 AccountFactory 合约地址、value 为设置的以太币数量


  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值