英文原文地址:https://rahulsethuram.medium.com/the-new-solidity-dev-stack-buidler-ethers-waffle-typescript-tutorial-f07917de48ae
原文發(fā)表日期:2020-02-09
【譯文省略了很多原作者抒情性文字,直接進(jìn)入主題?!?br> 本文相關(guān)代碼:Starter Kit repo
Hardhat (替代Truffle)
Hardhat 稱(chēng)自己為“task runner for Ethereum smart contract developers”。 在實(shí)踐中,Hardhat 將幫助你使用模板啟動(dòng) Solidity 項(xiàng)目,并提供測(cè)試及部署智能合約所需的所有腳手架。 之前,使用 Truffle 初始化 truffle init,編譯 truffle compile ,測(cè)試 truffle test 和部署 truffle migrate 功能來(lái)推動(dòng)Solidity項(xiàng)目。
Hardhat的殺手級(jí)功能是堆棧跟蹤信息,使得在調(diào)試 Solidity 時(shí),可以使用回退(revert)和console.log()。
Ethers.js (替代Web3.js)
Ethers.js 是一個(gè)Javascript SDK,用于與以太坊區(qū)塊鏈進(jìn)行交互。 我從使用Solidity開(kāi)發(fā)以來(lái),一直使用Web3.js。 當(dāng)我第一次嘗試Ethers.js時(shí),我對(duì)它如此簡(jiǎn)單及API的出色程度感到震驚。 我推薦曾經(jīng)使用Web3.js的任何人嘗試一下Ethers.js。 它擁有針對(duì)錢(qián)包,帳戶(hù)和合約的所有必需功能,并且還具有一些簡(jiǎn)潔的實(shí)用程序,例如ABICoder,HDNode,BigNumber,以及用于十六進(jìn)制字符串,以太單位轉(zhuǎn)換和以太坊地址的各種格式化實(shí)用工具。
Waffle (替代Truffle 測(cè)試工具)
Ethereum Waffle 是以太坊智能合約的輕量級(jí)測(cè)試運(yùn)行器。 Waffle內(nèi)置了一些非常不錯(cuò)的測(cè)試工具函數(shù),例如用于以太坊地址,哈希和BigNumbers的Chai匹配器,Waffle使用原生Typescript,與Ethers.js配合非常好。
譯者注:Chai 是一個(gè)斷言庫(kù),使用鏈?zhǔn)浇Y(jié)構(gòu)進(jìn)行斷言。
Typescript 無(wú)處不在
Typescript 最近很火,這是有原因的。 對(duì)我而言,Typescript 的最大的改變是 IDE的集成,它提供所有類(lèi)屬性,對(duì)象鍵,函數(shù)參數(shù)等的自動(dòng)補(bǔ)全功能。熟悉Typescript之后,我再也不會(huì)回過(guò)頭來(lái)編寫(xiě)原始Javascript了。
上面提到的所有工具都可以與Typescript一起很好地工作,并且一旦完成所有設(shè)置,開(kāi)發(fā)的體驗(yàn)很夢(mèng)幻。
項(xiàng)目啟動(dòng)(Project setup)
現(xiàn)在開(kāi)始真正有趣的實(shí)踐! 在一個(gè)空文件夾中,運(yùn)行以下命令初始化一個(gè)npm項(xiàng)目:
npm init
初始化過(guò)程中,需要多項(xiàng)目有一個(gè)簡(jiǎn)單的設(shè)置,因?yàn)槲覀冎皇茄菥?,可以隨意填。
安裝 Hardhat:
$ npm install --save-dev hardhat
譯者注,如果npm 安裝慢,本文的npm 命令都可以用cnpm替換
進(jìn)行Hardhat項(xiàng)目引導(dǎo):
$ npx hardhat
選擇"Create an empty hardhat.config.js"選項(xiàng),意思是常見(jiàn)一個(gè)新的而不是參考一個(gè)樣例。
$ 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 hardhat.config.js
Quit
創(chuàng)建一些目錄來(lái)保存項(xiàng)目文件,命令如下:
$ mkdir contracts test scripts
設(shè)置 Typescript
安裝Typescript需要的依賴(lài)項(xiàng):
$ npm install --save-dev ts-node typescript @types/node @types/mocha
在根目錄中創(chuàng)建tsconfig文件:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "dist"
},
"include": ["./scripts", "./test"],
"files": [
"./hardhat.config.ts"
]
}
重命名hardhat配置文件,修改后綴并使其類(lèi)型安全:
mv hardhat.config.js hardhat.config.ts
// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/types";
const config: HardhatUserConfig = {};
export default config;
創(chuàng)建和編譯合約
現(xiàn)在可以開(kāi)始編寫(xiě)代碼:
在 contracts/ 目錄創(chuàng)建一個(gè)非常簡(jiǎn)單的 Counter.sol 合約文件(當(dāng)前使用的最新Solidity 版本是 0.6.8):
pragma solidity ^0.6.8;
import "hardhat/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;
}
}
在hardhat.config.ts中通過(guò)修改solidity.compilers.$.version 來(lái)設(shè)置Solidity版本:
譯者注,此處可能是舊版本才需要修改,新版本config內(nèi)結(jié)構(gòu)也不盡相同
hardhat 集成了編譯任務(wù),因此編譯是小菜一碟:
> npx builder compile
Compiling...
Compiled 1 contract successfully
hardhat使用AMAZING 來(lái)對(duì)Solidity進(jìn)行版本控制。 切換版本很容易,hardhat會(huì)根據(jù)需要自動(dòng)下載并安裝Solidity版本,您所需要做的就是在配置中進(jìn)行更改。 hardhat團(tuán)隊(duì)提供了很多選項(xiàng)進(jìn)行設(shè)置!你可以通過(guò)將config文件中solidity key賦值為數(shù)組(array),而很簡(jiǎn)單的編譯出不同solidity版本的項(xiàng)目。
使用Ethers和Waffle配置測(cè)試環(huán)境
現(xiàn)在,創(chuàng)建一個(gè)測(cè)試環(huán)境,安裝 Ethers, Waffle, 以及 Buidler 插件:
$npm install --save-dev ethers @nomiclabs/buidler-waffle ethereum-waffle
在tsconfig.json添加需要的類(lèi)型定義:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"resolveJsonModule": true
},
"include": [
"./scripts",
"./test"
],
"files": [
"./hardhat.config.ts"
]
}
設(shè)置 hardhat.config.ts 以便使用 waffle 插件
import { HardhatUserConfig } from "hardhat/types";
import "@nomiclabs/hardhat-waffle";
const config: HardhatUserConfig = {
solidity: {
compilers: [{ version: "0.6.8", settings: {} }],
},
};
export default config;
設(shè)置 TypeChain
TypeChain 是一款非??岬墓ぞ撸蔀橹悄芎霞s提供完整的類(lèi)型接口。 設(shè)置完成后,我們可以在Typescript中獲得合約函數(shù)的類(lèi)型提示!
可以通過(guò)我構(gòu)建的Typechain插件使用,先安裝:
$ npm install --save-dev hardhat-typechain typechain ts-generator @typechain/ethers-v5
Builder配置文件中添加 typechain以配置插件:
import { HardhatUserConfig } from "hardhat/types";
import "@nomiclabs/hardhat-waffle";
import "hardhat-typechain";
const config: BuidlerConfig = {
solidity: {
compilers: [{ version: "0.6.8", settings: {} }],
},
};
export default config;
編譯
& npx hardhat compile
現(xiàn)在,進(jìn)入目錄 typechain/ ,你可以看到一些生成的文件,其中有一個(gè)是 Counter.d.ts 。這是主要的合同文件。
編寫(xiě)和運(yùn)行合約測(cè)試
編寫(xiě)測(cè)試大多遵循Waffle語(yǔ)法,但有一個(gè)主要區(qū)別:ethers.provider 對(duì)象是從 hardhat 庫(kù)而不是 ethereum-waffle 庫(kù)導(dǎo)入的。
現(xiàn)在讓我們編寫(xiě)一個(gè)測(cè)試。 在 test/ 目錄中創(chuàng)建一個(gè)名為counter.ts的文件:
import { ethers } from "hardhat";
import chai from "chai";
import { solidity } from "ethereum-waffle";
import { Counter } from "../typechain/Counter";
chai.use(solidity);
const { expect } = chai;
describe("Counter", () => {
let counter: Counter;
beforeEach(async () => {
// 1
const signers = await ethers.getSigners();
// 2
const counterFactory = await ethers.getContractFactory(
"Counter",
signers[0]
);
counter = (await counterFactory.deploy()) as Counter;
await counter.deployed();
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 () => {
// this test will fail
await counter.countDown();
});
it("should count down", async () => {
await counter.countUp();
await counter.countDown();
const count = await counter.getCount();
expect(count).to.eq(0);
});
});
});
代碼中幾處標(biāo)記處解釋如下:
- 從Ethers獲取一寫(xiě)預(yù)先存款的簽名器。
- 使用從 1 獲取的簽名器部署合約。 導(dǎo)入 Counter 類(lèi)型,并將其作為 beforeEach 中部署的變量的類(lèi)型。
- Waffle有一些有用的Chai匹配器可用于編寫(xiě)合約測(cè)試,例如BigNumber匹配器和以太坊地址匹配器。 在這里查看所有內(nèi)容。
- 簡(jiǎn)單計(jì)數(shù)測(cè)試,確保計(jì)數(shù)器正常工作。
- 此測(cè)試將失敗,值得關(guān)注,等下會(huì)看到 Hardhat 的真正魔力。
讓我們運(yùn)行測(cè)試。
首先,我們將Hardhat配置為使用其hardhat網(wǎng)絡(luò),該網(wǎng)絡(luò)提供了所有Solidity調(diào)試魔法:
import { HardhatUserConfig } from "hardhat/types";
import "[@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)";
import "hardhat-typechain";
const config: HardhatUserConfig = {
defaultNetwork: "hardhat",
solidity: {
compilers: [{ version: "0.6.8", settings: {} }],
},
};
export default config;
現(xiàn)在運(yùn)行測(cè)試:
$ npx hardhat test
注意在結(jié)果中有不尋常的內(nèi)容:
2 passing (1s)
1 failing
1) Counter
count down
should fail:
Error: VM Exception while processing transaction: revert Uint256 underflow
at Counter.countDown (contracts/Counter.sol:29)
它打印了Solidity 的輸出以及堆棧信息,顯示了觸發(fā)回退的行號(hào)!!! ??????
逐行注釋合約以查看觸發(fā)了哪個(gè)還原(revert)去猜測(cè)變量值的日子已經(jīng)一去不復(fù)返了。
譯者注:這里原作者稍微有點(diǎn)夸張,其實(shí)現(xiàn)在其他工具鏈也會(huì)給出 revert 原因。
部署合約
經(jīng)過(guò)測(cè)試后,開(kāi)發(fā)周期的最后一步是部署合約。
第一步是將網(wǎng)絡(luò)配置添加到hardhat.config.ts文件。 為此,本案例我們將使用rinkeby,但您可以類(lèi)似地添加任何網(wǎng)絡(luò)(如:mainnet),配置很簡(jiǎn)單:
import { HardhatUserConfig } from "hardhat/types";
import "[@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)";
import "hardhat-typechain";
const config: HardhatUserConfig = {
defaultNetwork: "hardhat",
solidity: {
compilers: [{ version: "0.6.8", settings: {} }],
},
networks: {
hardhat: {},
rinkeby: {
url: `[https://rinkeby.infura.io/v3/${INFURA_API_KEY}`],
accounts: [RINKEBY_PRIVATE_KEY],
},
},
};
export default config;
我將Infura用作以太坊節(jié)點(diǎn),不過(guò)你可以使用任何遠(yuǎn)程以太坊節(jié)點(diǎn)。 如果你之前沒(méi)有使用過(guò) Infura,請(qǐng)從Infura獲取API密鑰。
現(xiàn)在,我們?cè)?scripts 文件夾內(nèi)創(chuàng)建一個(gè)名為deploy.ts的部署腳本:
import { ethers } from "hardhat";
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);
});
部署非常簡(jiǎn)單,現(xiàn)在運(yùn)行這個(gè)腳本,我們?cè)诳刂婆_(tái)可以看到地址及交易hash.
$ npx hardhat run --network rinkeby scripts/deploy.ts
All contracts have already been compiled, skipping compilation.
0x01FF454Dd078dC7f3cd0905601d093b17E7B9CD7
0x2ae1444920ed76420fb69c9f2fc914c20956efc2ae05c94ab1ea53f224aa0930
我們可以前往 Etherscan 查看交易詳情。
這基本就是全部了,本文一步步進(jìn)行創(chuàng)建項(xiàng)目測(cè)試、部署環(huán)境,他們都是類(lèi)型安全的并且使用一些很酷的工具。
封裝一下
為了使一切保持干凈漂亮,讓我們編寫(xiě)一些順手的NPM腳本。 將以下內(nèi)容添加到您的package.json中:
"scripts": {
"build": "npm run compile",
"compile": "npx buidler compile",
"test": "npx buidler test"
}
build 用于執(zhí)行合約編譯并生成TypeChain綁定
test 腳本運(yùn)行合約測(cè)試。
福利: 在Etherscan上驗(yàn)證
Hardhat有一個(gè)超級(jí)方便的插件,可用于在Etherscan上驗(yàn)證合約,此任務(wù)其實(shí)比看起來(lái)要復(fù)雜。 Hardhat的工具可以為幫我們處理合約組合,當(dāng)我們導(dǎo)入了其他合約,例如使用了OpenZeppelin等庫(kù)的合約會(huì)非常方便。
安裝插件:
$ npm install --save-dev @nomiclabs/hardhat-etherscan
接下來(lái),我們?cè)趆ardhat.config.ts中添加所需的配置,前往Etherscan并從您的帳戶(hù)頁(yè)面獲取API密鑰:
import { HardhatUserConfig } from "hardhat/types";
import "[@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)";
import "hardhat-typechain";
const config: HardhatUserConfig = {
defaultNetwork: "hardhat",
solidity: {
compilers: [{ version: "0.6.8", settings: {} }],
},
networks: {
hardhat: {},
rinkeby: {
url: `[https://rinkeby.infura.io/v3/${INFURA_API_KEY}`](https://rinkeby.infura.io/v3/$%7BINFURA_API_KEY%7D%60),
accounts: [RINKEBY_PRIVATE_KEY],
},
},
etherscan: {
// Your API key for Etherscan
// Obtain one at [https://etherscan.io/](https://etherscan.io/)
apiKey: ETHERSCAN_API_KEY,
},
};
export default config;
希望我們可以順手保存上一步的部署地址,因?yàn)檫@樣就可以簡(jiǎn)單地運(yùn)行插件提供的內(nèi)置命令來(lái)驗(yàn)證合約:
$ 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
非常簡(jiǎn)單! 現(xiàn)在,在Etherscan上查看合約地址,可以查看到完整的合約源代碼,并在網(wǎng)頁(yè)上讀寫(xiě)合約。