【Solidity Yul Assembly】3.2 | Calling other contracts

  • 0xE
  • 更新于 2024-08-29 09:51
  • 阅读 839

关于合约之间调用的指令 callcallcodedelegatecallstaticcall

在 EVM 中,有四种与 call 相关的指令,它们分别是:callcallcodedelegatecallstaticcall

  1. call(g, a, v, in, insize, out, outsize)

    • g: 提供给被调用合约的 gas 数量。
    • a: 被调用合约的地址。
    • v: 发送给被调用合约的以太币数量(单位为 wei)。
    • in: 输入数据的内存起始位置。
    • insize: 输入数据的长度。 mem[in…(in+insize))
    • out: 输出数据的内存起始位置。
    • outsize: 输出数据的长度。 mem[out…(out+outsize))
  2. callcode(g, a, v, in, insize, out, outsize)
    callcodecall 的区别在于,callcode 在调用合约时只使用被调用合约的代码,但保持在当前合约的上下文中运行。这意味着状态变量的读写和主网币的转移都会发生在调用合约的上下文中,而不是被调用合约的上下文。

  3. delegatecall(g, a, in, insize, out, outsize)
    delegatecall 类似于 callcode,也是在当前合约的上下文中运行被调用合约的代码,但与 callcode 的区别在于,delegatecall 保留了 callercallvalue。这意味着被调用合约可以访问调用者地址和发送的主网币数额。

  4. staticcall(g, a, in, insize, out, outsize)
    staticcallcall 类似,主要区别在于 staticcall 不允许修改合约状态,因而具有较低的 gas 成本。它通常用于查询合约状态而不做修改的场景。

call 的使用示例

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。由于把 0x4018d9aa999 分别放到内存槽 0x000x20 中,此时内存中为 000...4018d9aa000...999,所以这里起始位置 in 填的是 28(从4018d9aa开始读取)。另外,insize 是 36,这是因为我们有 4 字节的函数选择器和 32 字节的参数,总共 36 字节。由于 setX 没有返回值,所以最后两个字段都为 0x00。

staticcall 的使用示例

基本用法

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) 是输出数据的内存存放地址。

使用 revert 返回数据

在 2.4 节中,我们说了 revert 也可以返回数据,在此处填坑,以下就是使用 revert 返回数据的例子。

contract OtherContract {
    // 函数选择器: "73712595": "revertWith999()",
    function revertWith999() external pure returns (uint256) {
        assembly {
            mstore(0x00, 999)
            revert(0x0...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。工作机会可加v:__0xE__