sodility call和delegatecall
call 和 delegatecall 都是 address 类型的低级成员函数,它用来与其他合约交互。它的返回值为(bool, data),分别对应 call 是否成功以及目标函数的返回值。
call
先来说说call
- call是solidity官方推荐的通过触发fallback或receive函数发送ETH的方法,
_to.call{value: AMOUNT}("")
,后面的传参直接为空,就是发送代币,而不是调用合约方法。 - 不推荐用call来调用另一个合约,因为当你调用不安全合约的函数时,你就把主动权交给了它。推荐的方法仍是声明合约变量后调用函数。
- 当我们不知道对方合约的源代码或ABI,就没法生成合约变量;这时,我们仍可以通过call调用对方合约的函数。
我们可以利用结构化编码函数abi.encodeWithSignature来帮我们生成编码code,然后再通过call来进行调用合约方法。具体的可以看solidity用abi调用智能合约的方法详解一文。
另外call在调用合约时可以指定交易发送的ETH数额和gas:_address.call{value:发送数额, gas:gas数额}(二进制编码)
;
我们来看下具体怎么用,下面是一个简单的合约 call.sol ,foo函数用来接收参数x和msg.value,注意当需要接收外部的代币时,需要指定方法为payable。
// SPDX-License-Identifier: MIT
// call.sol
pragma solidity ^0.8.17;
contract Callled {
uint256 public x;
uint256 public balance;
function foo(uint256 _x) external payable returns (string memory) {
x = _x;
balance = msg.value;
return "foo called";
}
}
再来看看调用合约,用法就是这么简单,Call方法里的address需要是call合约的地址,而如果需要通过call发送代币,则需要加上payable修饰符,且在call方法后面加个大括号,传入需要发送的代币量,此时call合约中的foo方法就可以直接拿到传递过去的msg.value了,同时我们还可以再大括号中指定用多少gas
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CallFoo {
function Call(address _address, uint256 _num) external payable {
(bool success, ) = _address.call{value: msg.value, gas: 300000}(
abi.encodeWithSignature("foo(uint256)", _num)
);
require(success, "call fail");
}
}
部署测试
delegatecall
delegatecall与call类似,是solidity中地址类型的低级成员函数。delegate中是委托/代表的意思,那么delegatecall委托了什么?
当用户A通过合约B来call合约C的时候,执行的是合约C的函数,语境(Context,可以理解为包含变量和状态的环境)也是合约C的:msg.sender是B的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约C的变量上。
而当用户A通过合约B来delegatecall合约C的时候,执行的是合约C的函数,但是语境仍是合约B的:msg.sender是A的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约B的变量上。也就是说合约c只是处理逻辑,修改的状态结果最终会保存在B合约上。
使用方式基本上和call一样,但有一点不一样,就这一点至关重要,delegatecall在调用合约时可以指定交易发送的gas,但不能指定发送的ETH数额
注意:delegatecall有安全隐患,使用时要保证当前合约和目标合约的状态变量存储结构相同,并且目标合约安全,不然会造成资产损失。
什么情况下会用到delegatecall?
目前delegatecall主要有两个应用场景:
代理合约(Proxy Contract):将智能合约的存储合约和逻辑合约分开:代理合约(Proxy Contract)存储所有相关的变量,并且保存逻辑合约的地址;所有函数存在逻辑合约(Logic Contract)里,通过delegatecall执行。因为合约一旦部署上链,是没有办法再进行修改的,此时我们就可以通过代理合约,来进行合约升级,此时只需要将代理合约地址指向新的升级后的逻辑合约即可。
EIP-2535 Diamonds(钻石):钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石是具有多个实施合约的代理合约。
合约升级
就代理合约,下面来举个例子看看:
假设我有一个代理合约delegateContrace.sol,一个逻辑合约A.sol,代码分别如下,有一点需要注意,代理合约和逻辑合约的变量类型以及顺序必须完全一致,并且虽然逻辑合约中修改的状态最终作用于代理合约,但是我们仍然需要声明变量类型,否则编译会报错。
delegateContrace.sol
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;contract delegateContract {
uint256 public x;
string public y;
address public sender;
function setXY(
address _address,
uint256 _x,
string memory _y
) external returns (bool) {
sender = msg.sender;
(bool success, ) = _address.delegatecall(
abi.encodeWithSignature(“setXY(uint256,string)”, _x, _y)
);
return success;
}
}
```A.sol
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;contract A {
uint256 public x;
string public y;
address public sender;
function setXY(
uint256 _x,
string memory _y
) external payable returns (string memory) {
sender = msg.sender;
x = _x;
y = _y;
return “setXY called”;
}
}
```
先分别部署测试一下,我们可以看到,逻辑合约中变量都是初始变量,并没有改变,而代理合约中的变量已经被修改了。
当然,不通过代理合约,我们也可以直接调用逻辑合约A的方法,此时A的变量会改变成为新的值,但是代理合约中的变量并不会随之更改,如下图所示
之前就说了,代理合约可以用来做合约升级,那如果我的处理逻辑需要改变,此时A合约就已经不适用了,那我们需要重新部署一个新合约B,来进行合约升级.
同样的,变量类型及顺序需要完全一致,我们修改了处理逻辑,把数字x加了1,而把字符串前面拼接了new string,下面是合约B的代码
- B.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract B { uint256 public x; string public y; address public sender; function setXY( uint256 _x, string memory _y ) external payable returns (string memory) { sender = msg.sender; x = _x + 1; bytes memory _ba = bytes(_y); bytes memory _bb = bytes("new string "); string memory ret = new string(_ba.length + _bb.length); bytes memory bret = bytes(ret); uint k = 0; for (uint i = 0; i < _bb.length; i++) bret[k++] = _bb[i]; for (uint i = 0; i < _ba.length; i++) bret[k++] = _ba[i]; y = string(ret); return "setXY called"; } }
此时只需要我们把代理合约的地址指向新的逻辑合约B,即可实现合约的升级,此时我们可以看到结果,代理合约中的变量已经变成了 101
和 new string hello
,这说明我们的处理逻辑已经变成了新的,并且代理合约中的变量并没有重置,而是在原有的基础上进行的修改,这样我们就通过delegatecall代理合约是现实了合约升级。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com