Solidity中的call和staticcall

  • Louis
  • 更新于 2024-07-27 09:02
  • 阅读 1026

在Solidity中,call和staticcall是用于与其他合约进行交互的低级函数。这些函数允许合约调用其他合约的函数,发送以太币,以及检查其他合约的状态。


theme: channing-cyan

基本概念

在Solidity中,callstaticcall 是用于与其他合约进行交互的低级函数。这些函数允许合约调用其他合约的函数,发送以太币,以及检查其他合约的状态。

call

定义和作用

call 是一个低级函数,用于调用另一个合约的函数。它允许发送以太币并传递数据。call 可以调用合约中的任何函数,包括未明确定义的函数和支付接收函数(fallbackreceive)。

每个类型为 address 的变量,都有一个名为 call 的方法。该方法期望参数是要在交易中执行的输入数据,即 ABI 编码的 calldata。在上述情况下,输入数据必须对应于具体调用的函数的签名。我们使用 abi.encodeWithSignature 方法从函数定义生成此签名。

语法

(bool success, bytes memory data) = target.call{value: msg.value, gas: gasLimit}(abi.encodeWithSignature("functionName(params)"));

参数说明

  • target:被调用合约的地址。
  • value:发送的以太币数量(可选)。
  • gas:调用时提供的Gas量(可选)。
  • abi.encodeWithSignature:编码函数签名和参数。

返回值:

  • success:布尔值,表示调用是否成功。
  • data:调用返回的字节数据。

代码示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Receiver {
    // 声明一个状态变量
    uint8 num;
    // 事件 Received:记录调用者地址、发送的以太币数量和消息。
    event Received(address caller, uint amount, string message);

    // 回退函数fallback:任何不匹配合约中函数签名的调用将触发此函数。它外部的,可以接收以太币。
    fallback() external payable {
        emit Received(msg.sender, msg.value, "Fallback was called");
    }

    // 接受字符串消息和一个整数值,能够接收以太币。调用时会触发事件Received,返回传入整数的加1结果。
    function foo(string memory _message, uint _x) public payable returns (uint) {
        emit Received(msg.sender, msg.value, _message);

        num += x; 

        return _x + 1;
    }
}

// 此合约没有定义回退函数fallback。如果调用一个不存在的函数,该合约会自动拒绝并回退交易。
contract ReceiverWithOutFallback {
    event Received(address caller, uint amount, string message);

    function foo(string memory _message, uint _x) public payable returns (uint) {
        emit Received(msg.sender, msg.value, _message);

        return _x + 1;
    }
}

contract Caller {
    // 事件Response:记录调用是否成功及返回数据。
    event Response(bool success, bytes data);

    // 使用低级`call`向目标地址发送以太币并调用其`foo`函数,传入参数`"call foo"`和`123`。限制了调用的最大Gas为5000单位。调用结果记录在事件`Response`中。
    function testCallFoo(address payable _addr) public payable {
        // You can send ether and specify a custom gas amount
        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
        );

        emit Response(success, data);
    }

    // 尝试调用目标合约中不存在的函数`doesNotExist`。如果目标合约定义了回退函数(如`Receiver`),则会触发该函数。调用结果同样记录在事件`Response`中。
    function testCallDoesNotExist(address _addr) public {
        (bool success, bytes memory data) = _addr.call(
            abi.encodeWithSignature("doesNotExist()")
        );

        emit Response(success, data);
    }
}

我们在Receiver合约中声明了一个状态变量num,在Caller合约中调用Receiver合约中的foo函数,num的值会变化。call方法不会验证目标地址是否实际对应于现有合约,也不会验证它是否包含指定的函数

call方法返回一个包含两个值的元组,第一个值是一个布尔值,指示交易成功或者失败,第二值是bytes类型,包含由call执行的函数的返回值,经过 ABI 编码(如果有)。

注意:call方法从不回滚。如果交易不成功,success 将为 false,开发人员需要相应地处理。

在底层EVM做了什么?

foo函数的执行可以增加名为num的状态变量的值。由于 EVM 不关心状态变量,而是在存储槽上操作,所以函数实际上是增加存储的第一个槽中的值,即槽 0。此操作发生在Called合约的存储中。

1721878509338.webp

staticcall

定义和作用

staticcall 是一个低级函数,用于以静态(只读)方式调用另一个合约的函数。staticcall 不允许修改状态(例如,不能改变状态变量、发送以太币或生成事件)。它主要用于调用视图函数或纯函数。

语法

(bool success, bytes memory data) = target.staticcall{gas: gasLimit}(abi.encodeWithSignature("functionName(params)"));

参数说明:

  • target:被调用合约的地址。
  • gas:调用时提供的Gas量(可选)。
  • abi.encodeWithSignature:编码函数签名和参数。

返回值:

  • success:布尔值,表示调用是否成功。
  • data:调用返回的字节数据。

代码示例

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.24;

 function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
     // We need to manually run the static call since the getter cannot be flagged as view
     // bytes4(keccak256("admin()")) == 0xf851a440
     (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
     require(success);
     return abi.decode(returndata, (address));
 }

这段代码展示了如何通过静态调用(staticcall)获取一个代理合约的管理员地址。这种方式在使用透明可升级代理模式(TransparentUpgradeableProxy)时很常见。

总结

  • call:用于常规调用,允许修改状态和发送以太币,使用 call 时要小心,尽量避免使用不必要的低级调用,可能会引入安全风险(例如重入攻击)。
  • staticcall:用于只读调用,不允许修改状态,使用 staticcall 来调用视图函数和纯函数,以确保合约的状态不被修改。
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis