最近在面试的过程中,被面试官问到一个问题:Call, DelegateCall, StaticCall区别。
1. Call
推荐的方式,但是容易发生重入攻击
contract Called {
uint public number;
function increment() public {
number++;
}
function increment2(uint _increment) public returns (uint) {
number = number + _increment;
return number;
}
}
contract Caller {
address public called = 0xd9145CCE52D386f254917e481eB44e9943F39138;
function callCalled() public returns(bool, bytes memory) {
(bool success,bytes memory data) = called.call(abi.encodeWithSignature("increment()"));
return (success, data);
}
}
function callCalled2() public returns(bool, bytes memory) {
// 带参数的函数签名
(bool success,bytes memory data) = called.call(abi.encodeWithSignature("increment(uint256)",2));
return (success, data);
}
Call 的参数payload 是函数签名,四个字节
返回bool, value
返回值可以使用:
uint decoded = abi.decode(data, (uint256));
进行解码。
Call 的另一个重要的用法
发送ether
address.call{value: 1 ether, gas: 10000}("")
2. Staticcall
和Call 相似,但是不允许改变变量状态。
上面这段代码会报错,原因是更改了变量的状态。
(, bytes memory data) = called.staticcall(abi.encodeWithSignature("number()"));
这种调用view 或者public变量就是合法的。
3. detegatecall
contract Called {
uint public number;
function setNumber(uint _number) public {
number = _number;
}
}
contract Caller {
uint public number;
address public called = 0xd9145CCE52D386f254917e481eB44e9943F39138;
function callSetNumber(uint _number) public {
called.delegatecall(abi.encodeWithSignature("setNumber(uint256)",_number));
}
}
contract Caller2 {
address public called = 0xd9145CCE52D386f254917e481eB44e9943F39138;
uint public number;
function callSetNumber(uint _number) public {
called.delegatecall(abi.encodeWithSignature("setNumber(uint256)",_number));
}
}
Caller 合约在调用的时候,会更改Caller 中的number。并且number变量是可以不声明的。
delegatecall 返回两个值bool, value。
需要注意的是:
在上述的函数中我们在caller中声明了number和called合约中的变量是一个位置。也就是同一个slot(0号位置)。如果改变位置,那么调用callSetNumber方法将会同样修改slot 为0的位置。
也就是Caller2 展示的那样。