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");
}
}
-
部署 B 合约
-
传入数字,设置以太币数量,调用 B 合约的 setVars 方法;可以看到 B 合约的 num 被更新为传入的数字、sender 为编辑器地址、value 为设置的以太币数量
-
部署 A 合约
-
传入 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;
}
}
-
部署 TestMultiCall、MultiCall 合约
-
调用 TestMultiCall 的 getFunc1Data、getFunc2Data 方法,获取调用 func1、func2 函数所需的编码数据
-
调用 MultiCall 的 aggregate 方法,传入
["TestMultiCall 合约地址", "TestMultiCall 合约地址"]
和["getFunc1Data 返回的编码数据", "getFunc2Data 返回的编码数据"]
,获取的调用结果应该为[bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]
-
观察返回结果,可以看到 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);
}
}
-
部署 Helper、TestMultiDelegateCall 合约
-
调用 Helper 的 getFunc1Data、getFunc2Data 方法,获取调用 func1、func2 函数所需的编码数据 func1Data、func2Data
-
调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,传入
["func1Data", "func2Data"]
,获取调用结果,应该返回[bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]
-
观察返回结果,可以看到
bytes 格式的 func1 的返回值
为0x
,这是因为 func1 函数没有返回值 -
查看 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);
}
}
-
部署 Helper、TestMultiDelegateCall 合约
-
调用 Helper 的 getMintData 方法,获取调用 mint 函数所需的编码数据 mintData
-
调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,传入
["mintData", "mintData", "mintData"]
,设置以太数为 1 wei -
用编辑器地址查看 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);
}
}
-
部署 AccountFactory 合约
-
传入编辑器地址,设置以太币数量,调用 AccountFactory 合约的 createAccount 方法;查看 accounts 的第 1 个元素,可以看到新创建的 Account 合约地址
-
通过 Account 合约地址,将 Account 合约添加到 Remix 中
-
点开新创建的 Account 合约,可以看到 creator 为编辑器地址、caller 为 AccountFactory 合约地址、value 为设置的以太币数量