21. 调用其他合约
调用已部署合约
开发者写智能合约来调用其他合约,这让以太坊网络上的程序可以复用,从而建立繁荣的生态。
很多web3
项目依赖于调用其他合约,比如收益农场(yield farming
)。
这里介绍如何在已知合约代码(或接口)和地址情况下调用目标合约的函数。
目标合约
我们先写一个简单的合约OtherContract
来调用。
contract OtherContract {
uint256 private _x = 0; // 状态变量_x
// 收到eth的事件,记录amount和gas
event Log(uint amount, uint gas);
// 返回合约ETH余额
function getBalance() view public returns(uint) {
return address(this).balance;
}
// 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)
function setX(uint256 x) external payable{
_x = x;
// 如果转入ETH,则释放Log事件
if(msg.value > 0){
emit Log(msg.value, gasleft());
}
}
// 读取_x
function getX() external view returns(uint x){
x = _x;
}
}
这个合约包含一个状态变量_x
,一个事件Log
在收到ETH
时触发,三个函数:
getBalance()
: 返回合约ETH
余额。setX()
:external payable
函数,可以设置_x
的值,并向合约发送ETH
。getX()
: 读取_x
的值。
调用OtherContract
合约
我们可以利用合约的地址和合约代码(或接口)来创建合约的引用:_Name(_Address)
,其中_Name
是合约名,_Address
是合约地址。然后用合约的引用来调用它的函数:_Name(_Address).f()
,其中f()
是要调用的函数。
4个调用该合约的例子:
下面我们介绍4个调用合约的例子:传入合约地址、传入合约变量、创建合约变量、调用合约并发送ETH
在remix中编译合约后,分别部署OtherContract
和CallContract
:
1. 传入合约地址
我们可以在函数里传入目标合约地址,生成目标合约的引用,然后调用目标函数。以调用OtherContract
合约的setX
函数为例,我们在新合约中写一个callSetX
函数,传入已部署好的OtherContract
合约地址_Address
和setX
的参数x
:
function callSetX(address _Address, uint256 x) external{
OtherContract(_Address).setX(x);
}
复制OtherContract
合约的地址,填入callSetX
函数的参数中,成功调用后,调用OtherContract
合约中的getX
验证x
变为123
2. 传入合约变量
我们可以直接在函数里传入合约的引用,只需要把上面参数的address
类型改为目标合约名,比如OtherContract
。下面例子实现了调用目标合约的getX()
函数。
注意该函数参数OtherContract _Address
底层类型仍然是address
,生成的ABI
中、调用callGetX
时传入的参数都是address
类型
function callGetX(OtherContract _Address) external view returns(uint x){
x = _Address.getX();
}
复制OtherContract
合约的地址,填入callGetX
函数的参数中,调用后成功获取x
的值
3. 创建合约变量
我们可以创建合约变量,然后通过它来调用目标函数。下面例子,我们给变量oc
存储了OtherContract
合约的引用:
function callGetX2(address _Address) external view returns(uint x){
OtherContract oc = OtherContract(_Address);
x = oc.getX();
}
复制OtherContract
合约的地址,填入callGetX2
函数的参数中,调用后成功获取x
的值
4. 调用合约并发送ETH
如果目标合约的函数是payable
的,那么我们可以通过调用它来给合约转账:_Name(_Address).f{value: _Value}()
,其中_Name
是合约名,_Address
是合约地址,f
是目标函数名,_Value
是要转的ETH
数额(以wei
为单位)。
OtherContract
合约的setX
函数是payable
的,在下面这个例子中我们通过调用setX
来往目标合约转账。
function setXTransferETH(address otherContract, uint256 x) payable external{
OtherContract(otherContract).setX{value: msg.value}(x);
}
复制OtherContract
合约的地址,填入setXTransferETH
函数的参数中,并转入10ETH
转账后,我们可以通过Log
事件和getBalance()
函数观察目标合约ETH
余额的变化。
习题
-
智能合约调用其他智能合约这一功能,主要起到了方便代码复用的作用
我们首先定义了一个接口 IOtherContract,然后基于此接口实现了一个具体合约 OtherContract: // SPDX-License-Identifier: MIT pragma solidity ^0.8.6; interface IOtherContract { function getBalance() external returns(uint); function setX(uint256 x) external payable; function getX() external view returns(uint x); } contract OtherContract is IOtherContract{ uint256 private _x = 0; event Log(uint amount, uint gas); function getBalance() external view override returns(uint) { return address(this).balance; } function setX(uint256 x) external override payable{ _x = x; if(msg.value > 0){ emit Log(msg.value, gasleft()); } } function getX() external view override returns(uint x){ x = _x; } }
-
假设我们部署了上述合约 OtherContract,其合约地址为
0xd9145CCE52D386f254917e481eB44e9943F39138。
我们希望在另一个合约中调用该合约,考虑如下两种方式:
(1) OtherContract other = OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138) (2) IOtherContract other = IOtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138)
两种写法均是调用其他合约的正确写法
-
我们新写了一个合约,并在合约中调用上述已部署的合约 0xd9145CCE52D386f254917e481eB44e9943F39138,如下: contract MyContract { OtherContract other = OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138); function call_getX() external view returns(uint x){ x = other.getX(); } function call_setX(uint256 x) external{ other.setX(x); } } 部署该合约,其地址为 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99。
这里需要注意,MyContract 的函数 call_setX 可以实现,这意味着 OtherContract 中 setX 的权限没有门槛,是很大安全隐患
-
(1) 在 0xd9145CCE52D386f254917e481eB44e9943F39138 调用函数 getX(); (2) 在 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99 调用函数 call_getX();
(1)(2) 的返回结果分别是 10, 20
-
我们可以看到,OtherContract 中 setX 函数是 payable 的。如果我们想在已部署的合约 0xd9145CCE52D386f254917e481eB44e9943F39138 中调用 setX 的时候向合约转账 50 wei,那么正确的写法可以是: OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138).setX{value: 50}(x);