关于合约之间调用的指令 call
、callcode
、delegatecall
和 staticcall
。
在 EVM 中,有四种与 call 相关的指令,它们分别是:call
、callcode
、delegatecall
和 staticcall
。
call(g, a, v, in, insize, out, outsize)
callcode(g, a, v, in, insize, out, outsize)
callcode
与 call
的区别在于,callcode
在调用合约时只使用被调用合约的代码,但保持在当前合约的上下文中运行。这意味着状态变量的读写和主网币的转移都会发生在调用合约的上下文中,而不是被调用合约的上下文。
delegatecall(g, a, in, insize, out, outsize)
delegatecall
类似于 callcode
,也是在当前合约的上下文中运行被调用合约的代码,但与 callcode
的区别在于,delegatecall
保留了 caller
和 callvalue
。这意味着被调用合约可以访问调用者地址和发送的主网币数额。
staticcall(g, a, in, insize, out, outsize)
staticcall
与 call
类似,主要区别在于 staticcall
不允许修改合约状态,因而具有较低的 gas
成本。它通常用于查询合约状态而不做修改的场景。
![](https://learnblockchain.cn/css/default/copy.svg)
contract OtherContract {
// 函数选择器: "0c55699c": "x()"
uint256 public x;
// 函数选择器: "4018d9aa": "setX(uint256)"
function setX(uint256 _x) external {
x = _x;
}
}
contract ExternalCalls {
// 使用 call 调用 setX 函数
function externalStateChangingCall(address _a) external payable {
assembly {
mstore(0x00, 0x4018d9aa)
mstore(0x20, 999)
let success := call(gas(), _a, callvalue(), 28, add(4, 32), 0x00, 0x00)
if iszero(success) {
revert(0,0)
}
}
}
}
在这个例子中,externalStateChangingCall
函数使用了 call
指令来调用另一个合约的 setX
函数,并传递一个参数。gas()
用于获取当前执行上下文中剩余的 gas 数量。callvalue()
表示当前调用发送的主网币数量,这里使用了 payable
,如果没有使用 payable
修饰符,该值一定为 0,可以直接填写 0。由于把 0x4018d9aa
和 999
分别放到内存槽 0x00
和 0x20
中,此时内存中为 000...4018d9aa000...999,所以这里起始位置 in
填的是 28(从4018d9aa
开始读取)。另外,insize
是 36,这是因为我们有 4 字节的函数选择器和 32 字节的参数,总共 36 字节。由于 setX 没有返回值,所以最后两个字段都为 0x00。
![](https://learnblockchain.cn/css/default/copy.svg)
contract OtherContract {
// 函数选择器: "9a884bde": "get21()",
function get21() external pure returns(uint256) {
return 21;
}
}
contract ExternalCalls {
// 使用 staticcall 调用 get21 函数
function externalViewCallNoArgs(address _a) external view returns (uint256) {
assembly {
mstore(0x00, 0x9a884bde)
// 000000000000000000000000000000000000000000000000000000009a884bde
// | |
// 28 32
let success := staticcall(gas(), _a, 28, 4, 0x00, 0x20)
if iszero(success) {
revert(0,0)
}
return(0x00, 0x20)
}
}
// 返回值为 21
}
分别部署以上两个合约,然后调用 externalViewCallNoArgs
函数并且填入 OtherContract
合约地址,可以看到返回了 21。mstore(0x00, 0x9a884bde)
先把需要调用的函数选择器放入内存槽 0x00 中。staticcall(gas(), _a, 28, 32, 0x00, 0x20)
由于是只读访问,所以没有 call(g, a, v, in, insize, out, outsize)
的字段 v
。调用后有返回值,0x00 到 (0x00 + 0x20) 是输出数据的内存存放地址。
在 2.4 节中,我们说了 revert 也可以返回数据,在此处填坑,以下就是使用 revert 返回数据的例子。
![](https://learnblockchain.cn/css/default/copy.svg)
contract OtherContract {
// 函数选择器: "73712595": "revertWith999()",
function revertWith999() external pure returns (uint256) {
assembly {
mstore(0x00, 999)
revert(0x0...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!