以太坊虽然还很年轻,但已经走了很长一段路。当我于2017年开始开发Solidity智能合约和以太坊DApp时,Truffle[1]和Web3.js[2]是行业标准。这些都是很棒的工具。但是,有一些新的工具使开发流程变得更好。
本文通过创建一个项目,来尝试这些新工具来构建和测试智能合约与DApp[3]。
本文代码在Starter Kit repo[4],克隆下来,就可以在功能齐全的Typescript开发环境中进行开发,编译,测试和部署。
工具链
Buidler (替代Truffle)
Buidler[5] 称自己为“以太坊智能合约开发者的跑腿”(“task runner for Ethereum smart contract developers”)。在实践中,Buidler将帮助我们使用模板启动Solidity[6]项目,并提供测试及部署智能合约所需的所有脚手架。之前,使用 Truffle 初始化(truffle init ),编译(truffle compile ),测试(truffle test )和部署(truffle migrate )功能来推动Solidity项目。
Buidler的杀手级功能是堆栈跟踪信息,当您的Solidity 遇到回退(revert)和用console.log()进行调试时,非常好用。
Ethers.js (替代Web3.js)
Ethers.js[7] 是一个Javascript SDK,用于与以太坊区块链进行交互。我之前使用Solidity开发时,一直使用Web3.js。当我第一次尝试Ethers.js时,我对它如此简单及API的出色程度感到震惊。我推荐曾经使用Web3.js的任何人尝试一下Ethers.js。它具有使用钱包,帐户和合约的所有必需功能,并且还具有一些简洁的实用程序,例如ABICoder,HDNode,BigNumber,以及用于十六进制字符串,以太单位转换和以太坊地址的各种格式化实用工具。
Ethers.js是一个Javascript SDK,用于与以太坊区块链进行交互。当我开始进行Solidity开发时,我长期使用Web3.js。当我第一次尝试Ethers时,我对它的设置如此简单以及API的出色程度感到震惊。我敦促曾经使用Web3.js的任何人尝试一下Ethers。它具有使用钱包,帐户和合约的所有必需功能,并且还具有一些简洁的实用程序,例如ABICoder,HDNode,BigNumber,以及用于十六进制字符串,以太单位和以太坊地址的各种格式化实用程序。
Waffle (替代Truffle 测试工具)
Ethereum Waffle[8] 是以太坊智能合约的轻量级测试运行器。Waffle内置了一些非常不错的测试工具函数,例如用于以太坊地址,哈希和BigNumbers的Chai匹配器,Waffle使用原生Typescript,与Ethers.js配合非常好。
译者注:Chai 是一个断言库,使用链式结构进行断言。
Typescript 无处不在
Typescript 最近很火,这是有原因的。对我而言,Typescript 的最大的改变是 IDE的集成,它提供所有类属性,对象键,函数参数等的自动补全功能。熟悉Typescript之后,我再也不会回过头来编写原始Javascript了。
上面提到的所有工具都可以与Typescript一起很好地工作,并且一旦完成所有设置,开发的体验很梦幻。
项目启动(Project setup)
现在开始真正有趣的实践!在一个空文件夹中,运行以下命令初始化一个npm项目:
npm init
初始化过程中,需要多项目有一个简单的设置,因为我们只是演练,可以随意填。
安装 Buidler:
$ npm install –save-dev @nomiclabs/buidler
译者注,如果npm 安装慢,本文的npm 命令都可以用cnpm替换
进行Buidler项目引导:
$ npx buidler
选择”Create an empty buidler.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.3.8
? What do you want to do? …
Create a sample project
❯ Create an empty buidler.config.js
Config file created
创建一些目录来保存项目文件,如 contracts,test,scripts, 命令如下:
$ 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配置文件buidler.config.js,修改后缀并使其类型安全:
mv buidler.config.js buidler.config.ts
修改buidler.config.ts:
// buidler.config.ts
import { BuidlerConfig } from “@nomiclabs/buidler/config”;
const config: BuidlerConfig = {};
export default config;
创建和编译合约
现在可以开始编写代码:
在 contracts/ 目录创建一个非常简单的 Counter.sol 合约文件,当前使用的最新Solidity 版本是 0.6.8:
pragma solidity ^0.6.8;
import “@nomiclabs/buidler/console.sol”;
contract Counter {
uint256 count = 0;
event CountedTo(uint256 number);
function getCount() public view returns (uint256) {
return count;
}
function countUp() public returns (uint256) {
console.log(“countUp: count =”, count);
uint256 newCount = count + 1;
require(newCount > count, “Uint256 overflow”);
count = newCount;
emit CountedTo(count);
return count;
}
function countDown() public returns (uint256) {
console.log(“countDown: count =”, count);
uint256 newCount = count – 1;
require(newCount < count, “Uint256 underflow”);
count = newCount;
emit CountedTo(count);
return count;
}
}
在buidler.config.ts中通过修改solc.version 来设置Solidity版本:
import { BuidlerConfig } from “@nomiclabs/buidler/config”;
const config: BuidlerConfig = {
solc: {
version: “0.6.8”,
},
}
export default config;
Builder 集成了编译任务,因此编译是小菜一碟:
> npx builder compile
You probably meant to type buidler. We got you.
Compiling…
Compiled 2 contracts successfully
Buidler使用AMAZING 来对Solidity进行版本控制。切换版本很容易,Buidler会根据需要自动下载并安装Solidity版本,您所需要做的就是在配置中进行更改。Buidler团队提供了很多选项进行设置!
使用Ethers和Waffle配置测试环境
现在,创建一个测试环境,安装 Ethers, Waffle, 以及 Buidler 插件:
$npm install –save-dev ethers @nomiclabs/buidler-waffle ethereum-waffle
在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”,
“node_modules/@nomiclabs/buidler-waffle/src/type-extensions.d.ts”
]
}
设置 buidler.config.ts 以便使用 Ethers 插件(包括 buidler-waffle插件):
import { BuidlerConfig, usePlugin } from “@nomiclabs/buidler/config”;
usePlugin(“@nomiclabs/buidler-waffle”);
const config: BuidlerConfig = {
solc: {
version: “0.6.8”
}
};
设置 TypeChain
TypeChain[9] 是一款非常酷的工具,可为智能合约提供完整的类型接口。设置完成后,我们可以在Typescript中获得合约函数的类型提示!
可以通过我构建的Typechain插件使用,先安装:
$ npm install –save-dev buidler-typechain typechain ts-generator @typechain/ethers-v4 @typechain/truffle-v4 @typechain/web3-v1
Builder配置文件中添加 typechain以配置插件:
import { BuidlerConfig, usePlugin } from “@nomiclabs/buidler/config”;
usePlugin(“@nomiclabs/buidler-waffle”);
usePlugin(“buidler-typechain”);
const config: BuidlerConfig = {
solc: {
version: “0.6.8”
},
typechain: {
outDir: “typechain”,
target: “ethers-v4”
}
};
export default config;
outDir 定义了产生文件的目录,生成目标的文件指定匹配ethers 。
给 tsconfig 添加类型:
{
“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-waffle/src/type-extensions.d.ts”,
“node_modules/buidler-typechain/src/type-extensions.d.ts”
]
}
如果运行 npx buidler,则会在菜单中看到新命令typechain。
通过运行命令npx buidler typechain来生成类型文件。
现在在typechain/目录中,您应该看到生成了一些文件,其中最主要的是Counter.d.ts。这是合约对应的 Typescript 类型文件,提供了类型安全测试所需的信息!
编写和运行合约测试
编写测试大多遵循Waffle语法[10],但有一个主要区别:ethers.provider对象是从”@nomiclabs/buidler”库而不是ethereum-waffle库导入的。
现在让我们编写一个测试。在 test/ 目录中创建一个名为counter.ts的文件:
import { ethers } from “@nomiclabs/buidler”;
import { Wallet } from “ethers”;
import chai from “chai”;
import { deployContract, solidity } from “ethereum-waffle”;
import CounterArtifact from “../artifacts/Counter.json”;
import { Counter } from “../typechain/Counter”;
chai.use(solidity);
const { expect } = chai;
describe(“Counter”, () => {
let counter: Counter;
beforeEach(async () => {
// 1
const signers = await ethers.signers();
// 2
counter = (await deployContract(
<Wallet>signers[0],
CounterArtifact
)) as Counter;
const initialCount = await counter.getCount();
// 3
expect(initialCount).to.eq(0);
expect(counter.address).to.properAddress;
});
// 4
describe(“count up”, async () => {
it(“should count up”, async () => {
await counter.countUp();
let count = await counter.getCount();
expect(count).to.eq(1);
});
});
describe(“count down”, async () => {
// 5
it(“should fail”, async () => {
await counter.countDown();
});
it(“should count down”, async () => {
await counter.countUp();
await counter.countDown();
const count = await counter.getCount();
expect(count).to.eq(0);
});
});
});
代码中几处标记处解释如下:
1.从Ethers获取一写预先存款的签名器。
2.使用从 1 获取的签名器部署合约。导入 Counter 类型,并将其作为 beforeEach 中部署的变量的类型。
3.Waffle有一些有用的Chai匹配器可用于编写合约测试,例如BigNumber匹配器和以太坊地址匹配器。 在这里[11]查看所有内容。
4.简单计数测试,确保计数器正常工作。
5.此测试将失败,值得关注,等下会看到 Buidler 的真正魔力。
让我们运行测试。
首先,我们将Buidler配置为使用其buidlerevm网络,该网络提供了所有Solidity调试魔法:
import { BuidlerConfig, usePlugin } from “@nomiclabs/buidler/config”;usePlugin(“@nomiclabs/buidler-waffle”);
usePlugin(“buidler-typechain”);const config: BuidlerConfig = {
defaultNetwork: “buidlerevm”,
solc: {
version: “0.6.8”
},
typechain: {
outDir: “typechain”,
target: “ethers-v4”
}
};export default config;
现在运行测试:
$ npx buidler test
注意在结果中有不寻常的内容:
2 passing (1s)
1 failing1) Counter
count down
should fail:
Error: VM Exception while processing transaction: revert Uint256 underflow
at Counter.countDown (contracts/Counter.sol:29)
它打印了Solidity 的输出以及堆栈信息,显示了触发回退的行号!!!
逐行注释合约以查看触发了哪个还原(revert)去猜测变量值的日子已经一去不复返了。
译者注:这里原作者稍微有点夸张,其实现在其他工具链也会给出 revert 原因。
部署合约
经过测试后,开发周期的最后一步是部署合约。
第一步是将网络配置添加到buidler.config.ts文件。为此,本案例我们将使用rinkeby,但您可以类似地添加任何网络(如:mainnet),配置很简单:
import { BuidlerConfig, usePlugin } from “@nomiclabs/buidler/config”;usePlugin(“@nomiclabs/buidler-waffle”);
usePlugin(“@nomiclabs/buidler-etherscan”);
usePlugin(“buidler-typechain”);
const INFURA_API_KEY = “”;
const RINKEBY_PRIVATE_KEY = “”;
const config: BuidlerConfig = {
defaultNetwork: “buidlerevm”,
solc: {
version: “0.6.8”
},
networks: {
rinkeby: {
url: `https://rinkeby.infura.io/v3/${INFURA_API_KEY}`,
accounts: [RINKEBY_PRIVATE_KEY]
}
},
typechain: {
outDir: “typechain”,
target: “ethers-v4”
}
};export default config;
我将Infura用作以太坊节点,不过你可以使用任何远程以太坊节点。如果你之前没有使用过 Infura,请从Infura[12]获取API密钥。
现在,我们在 scripts 文件夹内创建一个名为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);
});
部署非常简单,现在运行这个脚本,我们在控制台可以看到地址及交易hash.
$ npx buidler run –network rinkeby scripts/deploy.ts
All contracts have already been compiled, skipping compilation.
0x01FF454Dd078dC7f3cd0905601d093b17E7B9CD7
0x2ae1444920ed76420fb69c9f2fc914c20956efc2ae05c94ab1ea53f224aa0930
我们可以前往 Etherscan[13] 查看交易详情。
这基本就是全部了,本文一步步进行创建项目测试、部署环境,他们都是类型安全的并且使用一些很酷的工具。
封装一下
为了使一切保持干净漂亮,让我们编写一些顺手的NPM脚本。将以下内容添加到您的package.json中:
“scripts”: {
“build”: “npm run compile && npx buidler typechain”,
“compile”: “npx buidler compile”,
“test”: “npx buidler test”
}
build 用于执行合约编译并生成TypeChain绑定
test 脚本运行合约测试。
福利: 在Etherscan上验证
Buidler有一个超级方便的插件,可用于在Etherscan上验证合约,此任务其实比看起来要复杂。Buidler的工具可以为帮我们处理合约组合,当我们导入了其他合约,例如使用了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”,
“node_modules/@nomiclabs/buidler-waffle/src/type-extensions.d.ts”,
“node_modules/buidler-typechain/src/type-extensions.d.ts”
]
}
接下来,我们在buidler.config.ts中添加所需的配置,前往Etherscan[14]并从您的帐户页面获取API密钥:
import { BuidlerConfig, usePlugin } from “@nomiclabs/buidler/config”;usePlugin(“@nomiclabs/buidler-waffle”);
usePlugin(“@nomiclabs/buidler-etherscan”);
usePlugin(“buidler-typechain”);const INFURA_API_KEY = “”;
const RINKEBY_PRIVATE_KEY = “”;
const ETHERSCAN_API_KEY = “”;
const config: BuidlerConfig = {
defaultNetwork: “buidlerevm”,
solc: {
version: “0.6.8”
},
networks: {
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
},
typechain: {
outDir: “typechain”,
target: “ethers”
}
};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[15]上查看合约地址,可以查看到完整的合约源代码,并在网页上读写合约。
最后的想法
在整个使用过程中,Buidler的开发者体验给我留下了深刻的印象。它具备很多酷炫功能,并且他们计划构建更多更酷的东西。除了Solidity堆栈跟踪,该团队还计划推出另一个急需的智能合约调试功能:console.log!。
我会继续密切关注这个项目,并尽我所能为其生态系统做出贡献。
如果您需要任何帮助,请执行Telegram上的Buidler Support group[16],这是迅速解决问题的好资源。Nomic Labs团队经常在外面闲逛,并且反应迅速。
请继续关注有关全栈dapp开发和工具的更多后续帖子!
References
[1] Truffle: https://learnblockchain.cn/docs/truffle/
[2] Web3.js: https://learnblo