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");
}
}
-
部署 Demo、Proxy 合约
-
传入 Demo 合约的地址、数字(这里为 10),设置以太币数量(这里设置为 100),调用 Proxy 合约的 setVars 方法
-
查看 Proxy 合约,可以看到 num 值加了 10、sender 值为编辑器地址、value 值加了 100
-
查看 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;
}
}
-
部署 TestMultiCall、MultiCall 合约
-
调用 TestMultiCall 的 getFunc1Data、getFunc2Data 方法,获取 func1、func2 的函数调用数据
-
传入
["TestMultiCall 合约地址", "TestMultiCall 合约地址"]
和["getFunc1Data 返回的编码数据", "getFunc2Data 返回的编码数据"]
,调用 MultiCall 的 aggregate 方法,获取的调用结果应该为[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
-
传入
["func1Data", "func2Data"]
,调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,获取调用结果,应该返回[bytes 格式的 func1 的返回值, bytes 格式的 func2 的返回值]
-
观察返回结果,可以看到
bytes 格式的 func1 的返回值
为0x
,这是因为 func1 函数没有返回值 -
查看 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);
}
}
-
部署 Helper、TestMultiDelegateCall 合约
-
调用 Helper 的 getMintData 方法,获取 mint 的函数调用数据 mintData
-
调用 TestMultiDelegateCall 继承的 multiDelegateCall 方法,传入
["mintData", "mintData", "mintData"]
,设置以太数为 1 wei -
用编辑器地址查看 TestMultiDelegateCall 合约的 balanceOf,可以看到传输的以太数为 3 wei