测试Chainlink智能合约

由于智能合约的不可更改性,在部署之前对其进行彻底的测试是至关重要的。

# 测试Chainlink智能合约 ![](https://img.learnblockchain.cn/2020/11/26_/998597825.png) 由于智能合约的不可更改性,在部署之前对其进行彻底的测试是至关重要的。在编写自动化测试时,开发人员有几个选择。 1. Solidity测试 2. Javascript/python/其他语言测试 通常情况下,用JavaScript和Solidity对[合约进行两种方式的测试](https://medium.com/better-programming/how-to-test-ethereum-smart-contracts-35abc8fa199d)是很有用的,因为大多数dApp都会以这种方式与合约交互,你可以从这个[示例测试仓库](https://github.com/alexroan/truffle-tests-tutorial/blob/master/contracts/Background.sol)中看到。另一方面,当你测试一个主要使用点来自另一个链上合约的合约/库时,最应该使用Solidity。 很明显,为了更加测试更加全面,请同时使用这两种方法。如果你有一个简单的智能合约,比如: ```javascript pragma solidity >=0.5.0; contract Background { uint[] private values; function storeValue(uint value) public { values.push(value); } function getValue(uint initial) public view returns(uint) { return values[initial]; } function getNumberOfValues() public view returns(uint) { return values.length; } } ``` 编写一些Solidity测试非常简单,例如: ```javascript pragma solidity >=0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../../../contracts/Background.sol"; contract TestBackground { Background public background; // Run before every test function function beforeEach() public { background = new Background(); } // Test that it stores a value correctly function testItStoresAValue() public { uint value = 5; background.storeValue(value); uint result = background.getValue(0); Assert.equal(result, value, "It should store the correct value"); } // Test that it gets the correct number of values function testItGetsCorrectNumberOfValues() public { background.storeValue(99); uint newSize = background.getNumberOfValues(); Assert.equal(newSize, 1, "It should increase the size"); } // Test that it stores multiple values correctly function testItStoresMultipleValues() public { for (uint8 i = 0; i < 10; i++) { uint value = i; background.storeValue(value); uint result = background.getValue(i); Assert.equal(result, value, "It should store the correct value for multiple values"); } } } ``` 对于那些想要了解更多关于一般智能合约测试的人,这里有一些额外的来源,你可以查看。 - [Ethereum.org](https://ethereum.org/en/developers/docs/smart-contracts/testing/) - [Truffle](https://www.trufflesuite.com/docs/truffle/testing/testing-your-contracts) - [Hardhat and Waffle](https://hardhat.org/guides/waffle-testing.html) 您至少需要熟悉Truffle或HardHat(以前称为Buidler),才能阅读本文档的其他内容。你也可以从我们之前的一些文章中学习如何[使用Truffle部署和测试Chainlink智能合约](https://www.trufflesuite.com/blog/using-truffle-to-interact-with-chainlink-smart-contracts)。另外你需要明白[单元测试和集成测试](https://www.guru99.com/unit-test-vs-integration-test.html)是不同的,它们各自有非常重要的功能。 然而,当使用Chainlink Oracles和链上数据时,测试可能会变得有点棘手。一些传统的方法并不能完全覆盖每一个结果。在这篇文章中,我们将几乎只关注JavaScript测试,但如果你也想使用Solidity的方式做测试,这些方法也同样适用。 ## 测试Chainlink智能合约的最简单方法 [DeFi Money Market(DMM)](https://github.com/defi-money-market-ecosystem/protocol/blob/3bc19678340c734b452741aeb0cbdbc50f0fe4f6/scripts/initial_deployment/DeployTokens.js)是一个使用测试网来运行Chainlink测试的项目的例子。 测试Chainlink智能合约最简单的方法就是使用测试网!大多数项目会在主网之前部署到测试网上,但他们也可以不断重新部署来迭代他们的测试,因为测试网ETH是免费的。Kovan或Rinkeby上目前有很多Chainlink节点,price feeds,以及任何其他你要找的东西。在你的测试文件中,需要获得一些测试网的LINK和ETH。另一个简单的方法就是运行你自己的Chainlink节点,让它监控你正在运行的本地私有链。 与本地私有区块链相比,在测试网上运行测试并不是特别快。你还会面临触及faucet极限的可能。让我们看看如何在本地私有链测试你的Chainlink智能合约。 ## 使用分叉 [Gelato](https://github.com/gelatodigital/gelato-instadapp/blob/3da14b7e90720a1b15b07c27a4805adac17002ca/hardhat.config.js)是一个使用分叉和Chainlink的项目例子。 [Chainlink Price Feeds](https://docs.chain.link/docs/get-the-latest-price)是Chainlink提供的最受欢迎的服务之一。Price Feeds 预言机网络聚合了来自去中心化的独立来源的数据,并在链上创建了一个真实的数据源。问题是,你如何测试你是否正确使用了这些价格数据? - 你是否部署自己的price feed? - 你是否直接忽略测试price feed? - 你是否完全跳过测试并祈祷你的dApp不会崩溃? 现在,我们非常欢迎你做第三种选择,但我们不鼓励你这样做,尤其是测试它们其实是一件很容易的事情。我们需要做的就是将我们正在使用的[链进行分叉](https://hardhat.org/guides/mainnet-forking.html)。如果你之前没有使用过Chainlink Price Feeds,请务必查看我们的[文档](https://docs.chain.link/docs/using-chainlink-reference-contracts)。本节的所有代码都可以在 [chainlink-hardhat代码仓库](https://github.com/PatrickAlphaC/chainlink-hardhat) 中找到。Hardhat是一个类似于Truffle的框架,但有很多不错的质量很好并且有一定的差异化。 假设我们有一个使用Chainlink Price Feeds的合同,看起来像这样: ```javascript pragma solidity ^0.6.6; import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol"; contract PriceConsumerV3 { AggregatorV3Interface internal priceFeed; /** * Network: Mainnet * Aggregator: ETH/USD * Address: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 */ constructor() public { priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); } /** * Returns the latest price */ function getLatestPrice() public view returns (int) { ( uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound ) = priceFeed.latestRoundData(); // If the round is not complete yet, timestamp is 0 require(timeStamp > 0, "Round not complete"); return price; } } ``` 首先,我们正在使用主网price feed地址,但请不要担心,我们是故意这样做的。通常,要与主网price feed互动,我们必须部署在主网上。但是实际上,我们可以在运行测试时分叉链,查看如果将合约部署在主网上的情况会是什么样子,而无需实际在主网上进行部署。使用[HardHat的设置](https://hardhat.org/),我们只需将分叉的相关配置添加到hardhat.config.js文件中即可。 我们的hardhat.config.js文件如下所示: ```javascript require("@nomiclabs/hardhat-waffle") module.exports = { defaultNetwork: "hardhat", networks: { hardhat: { forking: { url: process.env.ALCHEMY_MAINNET_RPC_URL } }, kovan: { url: process.env.KOVAN_RPC_URL, accounts: { mnemonic: process.env.MNEMONIC } } }, solidity: "0.6.6", } ``` 您会看到我们的`hardhat`网络有一个`forking`密钥。这意味着,当我们在`hardhat`网络上部署脚本时,我们将首先派生RPC_URL中的内容(此刻设置为`ALCHEMY_MAINNET_RPC_URL`),然后将其部署到该网络中。这对于测试非常有用,因为我们实际上可以将智能合约部署到主网的分叉版本中,并对其价格进行测试。 来尝试一下吧! ```bash git clone https://github.com/PatrickAlphaC/chainlink-hardhat cd chainlink-hardhat yarn npx hardhat test ``` 这将通过在分叉主网来测试我们的智能合约。[Truffle teams](https://www.trufflesuite.com/blog/sandbox-forking-with-truffle-teams)还有一个功能,你可以分叉主网,并基于分叉的网络进行测试。 ## 使用Mocks [Aave](https://github.com/aave/aave-protocol/tree/1ff8418eb5c73ce233ac44bfb7541d07828b273f/contracts/mocks/oracle)是一个使用mocks和Chainlink进行测试的项目的例子。 不幸的是,分叉主网来测试与Chainlink Oracles的交互是行不通的,这是因为我们没有任何Chainlink Oracles监控我们的分叉网络。所以我们经常需要寻找其他方法。测试具有依赖性的对象和服务并不是什么新鲜事,但在编写单元测试时可能会带来困难。一个好的解决方案是[模拟](https://medium.com/@piraveenaparalogarajah/what-is-mocking-in-testing-d4b0f2dbe20a#:~:text=We%20use%20mocking%20in%20unit,behavior%20of%20the%20real%20objects.)所有依赖关系,并将测试仅仅集中在合约本身。 Mocking本质上是用更简单的对象代替复杂的对象,以模拟我们要做的事情的功能。这对于使用Chainlink API Call、Chainlink VRF或任何Chainlink外部适配器的项目来说是非常棒的。通常情况下,工程师会在他们的测试文件夹中创建一个`mocks`文件,其中包含了所有的虚拟mocks。我们可以看到用这样的文件模拟一个ERC20的简单版本,它可以模拟我们在测试时与一个真实的ERC20一起工作。 ```javascript pragma solidity ^0.6.10; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { constructor() public ERC20("MOCK", "MCK") { _mint(msg.sender, 100*10**18); } } ``` 一个更相关的mock将与模拟Chainlink 消费者者一起使用,或者与Chainlink Oracle进行交互的智能合约。看起来像这样: ```javascript pragma solidity ^0.6.10; import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MockOracleClient is ChainlinkClient, Ownable { event Tweet(string content); constructor(address _link) public { link = _link; } function sendTweet(string memory content) external override onlyGovernance { emit Tweet(content); } } ``` 在这个Mock中,我们有`sendTweet`函数--在一个_真实的_Chainlink消费者合约中,它会向一个Chainlink节点发出Chainlink API请求来 "发送一条推特"。然而,在我们的mock中,我们只是发出一个日志,说明发送了一条tweet,这可以是一个简单的方式来虚构得到Chainlink节点的响应。你可以在[tweether repo](https://github.com/tweether-protocol/tweether/tree/master/contracts/mocks)中看到所有这些模拟的操作。那个repo也使用了Truffle和Hardhat的组合,所以你可以看到这两者的良好配合。 你可以看到很多生产项目都在使用这种方法。例如,Aave就使用[Chainlink Mocks](https://github.com/aave/aave-protocol/tree/1ff8418eb5c73ce233ac44bfb7541d07828b273f/contracts/mocks/oracle)来运行他们的测试。 ## 使用助手来部署 最复杂的测试可以在[truffle smartcontractkit mock](https://github.com/smartcontractkit/box/blob/master/test/MyContract_test.js)中找到,这是Chainlink工程师用来构建智能合约的首选工具之一。一旦你安装了Truffle,你可以通过打开一个新的repo,然后运行下面的命令,让你自己的盒子快速运转起来: `truffle unbox smartcontractkit/box` 一旦你安装好这个,你就会看到`MyContract_test.js`,它运行了所有你在调用Chainlink API时想要覆盖的潜在场景。在[Chainlink Truffle repo](https://github.com/smartcontractkit/box)中查看它。 ## 总结 测试Chainlink智能合约是确保你的代码在开发时保持高质量的好方法,上面的一系列选项让测试变得比以往任何时候都要简单。不要以为在测试中运行复杂的对象与彼此之间的测试太困难。当涉及到扩展你的dApp并构建一些惊人的东西时,集成测试是至关重要的。 对于那些希望开始使用这些神奇工具进行构建的人来说,一定要点击示例中的链接,或者直接前往[Chainlink文档](https://docs.chain.link/)。你会发现你需要开始并成为Solidity和区块链工程大师的一切。 [原文链接](https://blog.chain.link/testing-chainlink-smart-contracts/)

测试Chainlink智能合约

测试Chainlink智能合约插图

由于智能合约的不可更改性,在部署之前对其进行彻底的测试是至关重要的。在编写自动化测试时,开发人员有几个选择。

  1. Solidity测试
  2. Javascript/python/其他语言测试

通常情况下,用JavaScript和Solidity对合约进行两种方式的测试是很有用的,因为大多数dApp都会以这种方式与合约交互,你可以从这个示例测试仓库中看到。另一方面,当你测试一个主要使用点来自另一个链上合约的合约/库时,最应该使用Solidity。

很明显,为了更加测试更加全面,请同时使用这两种方法。如果你有一个简单的智能合约,比如:

pragma solidity >=0.5.0;

contract Background {
    uint[] private values;
    function storeValue(uint value) public {
        values.push(value);
    }
    function getValue(uint initial) public view returns(uint) {
        return values[initial];
    }
    function getNumberOfValues() public view returns(uint) {
        return values.length;
    }
}

编写一些Solidity测试非常简单,例如:

pragma solidity >=0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../../../contracts/Background.sol";

contract TestBackground {
    Background public background;
    // Run before every test function
    function beforeEach() public {
        background = new Background();
    }
    // Test that it stores a value correctly
    function testItStoresAValue() public {
        uint value = 5;
        background.storeValue(value);
        uint result = background.getValue(0);
        Assert.equal(result, value, "It should store the correct value");
    }
    // Test that it gets the correct number of values
    function testItGetsCorrectNumberOfValues() public {
        background.storeValue(99);
        uint newSize = background.getNumberOfValues();
        Assert.equal(newSize, 1, "It should increase the size");
    }
    // Test that it stores multiple values correctly
    function testItStoresMultipleValues() public {
        for (uint8 i = 0; i &lt; 10; i++) {
            uint value = i;
            background.storeValue(value);
            uint result = background.getValue(i);
            Assert.equal(result, value, "It should store the correct value for multiple values");
        }
    }
}

对于那些想要了解更多关于一般智能合约测试的人,这里有一些额外的来源,你可以查看。

  • Ethereum.org
  • Truffle
  • Hardhat and Waffle

您至少需要熟悉Truffle或HardHat(以前称为Buidler),才能阅读本文档的其他内容。你也可以从我们之前的一些文章中学习如何使用Truffle部署和测试Chainlink智能合约。另外你需要明白单元测试和集成测试是不同的,它们各自有非常重要的功能。

然而,当使用Chainlink Oracles和链上数据时,测试可能会变得有点棘手。一些传统的方法并不能完全覆盖每一个结果。在这篇文章中,我们将几乎只关注JavaScript测试,但如果你也想使用Solidity的方式做测试,这些方法也同样适用。

测试Chainlink智能合约的最简单方法

DeFi Money Market(DMM)是一个使用测试网来运行Chainlink测试的项目的例子。

测试Chainlink智能合约最简单的方法就是使用测试网!大多数项目会在主网之前部署到测试网上,但他们也可以不断重新部署来迭代他们的测试,因为测试网ETH是免费的。Kovan或Rinkeby上目前有很多Chainlink节点,price feeds,以及任何其他你要找的东西。在你的测试文件中,需要获得一些测试网的LINK和ETH。另一个简单的方法就是运行你自己的Chainlink节点,让它监控你正在运行的本地私有链。

与本地私有区块链相比,在测试网上运行测试并不是特别快。你还会面临触及faucet极限的可能。让我们看看如何在本地私有链测试你的Chainlink智能合约。

使用分叉

Gelato是一个使用分叉和Chainlink的项目例子。

Chainlink Price Feeds是Chainlink提供的最受欢迎的服务之一。Price Feeds 预言机网络聚合了来自去中心化的独立来源的数据,并在链上创建了一个真实的数据源。问题是,你如何测试你是否正确使用了这些价格数据?

  • 你是否部署自己的price feed?
  • 你是否直接忽略测试price feed?
  • 你是否完全跳过测试并祈祷你的dApp不会崩溃?

现在,我们非常欢迎你做第三种选择,但我们不鼓励你这样做,尤其是测试它们其实是一件很容易的事情。我们需要做的就是将我们正在使用的链进行分叉。如果你之前没有使用过Chainlink Price Feeds,请务必查看我们的文档。本节的所有代码都可以在 chainlink-hardhat代码仓库 中找到。Hardhat是一个类似于Truffle的框架,但有很多不错的质量很好并且有一定的差异化。

假设我们有一个使用Chainlink Price Feeds的合同,看起来像这样:

pragma solidity ^0.6.6;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

contract PriceConsumerV3 {

    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Mainnet
     * Aggregator: ETH/USD
     * Address: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
     */
    constructor() public {
        priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
    }

    /**
     * Returns the latest price
     */
    function getLatestPrice() public view returns (int) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        // If the round is not complete yet, timestamp is 0
        require(timeStamp > 0, "Round not complete");
        return price;
    }
}

首先,我们正在使用主网price feed地址,但请不要担心,我们是故意这样做的。通常,要与主网price feed互动,我们必须部署在主网上。但是实际上,我们可以在运行测试时分叉链,查看如果将合约部署在主网上的情况会是什么样子,而无需实际在主网上进行部署。使用HardHat的设置,我们只需将分叉的相关配置添加到hardhat.config.js文件中即可。

我们的hardhat.config.js文件如下所示:

require("@nomiclabs/hardhat-waffle")

module.exports = {
  defaultNetwork: "hardhat",
  networks: {
    hardhat: {
      forking: {
        url: process.env.ALCHEMY_MAINNET_RPC_URL
      }
    },
    kovan: {
      url: process.env.KOVAN_RPC_URL,
      accounts: {
        mnemonic: process.env.MNEMONIC
      }
    }
  },
  solidity: "0.6.6",
}

您会看到我们的hardhat网络有一个forking密钥。这意味着,当我们在hardhat网络上部署脚本时,我们将首先派生RPC_URL中的内容(此刻设置为ALCHEMY_MAINNET_RPC_URL),然后将其部署到该网络中。这对于测试非常有用,因为我们实际上可以将智能合约部署到主网的分叉版本中,并对其价格进行测试。

来尝试一下吧!

git clone https://github.com/PatrickAlphaC/chainlink-hardhat

cd chainlink-hardhat

yarn

npx hardhat test

这将通过在分叉主网来测试我们的智能合约。Truffle teams还有一个功能,你可以分叉主网,并基于分叉的网络进行测试。

使用Mocks

Aave是一个使用mocks和Chainlink进行测试的项目的例子。

不幸的是,分叉主网来测试与Chainlink Oracles的交互是行不通的,这是因为我们没有任何Chainlink Oracles监控我们的分叉网络。所以我们经常需要寻找其他方法。测试具有依赖性的对象和服务并不是什么新鲜事,但在编写单元测试时可能会带来困难。一个好的解决方案是模拟所有依赖关系,并将测试仅仅集中在合约本身。

Mocking本质上是用更简单的对象代替复杂的对象,以模拟我们要做的事情的功能。这对于使用Chainlink API Call、Chainlink VRF或任何Chainlink外部适配器的项目来说是非常棒的。通常情况下,工程师会在他们的测试文件夹中创建一个mocks文件,其中包含了所有的虚拟mocks。我们可以看到用这样的文件模拟一个ERC20的简单版本,它可以模拟我们在测试时与一个真实的ERC20一起工作。

pragma solidity ^0.6.10;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor() public ERC20("MOCK", "MCK") {
        _mint(msg.sender, 100*10**18);
    }
}

一个更相关的mock将与模拟Chainlink 消费者者一起使用,或者与Chainlink Oracle进行交互的智能合约。看起来像这样:

pragma solidity ^0.6.10;

import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MockOracleClient is ChainlinkClient, Ownable {

    event Tweet(string content);

    constructor(address _link) public {
        link = _link;
    }

    function sendTweet(string memory content) external override onlyGovernance {
        emit Tweet(content);
    }
}

在这个Mock中,我们有sendTweet函数--在一个_真实的_Chainlink消费者合约中,它会向一个Chainlink节点发出Chainlink API请求来 "发送一条推特"。然而,在我们的mock中,我们只是发出一个日志,说明发送了一条tweet,这可以是一个简单的方式来虚构得到Chainlink节点的响应。你可以在tweether repo中看到所有这些模拟的操作。那个repo也使用了Truffle和Hardhat的组合,所以你可以看到这两者的良好配合。

你可以看到很多生产项目都在使用这种方法。例如,Aave就使用Chainlink Mocks来运行他们的测试。

使用助手来部署

最复杂的测试可以在truffle smartcontractkit mock中找到,这是Chainlink工程师用来构建智能合约的首选工具之一。一旦你安装了Truffle,你可以通过打开一个新的repo,然后运行下面的命令,让你自己的盒子快速运转起来:

truffle unbox smartcontractkit/box

一旦你安装好这个,你就会看到MyContract_test.js,它运行了所有你在调用Chainlink API时想要覆盖的潜在场景。在Chainlink Truffle repo中查看它。

总结

测试Chainlink智能合约是确保你的代码在开发时保持高质量的好方法,上面的一系列选项让测试变得比以往任何时候都要简单。不要以为在测试中运行复杂的对象与彼此之间的测试太困难。当涉及到扩展你的dApp并构建一些惊人的东西时,集成测试是至关重要的。

对于那些希望开始使用这些神奇工具进行构建的人来说,一定要点击示例中的链接,或者直接前往Chainlink文档。你会发现你需要开始并成为Solidity和区块链工程大师的一切。

原文链接

区块链技术网。

  • 发表于 2020-11-25 11:28
  • 阅读 ( 1191 )
  • 学分 ( 3 )
  • 分类:ChainLink

评论