【Solidity】合约交互

Delegate Call

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

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

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

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

    function setVars(address _contract, uint _num) public payable {
        (bool success1, ) = _contract.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
        require(success1, "Error delegatecall");
    }
}
  1. 部署 Demo、Proxy 合约

  2. 传入 Demo 合约的地址、数字(这里为 10),设置以太币数量(这里设置为 100),调用 Proxy 合约的 setVars 方法

  3. 查看 Proxy 合约,可以看到 num 值加了 10、sender 值为编辑器地址、value 值加了 100

  4. 查看 Demo 合约,可以看到 num、sender、value 值均未改变

注意:调用合约和实现合约的存储布局必须一致,否则可能会导致数据混乱。

我们替换 Demo 合约 num 和 value 的位置,再重复上述步骤,可以看到 Proxy 合约变成 num 值加了 100、value 值加了 10。这是因为,替换后的 Demo 合约 num 在第 2 个存储槽,value 在第 1 个存储槽,而 Proxy 合约的 num 仍在第 1 个存储槽,value 仍在第 2 个存储槽。



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) {
        return abi.encodeWithSelector(this.func1.selector);
    }

    // 获取 func2 的函数调用数据
    function getFunc2Data() external pure returns (bytes memory) {
        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. 传入 ["TestMultiCall 合约地址", "TestMultiCall 合约地址"]["getFunc1Data 返回的编码数据", "getFunc2Data 返回的编码数据"],调用 MultiCall 的 aggregate 方法,获取的调用结果应该为 [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. 传入 ["func1Data", "func2Data"],调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,获取调用结果,应该返回 [bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]

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

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


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

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



猜你喜欢

转载自blog.csdn.net/Superman_H/article/details/141402867