在Solidity中,call和staticcall是用于与其他合约进行交互的低级函数。这些函数允许合约调用其他合约的函数,发送以太币,以及检查其他合约的状态。
在Solidity中,call
和 staticcall
是用于与其他合约进行交互的低级函数。这些函数允许合约调用其他合约的函数,发送以太币,以及检查其他合约的状态。
call
是一个低级函数,用于调用另一个合约的函数。它允许发送以太币并传递数据。call
可以调用合约中的任何函数,包括未明确定义的函数和支付接收函数(fallback
或 receive
)。
每个类型为 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,开发人员需要相应地处理。
foo
函数的执行可以增加名为num
的状态变量的值。由于 EVM 不关心状态变量,而是在存储槽上操作,所以函数实际上是增加存储的第一个槽中的值,即槽 0。此操作发生在Called
合约的存储中。
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
来调用视图函数和纯函数,以确保合约的状态不被修改。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!