译文出自:登链翻译计划 [1]
译者:翻译小组 [2]
校对:Tiny 熊 [3]
本文在官方 hardhat 教程的基础上,加入了 hardhat-deploy 插件的使用介绍,本文代码的 GitHub: https://github.com/wighawag/tutorial-hardhat-deploy
1. 设置环境
大多数以太坊库和工具都是用 JavaScript 编写的, Hardhat 也是如此。如果你不熟悉 Node.js,它是一个建立在 Chrome 的 V8 JavaScript 引擎上的 JavaScript 运行时。它是在 Web 浏览器之外运行 JavaScript 的最流行的解决方案, Hardhat 也是基于 Node.js 开发的。
安装 Node.js
如果你已经安装了 Node.js
>=12.0
,你可以跳过本节。如果没有,这里介绍如何在 Ubuntu、MacOS 和 Windows 上安装 Node.js。
Linux
Ubuntu
在终端中复制并粘贴这些命令:
sudo apt update sudo apt install curl git curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt install nodejs
MacOS
确保你已经安装了
git
。否则,请遵循这个安装说明安装 git[4]。
在 MacOS 上安装 Node.js 有多种方法。我们将使用 node 版本管理器 (nvm)[5], 在终端中复制并粘贴这些命令:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.2/install.sh | bash nvm install 12 nvm use 12 nvm alias default 12 npm install npm --global # Upgrade npm to the latest version
Windows
在 Windows 上安装 Node.js 需要几个手动步骤 , 我们将安装 git、Node.js 12.x 和 npm, 下载并运行:
-
Git 的 Windows 安装程序 [6]
-
安转此处 [7] 的
node-v12.XX.XX-x64.msi
升级你的 Node.js 装置
如果你的 Node.js 版本比
12.0
低,请按照下面的说明进行升级。
Linux
Ubuntu
-
在终端中运行
sudo apt remove nodejs
来删除 Node.js。 -
在这里 [8] 找到你要安装的 Node.js 版本,然后按照说明操作。
-
在终端运行
sudo apt update && sudo apt install nodejs
再次安装 Node.js。
MacOS
你可以使用 nvm[9] 更改你的 Node.js 版本。要升级到 Node.js
12.x
,请在终端运行这些。
nvm install 12 nvm use 12 nvm alias default 12 npm install npm --global # Upgrade npm to the latest version
Windows
你需要遵循相同的安装说明 [10] 和之前一样,但要选择不同的版本。你可以查看所有可用版本的列表这里 [11]。
安装 yarn
在本教程中,我们将使用 yarn[12]
要安装它,请执行以下操作:
npm install -g yarn
2. 创建一个 Hardhat 项目
我们将使用 npm CLI 安装 Hardhat 。 N ode.js p ackage m anager 是一个包管理器和 JavaScript 代码的在线存储库。
打开一个新的终端,运行这些命令。
mkdir hardhat-deploy-tutorial cd hardhat-deploy-tutorial yarn init --yes yarn add -D hardhat
安装 Hardhat 会安装一些以太坊 JavaScript 依赖,所以要耐心等待。
在安装
Hardhat
的同一个目录下,添加一个
hardhat.config.ts
(我们将使用 typescript 和 solidity 0.7.6 编译器)。
import {HardhatUserConfig} from 'hardhat/types'; const config: HardhatUserConfig = { solidity: { version: '0.7.6', } }; export default config;
Hardhat 的架构
Hardhat 是围绕 任务(Tasks) 和 插件(plugs) 的概念设计的。 Hardhat 的大部分功能来自于插件,作为开发者可以自由选择你想使用的插件。
任务
每次你从 CLI 运行
Hardhat
时,你都在运行一个任务,例如
npx hardhat compile
就是在运行
compile
任务。要查看项目中当前可用的任务,运行
npx hardhat
。可以通过运行
npx hardhat help [task]
来自由探索任何任务。
你可以创建自己的任务。查看创建任务 [13] 指南。
插件
Hardhat 不限制你使用什么工具,但它也内置了一些默认的工具。所有这些都可以替换掉。大多数时候,使用某个工具的方式是通过插件集成到 Hardhat 中。
在本教程中,我们将使用 hardhat-deploy-ethers 和 hardhat-deploy 插件。它们将允许你与以太坊交互,并测试合约。后面我们会解释如何使用的。我们还安装了 ethers chai 和 Mocha 以及 typescript。在项目目录下运行以下命令安装它们:
yarn add -D hardhat-deploy hardhat-deploy-ethers ethers chai chai-ethers mocha @types/chai @types/mocha @types/node typescript ts-node dotenv
编辑
hardhat.config.ts
,使其看起来像这样:
import {HardhatUserConfig} from 'hardhat/types'; import 'hardhat-deploy'; import 'hardhat-deploy-ethers'; const config: HardhatUserConfig = { solidity: { version: '0.7.6', }, namedAccounts: { deployer: 0, }, }; export default config;
我们还创建以下
tsconfig.json
。
{ "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "esModuleInterop": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "outDir": "dist" }, "include": [ "hardhat.config.ts", "./deploy", "./test", ] }
3. 编写和编译智能合约
我们创建一个简单的智能合约,实现一个可以转让的代币。代币合约最常用来交换或储存价值。在本教程中,我们不会深入讲解合约的 Solidity 代码,但你应该知道实现的逻辑 :
-
代币的发行总量是固定的,不能更改。
-
所有发行的代币都分配到部署合约的地址。
-
任何人都可以接受代币。
-
拥有至少一块代币的人,都可以转让代币。
-
代币是不可分割的。你可以转让 1、2、3 或 37 枚代币,但不能转让 2.5 枚。
你可能听说过 ERC20[14],它是以太坊中的一种代币标准。DAI、USDC、MKR 和 ZRX 等代币遵循 ERC20 标准,这使得它们都能与任何可以处理 ERC20 代币的软件兼容。 为了简单起见,我们要建立的代币不是 ERC20 。
编写智能合约
虽然默认情况下,hardhat 使用
contracts
作为合约代码源文件夹,但我们更倾向于将其改为
hide
。
因此,你需要用新的配置来编辑你的
hardhat.config.ts
文件。
import {HardhatUserConfig} from 'hardhat/types'; import 'hardhat-deploy'; import 'hardhat-deploy-ethers'; const config: HardhatUserConfig = { solidity: { version: '0.7.6', }, namedAccounts: { deployer: 0, }, paths: { sources: 'hide', }, }; export default config;
首先创建一个名为
hide
的新目录,并在该目录内创建一个名为
Token.sol
的文件。
将下面的代码粘贴到文件中,建议花一分钟时间阅读代码及注释。
要获得 Solidity 语法高亮支持, 我们推荐使用 Visual Studio Code 或 Sublime Text 3,并安装对应的 Solidity 或 以太坊插件。
// SPDX-License-Identifier: MIT // The line above is recommended and let you define the license of your contract // Solidity files have to start with this pragma. // It will be used by the Solidity compiler to validate its version. pragma solidity ^0.7.0; // This is the main building block for smart contracts. contract Token { // Some string type variables to identify the token. // The `public` modifier makes a variable readable from outside the contract. string public name = "My Hardhat Token"; string public symbol = "MBT"; // The fixed amount of tokens stored in an unsigned integer type variable. uint256 public totalSupply = 1000000; // An address type variable is used to store ethereum accounts. address public owner; // A mapping is a key/value map. Here we store each account balance. mapping(address => uint256) balances; /** * Contract initialization. * * The `constructor` is executed only once when the contract is created. */ constructor(address_owner) { // The totalSupply is assigned to transaction sender, which is the account // that is deploying the contract. balances[_owner] = totalSupply; owner =_owner; } /** * A function to transfer tokens. * * The `external` modifier makes a function *only* callable from outside * the contract. */ function transfer(address to, uint256 amount) external { // Check if the transaction sender has enough tokens. // If `require`'s first argument evaluates to `false` then the // transaction will revert. require(balances[msg.sender] >= amount, "Not enough tokens"); // Transfer the amount. balances[msg.sender] -= amount; balances[to] += amount; } /** * Read only function to retrieve the token balance of a given account. * * The `view` modifier indicates that it doesn't modify the contract's * state, which allows us to call it without executing a transaction. */ function balanceOf(address account) external view returns (uint256) { return balances[account]; } }
*.sol
是 Solidity 文件的后缀,同时建议文件名与其包含的合约名称进行匹配,这是一种常见的做法。
编译合约
要编译合约,请在你的终端运行
yarn hardhat compile
。
compile
是内置任务之一。
$ yarn hardhat compile Compiling 1 file with 0.7.3 Compilation finished successfully
合约已经编译成功,可以使用了。
4. 部署脚本
在能够测试或部署合约之前,你需要设置部署脚本,以便在测试和准备部署时使用。部署脚本让你可以专注于合约的最终形式,设置它们的参数和依赖关系,并确保你的测试的是将要部署的内容。部署脚本也省去了重复部署的烦恼。
编写部署脚本
在我们的项目根目录下创建一个名为
deploy
的新目录,并创建一个名为
001_deploy_token.ts
的新文件。
将下面的代码粘贴到
001_deploy_token.ts
中:
import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {DeployFunction} from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployments, getNamedAccounts} = hre; const {deploy} = deployments; const {deployer, tokenOwner} = await getNamedAccounts(); await deploy('Token', { from: deployer, args: [tokenOwner], log: true, }); }; export default func; func.tags = ['Token'];
你注意到了
getNamedAccounts
吗?
插件
hardhat-deploy
允许你命名你的账户,这里有 2 个记名账户:
-
deployer 将是用于部署合约的账户
-
tokenOwner,这可能是传递给 Token.sol 构造函数的另一个账户,它将接受最初的发行的代币。
这些账户需要在 hardhat.config.ts 中进行设置。
修改一下,让它看起来像这样:
import {HardhatUserConfig} from 'hardhat/types'; import 'hardhat-deploy'; import 'hardhat-deploy-ethers'; const config: HardhatUserConfig = { solidity: { version: '0.7.6', }, namedAccounts: { deployer: 0, tokenOwner: 1, }, paths: { sources: 'hide', }, }; export default config;
deployer
被设置为使用第一个账户 (index = 0),
tokenOwner
是第二个账户。
在你的终端上运行
yarn hardhat deploy
。你应该看到以下输出。
Nothing to compile deploying "Token" (tx: 0x259d19f33819ec8d3bd994f82912aec6af1a18ec5d74303cfb28d793a10ff683)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 592983 gas Done in 3.66s.
此次部署是在
内存
中的 hardhat 网络中进行的,上面的提示表明部署成功。
现在我们可以针对这个合约编写测试了,它的名称设置为与合约名称相同:
Token
。
不过我们先给上面的部署脚本中添加注释,解释每一行的重要性:
import {HardhatRuntimeEnvironment} from 'hardhat/types'; // this add the type from hardhat runtime environment import {DeployFunction} from 'hardhat-deploy/types'; // this add the type that a deploy function is expected to fullfil const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // 部署函数把 hardhat 运行时作为参数 const {deployments, getNamedAccounts} = hre; // we get the deployments and getNamedAccounts which are provided by hardhat-deploy const {deploy} = deployments; // the deployments field itself contains the deploy function const {deployer, tokenOwner} = await getNamedAccounts(); // we fetch the accounts. These can be configured in hardhat.config.ts as explained above await deploy('Token', { // this will create a deployment called 'Token'. By default it will look for an artifact with the same name. the contract option allows you to use a different artifact from: deployer, // deployer will be performing the deployment transaction args: [tokenOwner], // tokenOwner is the address used as the first argument to the Token contract's constructor log: true, // display the address and gas used in the console (not when run in test though) }); }; export default func; func.tags = ['Token']; // this setup a tag so you can execute the script on its own (and its dependencies)
5. 测试合约
在构建智能合约时,编写自动化测试是至关重要的,因为这关系到用户的资金。为此我们将使用 Hardhat 网络 ,这是一个为开发而设计的本地以太坊网络,是内置的,也是 Hardhat 中的默认网络。在我们的测试中,将使用 ethers.js 与前面部署的以太坊合约进行交互,并使用 Mocha[15] 作为我们的测试运行器。
编写测试
在项目根目录下创建一个名为
test
的新目录,并创建一个名为
Token.test.ts
的新文件。
把下面的代码粘贴到
Token.test.ts
中:
import {expect} from "./chai-setup"; import {ethers, deployments, getNamedAccounts} from 'hardhat'; describe("Token contract", function() { it("Deployment should assign the total supply of tokens to the owner", async function() { await deployments.fixture(["Token"]); const {tokenOwner} = await getNamedAccounts(); const Token = await ethers.getContract("Token"); const ownerBalance = await Token.balanceOf(tokenOwner); const supply = await Token.totalSupply(); expect(ownerBalance).to.equal(supply); }); });
依赖的
chai-setup
同样在测试文件夹中,内容如下:
import chaiModule from 'chai'; import {chaiEthers} from 'chai-ethers'; chaiModule.use(chaiEthers); export = chaiModule;
然后在终端上运行
npx hardhat test
。你应该看到以下输出。
$ npx hardhat test Token contract ✓ Deployment should assign the total supply of tokens to the owner (654ms) 1 passing (663ms)
这意味着测试通过了。现在我们来解释一下每一行代码。
await deployments.fixture(["Token"]);
还记得你之前写的 deploy 脚本,这一行允许在测试前执行。它还会自动生成一个 evm_snapshot,所以如果你写了很多测试,并且它们都指向那个 fixture,那么在背后就不会一次次重复部署。而是恢复到以前的状态,自动加快你的测试速度。
const {tokenOwner} = await getNamedAccounts();
这样你就可以访问 tokenOwner 的地址,也就是部署脚本中使用的地址。
const Token = await ethers.getContract("Token");
由于我们已经执行了部署脚本,所以我们可以很容易地通过名称访问已部署的合约。这就是这一行的作用,感谢
hardhat-deploy-ethers
插件,很容易得到一个可被调用的 ethers 合约。如果需要将该合约关联到一个特定的签名者,可以将地址作为额外的参数传递,比如
const TokenAsOwner = await ethers.getContract('Token', tokenOwner);
。
const ownerBalance = await Token.balanceOf(tokenOwner);
可以在
Token
上调用合约方法,通过调用
balanceOf()
来获取所有者账户的余额。
const supply = await Token.totalSupply();
在这里,再次使用
Token
实例调用一个智能合约函数,
totalSupply()
返回代币的发行量。
expect(ownerBalance).to.equal(supply);
最后,我们检查它是否等于
ownerBalance
。
为此,我们使用 Chai[16],它是一个断言库。这些断言函数被称为
matchers
,在这里使用的函数实际上来自
chai-ethers
包(它本身就是 Waffle chai matchers[17] 的一个 fork,没有不必要的依赖)。
使用不同的账户
如果你需要从默认账户以外的账户发送交易来测试你的代码,你可以使用
getContract
的第二个参数。
import {expect} from "./chai-setup"; import {ethers, deployments, getNamedAccounts, getUnnamedAccounts} from 'hardhat'; describe("Token contract", function() { it("Deployment should assign the total supply of tokens to the owner", async function() { await deployments.fixture(["Token"]); const {tokenOwner} = await getNamedAccounts(); const users = await getUnnamedAccounts(); const TokenAsOwner = await ethers.getContract("Token", tokenOwner); await TokenAsOwner.transfer(users[0], 50); expect(await TokenAsOwner.balanceOf(users[0])).to.equal(50); const TokenAsUser0 = await ethers.getContract("Token", users[0]); await TokenAsUser0.transfer(users[1], 50); expect(await TokenAsOwner.balanceOf(users[1])).to.equal(50); }); });
全面覆盖测试
现在我们已经介绍了测试合约所需的基础知识,这里有一个完整的代币测试 Case,其中有很多关于 Mocha 的附加信息以及如何构建测试。我们建议通过阅读。
但首先我们要添加一些实用函数,我们将在该测试套件中使用。
在
test
文件夹中创建一个
utils
文件夹,并在其中创建一个
index.ts
文件,内容如下:
import {Contract} from 'ethers'; import {ethers} from 'hardhat'; export async function setupUsers<T extends {[contractName: string]: Contract}>( addresses: string[], contracts: T ): Promise<({address: string} & T)[]> { const users: ({address: string} & T)[] = []; for (const address of addresses) { users.push(await setupUser(address, contracts)); } return users; } export async function setupUser<T extends {[contractName: string]: Contract}>( address: string, contracts: T ): Promise<{address: string} & T> { // eslint-disable-next-line @typescript-eslint/no-explicit-any const user: any = {address}; for (const key of Object.keys(contracts)) { user[key] = contracts[key].connect(await ethers.getSigner(address)); } return user as {address: string} & T; }
通过 utils 可以方便的创建账号,让测试简洁和容易阅读,例如下面的 Test.test.ts:
// We import Chai to use its asserting functions here. import {expect} from "./chai-setup"; // we import our utilities import {setupUsers, setupUser} from './utils'; // We import the hardhat environment field we are planning to use import {ethers, deployments, getNamedAccounts, getUnnamedAccounts} from 'hardhat'; // we create a stup function that can be called by every test and setup variable for easy to read tests async function setup () { // it first ensure the deployment is executed and reset (use of evm_snaphost for fast test) await deployments.fixture(["Token"]); // we get an instantiated contract in teh form of a ethers.js Contract instance: const contracts = { Token: (await ethers.getContract('Token')), }; // we get the tokenOwner const {tokenOwner} = await getNamedAccounts(); // get fet unnammedAccounts (which are basically all accounts not named in the config, useful for tests as you can be sure they do not have been given token for example) // we then use the utilities function to generate user object/ // These object allow you to write things like `users[0].Token.transfer(....)` const users = await setupUsers(await getUnnamedAccounts(), contracts); // finally we return the whole object (including the tokenOwner setup as a User object) return { ...contracts, users, tokenOwner: await setupUser(tokenOwner, contracts), }; } // `describe` is a Mocha function that allows you to organize your tests. It's // not actually needed, but having your tests organized makes debugging them // easier. All Mocha functions are available in the global scope. // `describe` receives the name of a section of your test suite, and a callback. // The callback must define the tests of that section. This callback can't be // an async function. describe("Token contract", function() { // You can nest describe calls to create subsections. describe("Deployment", function () { // `it` is another Mocha function. This is the one you use to define your // tests. It receives the test name, and a callback function. // If the callback function is async, Mocha will `await` it. it("Should set the right owner", async function () { // Expect receives a value, and wraps it in an Assertion object. These // objects have a lot of utility methods to assert values. // before the test, we call the fixture function. // while mocha have hooks to perform these automatically, they force you to declare the variable in greater scope which can introduce subttle errors // as such we prefers to have the setup called right at the beginning of the test. this also allow yout o name it accordingly for easier to read tests. const {Token} = await setup(); // This test expects the owner variable stored in the contract to be equal to our configured owner const {tokenOwner} = await getNamedAccounts(); expect(await Token.owner()).to.equal(tokenOwner); }); it("Should assign the total supply of tokens to the owner", async function () { const {Token, tokenOwner} = await setup(); const ownerBalance = await Token.balanceOf(tokenOwner.address); expect(await Token.totalSupply()).to.equal(ownerBalance); }); }); describe("Transactions", function () { it("Should transfer tokens between accounts", async function () { const {Token, users, tokenOwner} = await setup(); // Transfer 50 tokens from owner to users[0] await tokenOwner.Token.transfer(users[0].address, 50); const users0Balance = await Token.balanceOf(users[0].address); expect(users0Balance).to.equal(50); // Transfer 50 tokens from users[0] to users[1] // We use .connect(signer) to send a transaction from another account await users[0].Token.transfer(users[1].address, 50); const users1Balance = await Token.balanceOf(users[1].address); expect(users1Balance).to.equal(50); }); it("Should fail if sender doesn’t have enough tokens", async function () { const {Token, users, tokenOwner} = await setup(); const initialOwnerBalance = await Token.balanceOf(tokenOwner.address); // Try to send 1 token from users[0] (0 tokens) to owner (1000 tokens). // `require` will evaluate false and revert the transaction. await expect(users[0].Token.transfer(tokenOwner.address, 1) ).to.be.revertedWith("Not enough tokens"); // Owner balance shouldn't have changed. expect(await Token.balanceOf(tokenOwner.address)).to.equal( initialOwnerBalance ); }); it("Should update balances after transfers", async function () { const {Token, users, tokenOwner} = await setup(); const initialOwnerBalance = await Token.balanceOf(tokenOwner.address); // Transfer 100 tokens from owner to users[0]. await tokenOwner.Token.transfer(users[0].address, 100); // Transfer another 50 tokens from owner to users[1]. await tokenOwner.Token.transfer(users[1].address, 50); // Check balances. const finalOwnerBalance = await Token.balanceOf(tokenOwner.address); expect(finalOwnerBalance).to.equal(initialOwnerBalance - 150); const users0Balance = await Token.balanceOf(users[0].address); expect(users0Balance).to.equal(100); const users1Balance = await Token.balanceOf(users[1].address); expect(users1Balance).to.equal(50); }); }); });
下面是
yarn hardhat test
的输出。
$ yarn hardhat test Token contract Deployment ✓ Should set the right owner ✓ Should assign the total supply of tokens to the owner Transactions ✓ Should transfer tokens between accounts (199ms) ✓ Should fail if sender doesn’t have enough tokens ✓ Should update balances after transfers (111ms) 5 passing (1s)
请记住,当你运行
yarn hardhat test
时,如果合约在你上次运行测试后发生了变化,合约将被重新编译。
6. 使用 Hardhat 网络进行调试
Hardhat 内置了 Hardhat 网络 ,这是一个专为开发设计的本地以太坊网络。它允许你部署合约,运行测试和调试代码。这是 Hardhat 连接到的默认网络,所以你不需要设置任何东西就可以工作,只需要简单运行测试。
Solidity
console.log
。
当你在
Hardhat Network
上运行合约和测试时,可以在 Solidity 代码中调用
console.log()
打印日志信息和合约变量。要使用它,必须在合约代码中导入
Hardhat
的
console.log
。
例如:
pragma solidity 0.7.6; import "hardhat/console.sol"; contract Token { //... }
在
transfer()
函数中添加一些
console.log
,就像在 JavaScript 中使用一样。
function transfer(address to, uint256 amount) external { console.log("Sender balance is %s tokens", balances[msg.sender]); console.log("Trying to send %s tokens to %s", amount, to); require(balances[msg.sender] >= amount, "Not enough tokens"); balances[msg.sender] -= amount; balances[to] += amount; }
当你运行测试时,日志输出将显示:
$ yarn hardhat test Token contract Deployment ✓ Should set the right owner ✓ Should assign the total supply of tokens to the owner Transactions Sender balance is 1000 tokens Trying to send 50 tokens to 0xead9c93b79ae7c1591b1fb5323bd777e86e150d4 Sender balance is 50 tokens Trying to send 50 tokens to 0xe5904695748fe4a84b40b3fc79de2277660bd1d3 ✓ Should transfer tokens between accounts (373ms) ✓ Should fail if sender doesn’t have enough tokens Sender balance is 1000 tokens Trying to send 100 tokens to 0xead9c93b79ae7c1591b1fb5323bd777e86e150d4 Sender balance is 900 tokens Trying to send 100 tokens to 0xe5904695748fe4a84b40b3fc79de2277660bd1d3 ✓ Should update balances after transfers (187ms) 5 passing (2s)
查看文档 [18] 来了解更多关于这个功能的信息。
7. 部署到真实的网络
一旦准备好与其他人分享应用程序,你可能想做的是部署到一个实时网络。这样其他人就可以访问到。
处理真金白银的以太坊网络被称为
主网 (mainnet)
,还有其他不处理真金白银的网络,但确实能很好地模拟真实世界的场景, 这些被称为
测试网 (testnet)
,以太坊有多个测试网:
Ropsten
、
Kovan
、
Rinkeby_和_Goerli
。
在软件层面,部署到 testnet 和部署到 mainnet 是一样的。唯一不同的是连接的网络。
由于我们使用了
hardhat-deploy
插件,并且我们已经编写了部署脚本,现在只需要对部署到的网络进行一些配置,就可以部署到真实网络中。
正如我们的部署部分所解释的那样,你可以执行
yarn hardhat deploy
,但它只部署在内存中模式的默认的网络(
hardhat
)中,输出如下:
Nothing to compile deploying "Token" (tx: 0x259d19f33819ec8d3bd994f82912aec6af1a18ec5d74303cfb28d793a10ff683)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 592983 gas Done in 3.79s.
要部署到特定的网络,你需要添加
--network
,像这样。
yarn hardhat --network deploy
部署到远程网络
要部署到远程网络,如 mainnet 或任何 testnet,你需要在你的
hardhat.config.js
文件中添加一个
network
条目。我们将使用 Rinkeby 来做这个例子,但你可以类似地添加任何网络。
为了更方便地处理私钥和网络配置,在项目的根部创建了一个新的文件夹
utils
。
我们在其中创建一个文件
network.ts
,内容如下。
import 'dotenv/config'; export function node_url(networkName: string): string { if (networkName) { const uri = process.env['ETH_NODE_URI_' + networkName.toUpperCase()]; if (uri && uri !== '') { return uri; } } let uri = process.env.ETH_NODE_URI; if (uri) { uri = uri.replace('{{networkName}}', networkName); } if (!uri || uri === '') { if (networkName === 'localhost') { return 'http://localhost:8545'; } return ''; } if (uri.indexOf('{{') >= 0) { throw new Error( `invalid uri or network not supported by nod eprovider : ${uri}` ); } return uri; } export function getMnemonic(networkName?: string): string { if (networkName) { const mnemonic = process.env['MNEMONIC_' + networkName.toUpperCase()]; if (mnemonic && mnemonic !== '') { return mnemonic; } } const mnemonic = process.env.MNEMONIC; if (!mnemonic || mnemonic === '') { return 'test test test test test test test test test test test junk'; } return mnemonic; } export function accounts(networkName?: string): {mnemonic: string} { return {mnemonic: getMnemonic(networkName)}; }
然后我们可以修改
hardhat.config.ts
文件,使其包含以下内容。
import {HardhatUserConfig} from 'hardhat/types'; import 'hardhat-deploy'; import 'hardhat-deploy-ethers'; import {node_url, accounts} from './utils/network'; const config: HardhatUserConfig = { solidity: { version: '0.7.6', }, networks: { rinkeby: { url: node_url('rinkeby'), accounts: accounts('rinkeby'), }, }, namedAccounts: { deployer: 0, tokenOwner: 1, }, paths: { sources: 'hide', }, }; export default config;
最后,我们需要设置环境变量,让
utils/networks.ts
从
.env
中自动读取。
创建一个
.env
,在其中写上你自己的 alchemy api 键和 rinkeby 的助记词。
ETH_NODE_URI_RINKEBY=https://eth-rinkeby.alchemyapi.io/v2/ MNEMONIC_RINKEBY=for rinkeby>
我们使用的是 Alchemy[19],你可以使用任何其他指向以太坊节点或网关的 URL。
要在 Rinkeby 上部署,你需要把 rinkeby-ETH 发送到要进行部署的地址。你可以从水龙头(一个免费分发测试-ETH 的服务, 如 https://faucet.metamask.io/ )那里获得一些 ETH 的测试网。
你可以通过以下链接获得一些 ETH,用于其他测试网。
-
Kovan 水龙头 [20]
-
Rinkeby 水龙头 [21]
-
Goerli 水龙头 [22]
然后运行:
yarn hardhat --network rinkeby deploy
如果一切顺利,你应该看到这样的内容:
Nothing to compile deploying "Token" (tx: 0xb40879c3162e6a924cfadfc1027c4629dd57ee4ba08a5f8af575be1c751cd515)...: deployed at 0x8bDFEf5f67685725BC0eD9f54f20A2A4d3FEDA98 with 475842 gas
你还会看到在
deployments/rinkeby
文件夹中创建了一些文件。
最值得注意的是,你会看到
deployments/rinkeby/Token.json
,其中包含了你部署的合约信息,包括 addres、abi 以及用于创建合约的 solidity 输入。
然后你可以用 sourcify 或 etherscan 来验证它。
对于 sourceify,你可以执行以下操作:
yarn hardhat --network rinkeby sourcify
这应该给你以下输出(你的地址会不同)。
verifying Token (0x8bDFEf5f67685725BC0eD9f54f20A2A4d3FEDA98 on chain 4) ... => contract Token is now verified
对于 etherscan,你可以执行以下工作 (注意,你也可以通过 env 变量 ETHERSCAN_API_KEY 指定 api 密钥):
yarn hardhat --network rinkeby etherscan-verify --api-key
然后你应该看到。
verifying Token (0x8bDFEf5f67685725BC0eD9f54f20A2A4d3FEDA98) ... waiting for result... => contract Token is now verified
本翻译由 Cell Network[23] 赞助支持。
来源: https://github.com/wighawag/tutorial-hardhat-deploy
参考资料
[1]
登链翻译计划 : https://github.com/lbc-team/Pioneer
[2]
翻译小组 : https://learnblockchain.cn/people/412
[3]
Tiny 熊 : https://learnblockchain.cn/people/15
[4]
这个安装说明安装 git: https://www.atlassian.com/git/tutorials/install-git
[5]
node 版本管理器 (nvm): http://github.com/creationix/nvm
[6]
Git 的 Windows 安装程序 : https://git-scm.com/download/win
[7]
此处 : https://nodejs.org/dist/latest-v12.x
[8]
这里 : https://github.com/nodesource/distributions#debinstall
[9]
nvm: http://github.com/creationix/nvm
[10]
相同的安装说明 : #windows
[11]
这里 : https://nodejs.org/en/download/releases/
[12]
yarn: yarnpkg.com
[13]
创建任务 : https://hardhat.org/guides/create-task.html
[14]
ERC20: https://learnblockchain.cn/tags/ERC20
[15]
Mocha: https://mochajs.org/
[16]
Chai: https://www.chaijs.com/
[17]
Waffle chai matchers: https://getwaffle.io/
[18]
文档 : https://hardhat.org/hardhat-network/
[19]
Alchemy: https://alchemyapi.io/?r=7d60e34c-b30a-4ffa-89d4-3c4efea4e14b
[20]
Kovan 水龙头 : https://faucet.kovan.network/
[21]
Rinkeby 水龙头 : https://faucet.rinkeby.io/
[22]
Goerli 水龙头 : https://goerli-faucet.slock.it/
[23]
Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain