21. solidity 调用其他合约

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中编译合约后,分别部署OtherContractCallContract

deploy contract0 in remix

deploy contract1 in remix

deploy contract2 in remix

1. 传入合约地址

我们可以在函数里传入目标合约地址,生成目标合约的引用,然后调用目标函数。以调用OtherContract合约的setX函数为例,我们在新合约中写一个callSetX函数,传入已部署好的OtherContract合约地址_AddresssetX的参数x

    function callSetX(address _Address, uint256 x) external{
        OtherContract(_Address).setX(x);
    }

复制OtherContract合约的地址,填入callSetX函数的参数中,成功调用后,调用OtherContract合约中的getX验证x变为123

call contract1 in remix

call contract2 in remix

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的值

call contract3 in remix

3. 创建合约变量

我们可以创建合约变量,然后通过它来调用目标函数。下面例子,我们给变量oc存储了OtherContract合约的引用:

    function callGetX2(address _Address) external view returns(uint x){
        OtherContract oc = OtherContract(_Address);
        x = oc.getX();
    }

复制OtherContract合约的地址,填入callGetX2函数的参数中,调用后成功获取x的值

call contract4 in remix

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

call contract5 in remix

转账后,我们可以通过Log事件和getBalance()函数观察目标合约ETH余额的变化。

call contract6 in remix

习题

  1. 智能合约调用其他智能合约这一功能,主要起到了方便代码复用的作用

    我们首先定义了一个接口 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;
        }
    }
    
  2. 假设我们部署了上述合约 OtherContract,其合约地址为

    0xd9145CCE52D386f254917e481eB44e9943F39138

    我们希望在另一个合约中调用该合约,考虑如下两种方式:

    (1) OtherContract other = OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138)
    
    (2) IOtherContract other = IOtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138)
    

    两种写法均是调用其他合约的正确写法

  3. 我们新写了一个合约,并在合约中调用上述已部署的合约 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 的权限没有门槛,是很大安全隐患

  4. (1) 在 0xd9145CCE52D386f254917e481eB44e9943F39138 调用函数 getX();
    (2) 在 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99 调用函数 call_getX();
    

    (1)(2) 的返回结果分别是 10, 20

  5. 我们可以看到,OtherContract 中 setX 函数是 payable 的。如果我们想在已部署的合约 0xd9145CCE52D386f254917e481eB44e9943F39138 中调用 setX 的时候向合约转账 50 wei,那么正确的写法可以是:
    OtherContract(0xd9145CCE52D386f254917e481eB44e9943F39138).setX{value: 50}(x);
    

猜你喜欢

转载自blog.csdn.net/qq_42465670/article/details/129827139
21.