底层调用 call
本章讲解在 Solidity
中,如何使用 call
来调用其它合约的函数。
在一个智能合约中调用另外一个外部智能合约的函数,我们可以通过接口 interface
的方式进行调用。
关于这方面的内容,我们在基础教程中的 interface
章节中已经学习过。
另外,还有一种比较底层的调用方法,就是使用 call
函数。
在 Solidity
中,call
函数用于在智能合约内部调用其它合约的函数,与之进行交互。
call
是一种低级、底层的调用方式,它具有更大的灵活性。
在 Solidity
中,与 call
类似的函数还有 staticcall
、delegatecall
和 callcode
。它们有不同的工作方式和使用场景,我们将分别进行讲解。其中的 callcode
在 Solidity 0.8
及以上版本中已经废弃,我们也不再讲解。
1. call 函数的语法
(bool success, bytes memory result) = address(contractAddress).call{value: valueToSend}(data);
其中的返回值的含义如下:
success
:指示调用外部函数是否成功。
result
:调用的外部函数的返回值。
其中的参数的含义如下:
contractAddress
:要调用的外部合约的地址。
valueToSend
:发送到外部合约的 ETH
数量,它的单位是 wei
。这是一个可选参数,如果无需发送 ETH
,就可以选择忽略这个参数。
data
:发送到外部合约的数据。它是对外部函数签名和参数进行编码,而生成的字节数组。
比如,我们要调用一个外部合约的函数 functionName(uint256)
,那么就需要使用 abi
对函数签名和参数进行编码。
编码方法如下:
abi.encodeWithSignature("functionName(uint256)", parameter)
您如果不清楚的话,可以参考前面的 abi
编码解码章节。
2. call 函数的用法
我们先准备一个被调用的合约,合约中定义了一个函数 setValue
。
另外,这个合约还定义了 receive
函数,使之具有接收 ETH
的能力。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Callee { uint256 public value; function setValue(uint256 _value) external payable returns(uint) { // 将参数值保存到状态变量 value = _value; // 返回收到的参数值 return value; } // 合约具有接收 ETH 能力 receive() external payable { } }
我们使用 Remix
对合约进行编译和部署,就会得到它部署后的地址。比如为: 0x975218C73817F1833b382BF2845B3D1E4eF11836。
下面就是 3 种使用 call
与外部合约进行交互的示例。
2.1 只调用外部合约的函数
我们编写一个智能合约 Caller
,它会调用外部合约 Callee
的函数 setValue(uint256)
,函数的参数值为 8。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Caller { // 调用外部合约的函数 // 参数 contractAddress 是被调用合约的地址 function callExternalFunc(address contractAddress) external returns(bool, bytes memory) { // 对函数签名和参数进行编码 bytes memory data = abi.encodeWithSignature("setValue(uint256)", 8); // 调用外部合约函数 return contractAddress.call(data); } }
在上面的合约中,callExternalFunc
函数功能:调用外部合约 Callee
的函数 setValue(uint256)
。
我们先要对被调用函数 setValue
的函数签名和参数进行编码,再将编码结果作为参数,使用 call
来调用外部合约 Callee
的函数。
我们使用 Remix
对合约进行编译和部署,然后在 callExternalFunc
方法中,填入被调用合约 Callee
的地址 0x975218C73817F1833b382BF2845B3D1E4eF11836,最后点击调用 callExternalFunc
。
我们可以在控制台中看到输出的结果:第一个输出值为 true
,表示调用函数成功。第二个输出值为 8,这是被调用函数的返回值。
这与我们预想的结果是一致的。
2.2 只向外部合约的发送 ETH
我们再编写一个智能合约 Caller
。我们会在这个合约中,通过 call
函数向被调用合约 Callee
发送 1 ETH
。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Caller { // 构造函数 constructor() payable { } // 向外部合约发送以太币 // 参数 contractAddress 是被调用合约的地址 function callExternalFunc(address contractAddress) external returns(bool, bytes memory) { return contractAddress.call{value: 1 ether}(""); } }
在上面的合约中,构造函数的状态可变性为 payable
,所以在部署的时候,可以向合约中存入以太币。
callExternalFunc
函数用于向外部合约 Callee
发送以太币,发送数量放置在 value
属性中,值为 1 ether
。
因为不需要调用外部合约的函数,所以 call
函数的参数设置为空字符串。
我们使用 Remix
对合约进行编译,在部署的时候先存入 10 ETH
,然后再点击 callExternalFunc
方法。
我们可以在控制台中看到输出的结果:第一个输出值为 true
,表示调用成功。第二个输出值为 0,这是被调用函数的返回值。
这时候 Caller
合约的余额已经变成 9 ETH
,另外的 1 ETH
,已经转入了被调用合约 Callee
中。
2.3 调用外部合约的函数,并发送 ETH
我们再编写一个合约 Caller
。我们会在这个合约中,调用 Callee
合约的 setValue(uint256)
函数,参数值为 8,并向 Callee
合约转入 1 ETH
。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Caller { // 构造函数 constructor() payable { } // 向外部合约发送以太币 function callExternalFunc(address contractAddress) external returns(bool, bytes memory) { // 对函数签名和参数进行编码 bytes memory data = abi.encodeWithSignature("setValue(uint256)", 8); // 调用外部外部函数,并发送以太币 return contractAddress.call{value: 1 ether}(data); } }
我们使用 Remix
对合约进行编译,在部署的时候先存入 10 ETH
,然后再点击 callExternalFunc
方法。
我们可以在控制台中看到输出的结果:第一个输出值为 true
,表示调用成功。第二个输出值为 8,这是被调用函数的返回值。
这时候 Caller
合约的余额已经变成 9 ETH
,另外的 1 ETH
,已经转入了被调用合约 Callee
中。
学习本章的内容,您可以参考 receive
和 fallback
两个章节。