3. 使用 Hardhat
使用 Hardhat 在 ChainIDE 上快速搭建一个 dApp。
Last updated
使用 Hardhat 在 ChainIDE 上快速搭建一个 dApp。
Last updated
注:以 GIthub 方式登录 ChainIDE 后才可使用 Sandbox 功能。
打开 Sandbox
我们将探索使用示例合约创建 Hardhat 项目的基础知识、该合约的测试以及部署它的脚本。
要创建示例项目,请在项目文件夹中运行 npx hardhat init
:
$ npx hardhat init
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.19.1 👷
? What do you want to do? …
❯ Create a JavaScript project
Create a TypeScript project
Create an empty hardhat.config.js
Quit
让我们创建 JavaScript 或 TypeScript 项目,并完成这些步骤来编译、测试和部署示例合约。我们建议使用 TypeScript,但如果您不熟悉它,请选择 JavaScript。
要首先快速了解可用的内容和正在发生的情况,请在项目文件夹中运行 npx hardhat
:
$ npx hardhat
Hardhat version 2.9.9
Usage: hardhat [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]
GLOBAL OPTIONS:
--config A Hardhat config file.
--emoji Use emoji in messages.
--help Shows this message, or a task's help if its name is provided
--max-memory The maximum amount of memory that Hardhat can use.
--network The network to connect to.
--show-stack-traces Show stack traces.
--tsconfig A TypeScript config file.
--verbose Enables Hardhat verbose logging
--version Shows hardhat's version.
AVAILABLE TASKS:
check Check whatever you need
clean Clears the cache and deletes all artifacts
compile Compiles the entire project, building all artifacts
console Opens a hardhat console
coverage Generates a code coverage report for tests
flatten Flattens and prints contracts and their dependencies
help Prints this message
node Starts a JSON-RPC server on top of Hardhat Network
run Runs a user-defined script after compiling the project
test Runs mocha tests
typechain Generate Typechain typings for compiled contracts
verify Verifies contract on Etherscan
To get help for a specific task run: npx hardhat help [task]
可用任务列表包括内置任务以及任何已安装插件附带的任务。 npx hardhat
是您了解哪些任务可以运行的起点。
接下来,如果您查看 contracts/
文件夹,您将看到 Lock.sol
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
// Uncomment this line to use console.log
// import "hardhat/console.sol";
contract Lock {
uint public unlockTime;
address payable public owner;
event Withdrawal(uint amount, uint when);
constructor(uint _unlockTime) payable {
require(
block.timestamp < _unlockTime,
"Unlock time should be in the future"
);
unlockTime = _unlockTime;
owner = payable(msg.sender);
}
function withdraw() public {
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);
require(block.timestamp >= unlockTime, "You can't withdraw yet");
require(msg.sender == owner, "You aren't the owner");
emit Withdrawal(address(this).balance, block.timestamp);
owner.transfer(address(this).balance);
}
}
要编译它,只需运行:
npx hardhat compile
如果您创建了 TypeScript 项目,此任务还将使用 TypeChain 生成 TypeScript 绑定。
您的项目附带使用 Mocha、Chai 和 Ethers.js 的测试。
如果您查看 test/
文件夹,您将看到一个测试文件:
import {
time,
loadFixture,
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Lock", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
const lockedAmount = ONE_GWEI;
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners();
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
return { lock, unlockTime, lockedAmount, owner, otherAccount };
}
describe("Deployment", function () {
it("Should set the right unlockTime", async function () {
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
expect(await lock.unlockTime()).to.equal(unlockTime);
});
it("Should set the right owner", async function () {
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
expect(await lock.owner()).to.equal(owner.address);
});
it("Should receive and store the funds to lock", async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount
);
});
it("Should fail if the unlockTime is not in the future", async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest();
const Lock = await ethers.getContractFactory("Lock");
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
"Unlock time should be in the future"
);
});
});
describe("Withdrawals", function () {
describe("Validations", function () {
it("Should revert with the right error if called too soon", async function () {
const { lock } = await loadFixture(deployOneYearLockFixture);
await expect(lock.withdraw()).to.be.revertedWith(
"You can't withdraw yet"
);
});
it("Should revert with the right error if called from another account", async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture
);
// We can increase the time in Hardhat Network
await time.increaseTo(unlockTime);
// We use lock.connect() to send a transaction from another account
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
"You aren't the owner"
);
});
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture
);
// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;
});
});
describe("Events", function () {
it("Should emit an event on withdrawals", async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw())
.to.emit(lock, "Withdrawal")
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
});
});
describe("Transfers", function () {
it("Should transfer the funds to the owner", async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount]
);
});
});
});
});
您可以使用 npx hardhat test
运行测试:
$ npx hardhat test
Generating typings for: 2 artifacts in dir: typechain-types for target: ethers-v6
Successfully generated 6 typings!
Compiled 2 Solidity files successfully
Lock
Deployment
✔ Should set the right unlockTime (610ms)
✔ Should set the right owner
✔ Should receive and store the funds to lock
✔ Should fail if the unlockTime is not in the future
Withdrawals
Validations
✔ Should revert with the right error if called too soon
✔ Should revert with the right error if called from another account
✔ Shouldn't fail if the unlockTime has arrived and the owner calls it
Events
✔ Should emit an event on withdrawals
Transfers
✔ Should transfer the funds to the owner
9 passing (790ms)
接下来,为了部署合约,我们将使用 Hardhat 脚本。
在 scripts/
文件夹中,您将找到一个包含以下代码的文件:
import { ethers } from "hardhat";
async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
const unlockTime = currentTimestampInSeconds + 60;
const lockedAmount = ethers.parseEther("0.001");
const lock = await ethers.deployContract("Lock", [unlockTime], {
value: lockedAmount,
});
await lock.waitForDeployment();
console.log(
`Lock with ${ethers.formatEther(
lockedAmount
)}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}`
);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
您可以使用 npx hardhat run scripts/deploy.ts
运行它:
$ npx hardhat run scripts/deploy.ts
Lock with 1 ETH deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
默认情况下,Hardhat 将在启动时启动一个新的内存中 Hardhat 网络实例。还可以以独立方式运行 Hardhat Network,以便外部客户端可以连接到它。这可以是钱包、Dapp 前端或脚本。
要以这种方式运行 Hardhat Network,请运行 npx hardhat node
:
$ npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
这将向 Hardhat 网络公开 JSON-RPC 接口。要使用它,请将您的钱包或应用程序连接到 http://127.0.0.1:8545
。
如果想要其他客户端可以通过链接访问到上面启动的 Hardhat,请参考 ChainIDE - 端口转发 转发 HTTP 8545 端口,并复制其地址 (例如:https://sandbox-1e82a87a951241179f98494a6dbb2617-ethereum-8545.uat-sandbox.chainide.com)。
如果您想将 Hardhat 连接到此节点,例如要针对它运行部署脚本,您只需使用 --network localhost
运行它。
要尝试此操作,请使用 npx hardhat node
启动节点并使用 network
选项重新运行部署脚本:
npx hardhat run scripts/deploy.ts --network localhost
恭喜!您已经创建了一个项目并编译、测试和部署了智能合约。
本快速入门介绍了 Hardhat 项目生命周期的基础知识,但还有很多东西需要学习。请继续阅读 Hardhat 官方文档 和 BNB Chain: Using Hardhat。