Solidity中的call和staticcall

  • Louis
  • 更新于 5天前
  • 阅读 136

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

基本概念

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

call

定义和作用

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

语法

(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 {
    // 事件 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);

        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);
    }
}

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 来调用视图函数和纯函数,以确保合约的状态不被修改。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
0x2b75...1A7D
区块链开发工程师,技术交流或者有工作机会可加VX: magicalLouis