新型solidity开发堆栈:buidler+ethers+waffle+typescript

以太坊的发展,虽然在感觉上还处于萌芽阶段,但已经取得了不错的进步。当我在2017年开始开发Solidity智能合约和以太坊DAPP时,Truffle和Web3.js是行业标准。这些都是很棒的工具,我对创建它们的人表示敬意。虽然这类工具还不是很完善,在使用过程中会存在少些Bug,但是并不影响我们的正常使用。有一些新的工具显然受到了这些第一组工具的启发,使开发过程变得更好。

我最近参加了一次黑客马拉松,在那我不得不重新回顾并了解事物的当前状态。我发现了很多很酷的工具,但是在文档上却没有太多关于如何使事情协同工作的工具。我决定写下我的发现并创建一个项目,该项目可以作为任何想要构建和测试智能合约和dapp的人的起点。

Buidler(Truffle的替代工具)

Buidler自称是“以太坊智能合约开发人员的任务管理者”。实际上,这意味着该工具将帮助您使用模板引导您的Solidity项目,并为您提供测试智能合约并最终部署到以太坊区块链所需的所有框架。以前是使用Truffle的 init, compile, test和migrate功能来引导Solidity项目是标准过程。Buidler吹捧的杀手级功能就是您的Solidity合约恢复的堆栈跟踪。

Ethers.js(取代Web3.js)

Ethers.js是一个Javascript SDK用于与以太坊区块链进行交互。在开始solidity开发时,我专门使用web3.js很长一段时间。当我第一次尝试Ethers时,我对它的设置如此简单以及API多么出色感到震惊。我敦促曾经使用Web3.js的任何人尝试一下Ethers。它具有使用钱包,帐户和合约的所有必需功能,还具有一些简洁的实用程序,例如ABICoder,HDNode,BigNumber,以及用于十六进制字符串,以太单位和以太坊地址的各种格式化实用程序。

Waffle (Truffle 测试的替代工具)

以太坊Waffle是用于以太坊智能合约的轻量级测试运行器。它内置了一些非常不错的测试工具,例如用于以太坊地址,哈希和BigNumbers的Chai匹配器,它是Typescript原生的,并且与Ethers的搭配非常好。

Typescript

对我来说,Typescript的绝对改变者是IDE集成,它为您提供了所有类属性,对象键,函数参数等的自动完成功能。熟悉Typescript之后,我再也不会回过头来编写原始Javascript了。

我上面提到的所有工具的优点是,它们都可以与Typescript一起很好地协作,并且一旦完成所有设置,开发人员的经验是非常棒的。

项目设置

现在开始做有趣的事情!在空文件夹中,通过运行npm init创建npm项目,在本练习的范围内,将值设置为多少并不重要。

安装Buidler:

$ npm install --save-dev @nomiclabs/buidler

引导Buidler项目:

$ npx buidler

选择“创建一个空的builder.config.js”选项(我们将使用与示例不同的堆栈,因此将创建自己的堆栈)。

$ npx buidler
888               d8b      888 888
888               Y8P      888 888
888                        888 888
88888b.  888  888 888  .d88888 888  .d88b.  888d888
888 "88b 888  888 888 d88" 888 888 d8P  Y8b 888P"
888  888 888  888 888 888  888 888 88888888 888
888 d88P Y88b 888 888 Y88b 888 888 Y8b.     888
88888P"   "Y88888 888  "Y88888 888  "Y8888  888

Welcome to Buidler v1.0.1 

? What do you want to do? … 
  Create a sample project
❯ Create an empty buidler.config.js
  Quit

创建一些目录来保存您的项目文件:

$ mkdir contracts test scripts

设置Typescript

安装所需的Typescript依赖项:

$ npm install --save-dev ts-node typescript @types/node @types/mocha

在项目根目录中创建tsconfig文件:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist"
  },
  "include": ["./scripts", "./test"],
  "files": [
    "./buidler.config.ts"
  ]
}

重命名Builder配置文件,并使其类型安全:

mv buidler.config.js buidler.config.ts

import { BuidlerConfig } from "@nomiclabs/buidler/config";
const config: BuidlerConfig = {};
export default config;

创建和编辑合约

现在,我们准备开始写代码了!

在contracts/目录中创建一个名为counter.sol的非常简单的solidity契约(编写时最新的solidity版本是0.5.12):

pragma solidity 0.5.12;
contract Counter {
  uint256 count = 0;
  event CountedTo(uint256 number);
  function countUp() public returns (uint256) {
    uint256 newCount = count + 1;
    require(newCount > count, "Uint256 overflow");
    count = newCount;
    emit CountedTo(count);
    return count;
  }
  function countDown() public returns (uint256) {
    uint256 newCount = count - 1;
    require(newCount < count, "Uint256 underflow");
    count = newCount;
    emit CountedTo(count);
    return count;
  }
}

在buidler.config.ts中设置Solidity版本(自动完成类型注释!):

Builder方便地捆绑了一个编译任务,因此编译是小菜一碟:

$ npx buidler compile
Compiling...
Compiled 1 contract successfully

Buidler使用的Solidity版本控制系统是令人惊叹的。 切换版本很容易,Buidler会根据需要自动下载并安装Solidity版本,您所需要做的就是在配置中进行更改。 为Buidler团队提供了巨大的道具来进行设置!

用Ethers和Waffle建立测试环境

现在,我们将设置您的测试环境。

安装Ethers,Waffle和Builder插件:

$ npm install --save-dev @nomiclabs/buidler-ethers ethers ethereum-waffle chai @types/chai

将所需的类型定义添加到tsconfig.json中:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "resolveJsonModule": true
  },
  "include": ["./scripts", "./test"],
  "files": [
    "./buidler.config.ts",
    "node_modules/@nomiclabs/buidler-ethers/src/type-extensions.d.ts",
}

设置builder.config.ts以使用Ethers插件:

import { BuidlerConfig, usePlugin } from "@nomiclabs/buidler/config";
usePlugin("@nomiclabs/buidler-ethers");
const config: BuidlerConfig = {
  solc: {
    version: "0.5.12"
  }
};
export default config;

设置TypeChain

TypeChain是一个非常酷的工具,可为您的智能合约提供完整的输入界面。 设置完成后,我们可以在Typescript中获得合同功能的类型提示!

在撰写本文时,Buidler没有TypeChain插件。 如果有人不先做,我打算很快就自己建一个!

首先安装库和Ethers绑定:

$ npm install --save-dev typechain typechain-target-ethers

通过运行命令生成类型文件。 outDir定义了生成文件的存储位置,最后用引号引起来的字符串将提取构建目录中所有生成的合同:

$ ./node_modules/.bin/typechain --target ethers --outDir typechain 'build/*.json'

现在在typechain /目录中,您应该看到生成了一些文件,其中一个是Counter.d.ts。 这是主要的合约类型文件,为我们提供了编写类型安全测试所需的信息!

编写和运行合约测试

编写测试大多遵循Waffle语法,但有一个主要区别:ethers.provider对象是从“ @ nomiclabs / buidler”库而不是ethereum-waffle库导入的。

现在让我们编写一个测试。 在test /目录中创建一个名为counter.ts的文件:

import { ethers } from "@nomiclabs/buidler";
import chai from "chai";
import { deployContract, getWallets, solidity } from "ethereum-waffle";

import CounterArtifact from "../build/Counter.json";
import { Counter } from "../typechain/Counter"

chai.use(solidity);
const { expect } = chai;

describe("Counter", () => {
  // 1
  const provider = ethers.provider;

  // 2
  let [wallet] = getWallets(provider);

  // 3
  let counter: Counter;

  beforeEach(async () => {
    counter = await deployContract(wallet, CounterArtifact) as Counter;
    const initialCount = await counter.getCount();

    // 4
    expect(initialCount).to.eq(0);
    expect(counter.address).to.properAddress;
  });

  // 5
  it("should count up", async () => {
    await counter.countUp();
    let count = await counter.getCount();
    expect(count).to.eq(1);

    await counter.countUp();
    count = await counter.getCount();
    expect(count).to.eq(2);
  });

  it("should count down", async () => {
    // 6
    await counter.countDown();
    const count = await counter.getCount();
    expect(count).to.eq(0);
  });
});

编号行的说明:

1.使用从Buidler导入的provider

2.从getWallets功能获取钱包另外请注意,您可以使用此函数对任意数量的钱包进行结构分解,例如:

let [wallet1, wallet2, wallet3] = getWallets(provider);

3.导入Counter类型,并将其用作在beforeEach中部署的变量的类型。

4. Waffle有一些有用的Chai匹配器,用于编写合同测试,例如BigNumber匹配器和以太坊地址匹配器。 在这里查看所有内容。

5.通过简单的测试来计数并确保Counter正常工作。

6.注意的那些人将看到此测试将失败。

让我们开始运行测试!

$ npx buidler test

注意到结果有异常吗?

All contracts have already been compiled, skipping compilation.

  Counter
    ✓ should count up (143ms)
    1) should count down

  1 passing (593ms)
  1 failing

  1) Counter
       should count down:
     Error: VM Exception while processing transaction: revert Uint256 underflow
      at Counter.countDown (contracts/Counter.sol:24)

这是您的Solidity代码中的STACK TRACE,其中显示发生还原的行号!!! line逐行注释合同以查看触发哪个还原的日子已经一去不复返了。

部署合约

经过测试后,循环的最后一步是部署合约。

第一步是将网络配置添加到buidler.config.ts文件。 我们将为此使用rinkeby,但您可以类似地添加任何网络(即mainnet):

import { BuidlerConfig, usePlugin } from "@nomiclabs/buidler/config";
import waffleDefaultAccounts from "ethereum-waffle/dist/config/defaultAccounts";

usePlugin("@nomiclabs/buidler-ethers");

const INFURA_API_KEY = "";
const RINKEBY_PRIVATE_KEY = "";

const config: BuidlerConfig = {
  solc: {
    version: "0.5.12"
  },
  paths: {
    artifacts: "./build"
  },
  networks: {
    buidlerevm: {
      accounts: waffleDefaultAccounts.map(acc => ({
        balance: acc.balance,
        privateKey: acc.secretKey
      }))
    },
    rinkeby: {
      url: `https://rinkeby.infura.io/v3/${INFURA_API_KEY}`,
      accounts: [RINKEBY_PRIVATE_KEY]
    }
  }
};

export default config;

我将Infura用作以太坊节点端点,但是任何远程端点都可以使用。 如果您从未这样做,请从Infura获取API密钥。

现在,我们在脚本/文件夹内创建一个名为deploy.ts的部署脚本:

import { ethers } from "@nomiclabs/buidler";

async function main() {
  const factory = await ethers.getContract("Counter")

  // If we had constructor arguments, they would be passed into deploy()
  let contract = await factory.deploy();

  // The address the Contract WILL have once mined
  console.log(contract.address);

  // The transaction that was sent to the network to deploy the Contract
  console.log(contract.deployTransaction.hash);

  // The contract is NOT deployed yet; we must wait until it is mined
  await contract.deployed()
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

超级简单的东西!

现在,只需运行脚本,我们就可以在控制台中看到我们的地址和交易哈希:

$ npx buidler run --network rinkeby scripts/deploy.ts
All contracts have already been compiled, skipping compilation.
0x01FF454Dd078dC7f3cd0905601d093b17E7B9CD7
0x2ae1444920ed76420fb69c9f2fc914c20956efc2ae05c94ab1ea53f224aa0930

我们可以转到Etherscan,查看该事务实际上已成功完成。

你有它! 完整的分步指南,用于设置类型安全的增压构建,测试,部署环境,并使用一些很酷的新工具。

收尾

为了使所有内容整洁无比,让我们制作一些方便的NPM脚本。 将以下内容添加到您的package.json中:

"scripts": {
  "build": "npm run compile && npm run typechain",
  "compile": "npx buidler compile",
  "typechain": "npx buidler compile && typechain --outDir typechain --target ethers 'build/*.json'",
  "test": "npx buidler test"
}

构建脚本执行合同编译并生成TypeChain绑定,并且测试脚本运行合同测试。

 附加:在etherscan上验证

Buidler有一个超级方便的插件,可用于在Etherscan上验证合约,此任务比看起来应该的要复杂得多。 他们的工具可以为您处理拼合,这对于导入其他合约,使用OpenZeppelin库等的合约非常方便。

我们可以从安装插件开始:

$ npm install --save-dev @nomiclabs/buidler-etherscan

然后,我们添加到tsconfig.json中,以确保我们的Typescript环境了解此插件:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "resolveJsonModule": true
  },
  "include": ["./scripts", "./test"],
  "files": [
    "./buidler.config.ts",
    "node_modules/@nomiclabs/buidler-ethers/src/type-extensions.d.ts",
    "node_modules/@nomiclabs/buidler-etherscan/src/type-extensions.d.ts"
  ]
}

接下来,我们将所需的配置添加到我们的buidler.config.ts中(跳到Etherscan并从您的帐户页面获取API密钥):

import { BuidlerConfig, usePlugin } from "@nomiclabs/buidler/config";
import waffleDefaultAccounts from "ethereum-waffle/dist/config/defaultAccounts";

usePlugin("@nomiclabs/buidler-ethers");
usePlugin("@nomiclabs/buidler-etherscan");

const INFURA_API_KEY = "";
const RINKEBY_PRIVATE_KEY = "";
const ETHERSCAN_API_KEY = "";

const config: BuidlerConfig = {
  solc: {
    version: "0.5.12"
  },
  paths: {
    artifacts: "./build"
  },
  networks: {
    buidlerevm: {
      accounts: waffleDefaultAccounts.map(acc => ({
        balance: acc.balance,
        privateKey: acc.secretKey
      }))
    },
    rinkeby: {
      url: `https://rinkeby.infura.io/v3/${INFURA_API_KEY}`,
      accounts: [RINKEBY_PRIVATE_KEY]
    }
  },
  etherscan: {
    // The url for the Etherscan API you want to use.
    url: "https://api-rinkeby.etherscan.io/api",
    // Your API key for Etherscan
    // Obtain one at https://etherscan.io/
    apiKey: ETHERSCAN_API_KEY
  }
};

export default config;

希望我们可以方便地保留上一步中的部署地址,因为这样我们就可以简单地运行此插件提供的内置任务:

$ npx buidler verify-contract --contract-name Counter --address 0xF0E6Ea29799E85fc1A97B7b78382fd034A6d7864
All contracts have already been compiled, skipping compilation.
Successfully submitted contract at 0xF0E6Ea29799E85fc1A97B7b78382fd034A6d7864 for verification on etherscan. Waiting for verification result...
Successfully verified contract on etherscan

易如反掌! 现在,在Etherscan上检查合约地址,您可以查看完整的合约源代码,并直接从网页上读写合约。

最后的想法

在整个使用过程中,Buidler对其devex的使用给我留下了深刻的印象。 它已经具有大量很酷的功能,并且他们计划建造更多更酷的东西。 除了Solidity堆栈跟踪,该团队还计划推出另一个急需的智能合约调试功能:console.log!。

我一定会密切关注这个项目,并尽我所能为其生态系统做出贡献。

相关文章