Hardhat 是一个用于以太坊软件开发的完整开发环境。它包含了多个组件,用于编辑、编译、调试和部署智能合约和去中心化应用程序(dApps),这些组件共同协作,提供了全面的开发体验。
Hardhat Runner 是你使用 Hardhat
时与之交互的主要组件。它是一个灵活且可扩展的任务执行工具,帮助你管理和自动化开发智能合约和
dApps 过程中所需的任务。每次从命令行运行 Hardhat
时,实际上是运行一个任务,例如 npx hardhat compile
运行内置的编译任务。任务可以调用其他任务,允许定义复杂的工作流程,用户和插件也可以覆盖现有任务,实现可定制和可扩展的工作流。
我们推荐使用 Hardhat 官方的 VS Code 插件,这个插件为 Solidity 提供了高级支持。
Hardhat 是通过在项目中本地安装来使用的,这样可以保证环境的可重现性,并避免未来版本冲突。
要安装 Hardhat,首先需要创建一个 npm 项目,进入一个空文件夹并运行
npm init
,然后执行以下命令安装 Hardhat:
npm install --save-dev hardhat
安装完成后,通过 npx hardhat init
使用 Hardhat。
你可以通过 npx hardhat init
在你的项目文件夹中创建一个
Hardhat 项目。接下来,你可以选择创建 JavaScript 或 TypeScript
项目,并按照步骤编译、测试和部署示例合约。
要了解项目中的可用任务,你可以运行 npx hardhat
查看所有任务。例如:
npx hardhat
项目文件夹中的 contracts/
目录包含合约代码。要编译合约,运行以下命令:
npx hardhat compile
如果你创建的是 TypeScript 项目,这个任务还会生成 TypeScript 绑定。
你的项目包含了使用 Mocha、Chai、Ethers.js 和 Hardhat Ignition
编写的测试。你可以在 test/
文件夹中找到测试文件,并通过以下命令运行测试:
npx hardhat test
要部署合约,你可以使用 Hardhat Ignition 模块。创建部署脚本并使用以下命令部署:
npx hardhat ignition deploy ./ignition/modules/Lock.ts
Hardhat 可以作为一个独立的本地节点运行,让外部客户端(如钱包或 DApp)连接到它。运行以下命令启动本地节点:
npx hardhat node
然后将你的钱包或应用程序连接到
http://127.0.0.1:8545
。
Hardhat 的配置通过 hardhat.config.js
文件进行管理,该文件通常位于项目的根目录。即使
hardhat.config.js
是空的,Hardhat
也可以正常工作。然而,建议根据需要自定义配置,以适应项目的需求。
你需要从 hardhat.config.js
中导出一个对象来设置配置。该对象可以包含以下几项配置选项:
例如:
module.exports = {
defaultNetwork: "sepolia",
networks: {
hardhat: {
},
sepolia: {
url: "https://sepolia.infura.io/v3/<key>",
accounts: [privateKey1, privateKey2]
}
},
solidity: {
version: "0.8.27",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts"
},
mocha: {
timeout: 40000
}
};
Hardhat 支持两类网络:内置的 Hardhat 网络和基于 JSON-RPC 的网络。
Hardhat 网络: 内置开发网络,自动创建并模拟以太坊网络中的智能合约执行和错误报告。可以用于测试和调试。
基于 JSON-RPC 的网络: 连接到外部节点的网络,比如 Infura 或 Alchemy。这种网络的配置包括 URL、账户信息和其他网络相关的设置。
示例配置:
module.exports = {
networks: {
sepolia: {
url: "https://sepolia.infura.io/v3/YOUR_PROJECT_ID",
accounts: ["0xprivateKey1", "0xprivateKey2"]
}
}
};
你可以通过设置 accounts
字段为一个包含以下字段的对象来使用 HD 钱包:
m/44'/60'/0'/0
示例配置:
module.exports = {
networks: {
sepolia: {
url: "https://sepolia.infura.io/v3/YOUR_PROJECT_ID",
accounts: {
mnemonic: "test test test test test test test test test test test junk"
}
}
}
};
Solidity 编译器配置项 solidity
可以设置编译器的版本以及优化选项:
module.exports = {
solidity: {
version: "0.8.27",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
};
你还可以为多个编译器配置不同的设置,或者通过 overrides
针对特定的文件进行自定义配置。
Hardhat 允许你自定义项目中的不同路径:
./contracts
./test
./cache
./artifacts
module.exports = {
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts"
}
};
Hardhat 使用 Mocha 作为测试框架,mocha
配置项接受与
Mocha 本身相同的配置选项,例如:
module.exports = {
mocha: {
timeout: 40000
}
};
默认的 EVM 版本由 solc
的版本决定,你可以通过
evmVersion
来设置一个特定的 EVM 版本。
module.exports = {
solidity: {
version: "0.8.21",
settings: {
evmVersion: "shanghai"
}
}
};
通过合理配置 Hardhat,你可以为以太坊智能合约开发创建一个高度定制化的工作流,支持从编写、编译到测试、部署等各个环节。
Hardhat 是一个用于以太坊智能合约开发的开发环境,帮助开发者自动化和管理智能合约的开发流程。它包含多个组件,每个组件各有其特定的功能。让我们来逐个解释一下这几个重要的 Hardhat 组件,并简要说明如何取用它们。
Hardhat Runner 是 Hardhat 的核心组件,是一个灵活且可扩展的任务运行器,帮助你管理和自动化开发智能合约和 dApps 中的常见任务。你通过命令行界面(CLI)与 Hardhat Runner 进行交互,运行编译、测试、部署等任务。
npx hardhat
使用 Hardhat
Runner。在项目目录下,可以运行以下常见命令: npx hardhat compile # 编译智能合约
npx hardhat test # 运行测试
npx hardhat deploy # 部署智能合约
npx hardhat run # 运行脚本
task("hello", "Prints 'Hello, Hardhat!'").setAction(async () => {
console.log("Hello, Hardhat!");
});
Hardhat Network 是一个为开发设计的本地以太坊网络节点。它支持在本地快速部署、测试和调试智能合约。这个网络只在本地运行,不需要连接外部网络,适合开发阶段的快速迭代。
npx hardhat node
启动后,你可以通过 JSON-RPC 与它交互,或者在其他任务中连接到这个本地网络。 - 在测试中,Hardhat Network 是默认网络,无需额外设置:
npx hardhat test
Hardhat Ignition 是一个声明式的智能合约部署系统,简化了复杂的部署过程。它允许你定义模块,描述如何部署和初始化智能合约,并处理多个部署步骤和事务。
ignition/modules
文件夹中,像这样: const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const TokenModule = buildModule("TokenModule", (m) => {
const token = m.contract("Token");
return { token };
});
module.exports = TokenModule;
npx hardhat ignition deploy ./ignition/modules/Token.js --network <network-name>
Hardhat for Visual Studio Code 是官方提供的 Visual Studio Code 扩展,增加了对 Solidity 智能合约语言的支持,并提供了与 Hardhat 项目的编辑器集成。它能帮助你在编写 Solidity 时提供语法高亮、自动补全和错误提示等功能。
Hardhat for Visual Studio Code
插件。Hardhat Chai Matchers 是一个 Chai 的扩展插件,它增加了 Ethereum 智能合约的特定匹配功能,帮助你编写和读取智能合约的测试更加简单直观。例如,你可以测试事件是否触发、事务是否正确回滚,或者钱包的余额是否发生了变化。
@nomicfoundation/hardhat-chai-matchers
插件: npm install --save-dev @nomicfoundation/hardhat-chai-matchers
const { expect } = require("chai");
it("Should emit an event on transfer", async function () {
await expect(token.transfer(addr1.address, 50))
.to.emit(token, "Transfer")
.withArgs(owner.address, addr1.address, 50);
});
Hardhat Network Helpers 提供了一个便捷的 JavaScript 接口,用于与 Hardhat Network 的 JSON-RPC 功能交互。它简化了在测试和开发过程中与区块链的低级交互。
const { time, loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
time
等工具来处理区块链中的时间操作,例如增加时间: await time.increase(3600); // 增加1小时
总结一下,这些 Hardhat 组件的主要用途如下: - Hardhat Runner:核心任务运行器,帮助管理开发任务,如编译、测试、部署。 - Hardhat Network:内置的本地以太坊网络节点,专为开发和调试设计。 - Hardhat Ignition:声明式部署系统,简化智能合约部署。 - Hardhat for Visual Studio Code:VS Code 插件,提供 Solidity 语言支持。 - Hardhat Chai Matchers:Chai 扩展,专门用于测试智能合约的断言功能。 - Hardhat Network Helpers:用于简化与区块链交互的工具,特别是在测试和调试中。
你可以根据需要在不同阶段使用这些组件来加速你的智能合约开发过程。
以太坊的大多数库和工具都是用 JavaScript 编写的,Hardhat 也是如此。如果你不熟悉 Node.js,Node.js 是基于 Chrome 的 V8 JavaScript 引擎构建的 JavaScript 运行环境。它是目前最流行的在浏览器外运行 JavaScript 的解决方案,Hardhat 就是构建在其基础上的。
提示: 如果你使用 Visual Studio Code,可以尝试 Hardhat 的官方扩展,它为 Solidity 提供了高级支持。
如果你已经安装了 Node.js >= 18.0 版本,可以跳过此部分。如果没有,可以按照以下步骤在 Ubuntu、MacOS 和 Windows 系统上安装。
在终端中运行以下命令:
sudo apt update
sudo apt install curl git
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
确保你已经安装了 Git。如果没有安装,可以参考 Git 安装指南。
有多种方法可以在 MacOS 上安装 Node.js,这里我们使用 Node Version Manager (nvm)。在终端中运行以下命令:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install 22
nvm use 22
nvm alias default 22
npm install npm --global # 升级 npm 到最新版本
如果你使用 Windows 系统,强烈推荐使用 Windows Subsystem for Linux (WSL 2)。虽然不使用 WSL 也可以安装 Hardhat,但使用 WSL 可以获得更好的兼容性和性能。
你可以参考 WSL 2 安装指南 以安装 Node.js,并确保在 WSL 中安装了 Git。
如果你已经安装了 Node.js 但版本较旧且不被 Hardhat 支持,可以按照以下步骤进行升级。
在终端中运行以下命令移除当前版本的 Node.js:
sudo apt remove nodejs
找到你要安装的 Node.js 版本 Node.js 版本列表,并按照安装说明操作。
然后在终端中运行以下命令重新安装 Node.js:
sudo apt update && sudo apt install nodejs
你可以使用 nvm 切换 Node.js 版本。要升级到 Node.js 22.x,在终端中运行以下命令:
nvm install 22
nvm use 22
nvm alias default 22
npm install npm --global # 升级 npm 到最新版本
请按照之前的安装步骤重新安装,并选择合适的版本。你可以在 这里 查看所有可用的 Node.js 版本。
我们将使用 Node.js 的包管理器 (npm) 来安装 Hardhat。npm 是一个包管理器,同时也是一个在线 JavaScript 代码的存储库。
你可以选择其他包管理器,但我们建议你使用 npm 7 或更高版本来完成本指南。如果你已经按照前一节的步骤安装了 Node.js,那么应该已经有了合适版本的 npm。
打开一个新的终端,运行以下命令创建一个新文件夹:
mkdir hardhat-tutorial
cd hardhat-tutorial
然后,初始化一个 npm 项目。你会被要求回答一些问题:
npm init
接下来,在项目目录中安装 Hardhat:
npm install --save-dev hardhat
安装完成后,运行以下命令来初始化 Hardhat 项目:
npx hardhat init
选择 Create an empty hardhat.config.js
并按下回车:
$ 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.22.13 👷
? What do you want to do? …
Create a JavaScript project
Create a TypeScript project
Create a TypeScript project (with Viem)
❯ Create an empty hardhat.config.js
Quit
此时,Hardhat 会创建一个空的 hardhat.config.js
文件,它通常位于项目的根目录。即使这个配置文件是空的,Hardhat
也能正常工作。
Hardhat 的架构围绕 任务 (tasks) 和 插件 (plugins) 构建。大多数 Hardhat 的功能来自插件,你可以根据需要自由选择插件来使用。
每次你从命令行运行 Hardhat 时,实际上是在执行一个任务。例如,运行
npx hardhat compile
是在执行编译任务。你可以通过运行
npx hardhat
来查看当前项目中可用的任务。
Hardhat 不限制你使用什么工具,但它提供了一些内置的默认设置,所有这些默认设置都可以被覆盖。大多数情况下,你可以通过插件将需要的工具集成到 Hardhat 中。
在本教程中,我们将使用推荐的插件 @nomicfoundation/hardhat-toolbox,它包含了开发智能合约所需的全部工具。
安装插件:
npm install --save-dev @nomicfoundation/hardhat-toolbox
然后,编辑 hardhat.config.js
,在文件中添加以下内容:
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.27",
};
完成这些步骤后,你的 Hardhat 项目就准备好了,可以继续编写和测试智能合约了。
在本节中,我们将创建一个简单的智能合约,模拟一个可以转移的代币。该代币合约的逻辑非常基础,主要包括以下几点:
提示: 你可能听说过 ERC-20 标准,这是以太坊上的一种通用代币标准。像 DAI 和 USDC 这样的代币都实现了 ERC-20 标准。为了简化操作,我们在本教程中构建的代币不会实现 ERC-20 标准。
首先,在项目目录下创建一个新的 contracts
文件夹,并在该文件夹中创建 Token.sol
文件。
将以下代码粘贴到 Token.sol
文件中。代码中充满了注释,帮助你理解 Solidity 的基础知识。
//SPDX-License-Identifier: UNLICENSED
// Solidity 文件必须以这个 pragma 开始。
// 它用于 Solidity 编译器验证版本。
pragma solidity ^0.8.0;
// 这是智能合约的主要构建模块。
contract Token {
// 用一些字符串类型变量来标识代币。
string public name = "My Hardhat Token";
string public symbol = "MHT";
// 固定的代币数量,存储在无符号整数类型变量中。
uint256 public totalSupply = 1000000;
// 地址类型变量用于存储以太坊账户。
address public owner;
// 映射是一个键/值对的存储。这里我们存储每个账户的余额。
mapping(address => uint256) balances;
// Transfer 事件帮助链外应用了解合约内发生的事情。
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/**
* 合约初始化。
*/
constructor() {
// 总供应量分配给交易的发送者,即部署合约的账户。
balances[msg.sender] = totalSupply;
owner = msg.sender;
}
/**
* 转移代币的函数。
*
* `external` 修饰符表示该函数只能从合约外部调用。
*/
function transfer(address to, uint256 amount) external {
// 检查交易的发送者是否有足够的代币。
// 如果 `require` 的第一个参数为 `false`,则交易会回滚。
require(balances[msg.sender] >= amount, "Not enough tokens");
// 转移金额。
balances[msg.sender] -= amount;
balances[to] += amount;
// 通知链外应用转移信息。
emit Transfer(msg.sender, to, amount);
}
/**
* 只读函数,用于获取指定账户的代币余额。
*
* `view` 修饰符表明它不会修改合约状态,
* 这允许我们在不执行交易的情况下调用它。
*/
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
}
提示: Solidity 文件使用
.sol
作为扩展名。我们建议将文件名与合约名称保持一致,这也是一种常见的编程实践。
为了编译合约,在终端中运行以下命令:
npx hardhat compile
编译任务是 Hardhat 的内置任务之一。
你应该看到类似以下的输出:
$ npx hardhat compile
Compiled 1 Solidity file successfully (evm target: paris).
这意味着合约已成功编译,现在可以在后续步骤中使用。
在构建智能合约时编写自动化测试至关重要,因为你用户的资金可能会受到威胁。
我们将使用 Hardhat Network 进行测试,这是一个内置于
Hardhat
的本地以太坊网络,专为开发而设计。你无需额外设置即可使用它。我们将使用
ethers.js
与之前构建的以太坊合约交互,并使用
Mocha 作为测试运行器。
首先,在项目的根目录下创建一个名为 test
的新文件夹,并在其中创建一个名为 Token.js
的文件。
将以下代码粘贴到 Token.js
文件中:
const { expect } = require("chai");
describe("Token contract", function () {
it("Deployment should assign the total supply of tokens to the owner", async function () {
const [owner] = await ethers.getSigners();
const hardhatToken = await ethers.deployContract("Token");
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
接下来,在终端中运行
npx hardhat test
。你应该看到如下输出:
$ npx hardhat test
Token contract
✓ Deployment should assign the total supply of tokens to the owner (654ms)
1 passing (663ms)
这表示测试通过了。
const [owner] = await ethers.getSigners();
: 通过
ethers.js
获取当前连接节点的账户列表,并保留第一个账户。这个账户是合约的部署者。
const hardhatToken = await ethers.deployContract("Token");
:
部署 Token
合约,并返回一个包含所有合约方法的
Contract
实例。
const ownerBalance = await hardhatToken.balanceOf(owner.address);
:
调用合约中的 balanceOf()
方法,获取合约部署者的代币余额。
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
:
使用 Chai 的 expect()
断言来检查总供应量是否与所有者的余额一致。
如果需要用其他账户发送交易,可以使用 connect()
方法连接到另一个 Signer
(账户):
const { expect } = require("chai");
describe("Token contract", function () {
// ...之前的测试...
it("Should transfer tokens between accounts", async function () {
const [owner, addr1, addr2] = await ethers.getSigners();
const hardhatToken = await ethers.deployContract("Token");
// 从 owner 向 addr1 转移 50 个代币
await hardhatToken.transfer(addr1.address, 50);
expect(await hardhatToken.balanceOf(addr1.address)).to.equal(50);
// 从 addr1 向 addr2 转移 50 个代币
await hardhatToken.connect(addr1).transfer(addr2.address, 50);
expect(await hardhatToken.balanceOf(addr2.address)).to.equal(50);
});
});
为了避免重复代码并提高测试性能,我们可以使用 fixtures 来复用测试设置:
const {
loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = require("chai");
describe("Token contract", function () {
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const hardhatToken = await ethers.deployContract("Token");
return { hardhatToken, owner, addr1, addr2 };
}
it("Should assign the total supply of tokens to the owner", async function () {
const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
it("Should transfer tokens between accounts", async function () {
const { hardhatToken, owner, addr1, addr2 } = await loadFixture(deployTokenFixture);
await expect(hardhatToken.transfer(addr1.address, 50))
.to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);
await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
.to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
});
});
在这里,deployTokenFixture
函数执行了必要的合约部署和账户设置,接着我们通过
loadFixture
函数加载该设置,避免在每个测试中重复执行部署操作。
我们编写了一个完整的测试套件,包含所有重要的测试场景:
const { expect } = require("chai");
const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
describe("Token contract", function () {
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const hardhatToken = await ethers.deployContract("Token");
await hardhatToken.waitForDeployment();
return { hardhatToken, owner, addr1, addr2 };
}
describe("Deployment", function () {
it("Should set the right owner", async function () {
const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
expect(await hardhatToken.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
const { hardhatToken, owner, addr1, addr2 } = await loadFixture(deployTokenFixture);
await expect(hardhatToken.transfer(addr1.address, 50))
.to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);
await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
.to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
});
it("Should emit Transfer events", async function () {
const { hardhatToken, owner, addr1, addr2 } = await loadFixture(deployTokenFixture);
await expect(hardhatToken.transfer(addr1.address, 50))
.to.emit(hardhatToken, "Transfer")
.withArgs(owner.address, addr1.address, 50);
await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
.to.emit(hardhatToken, "Transfer")
.withArgs(addr1.address, addr2.address, 50);
});
it("Should fail if sender doesn't have enough tokens", async function () {
const { hardhatToken, owner, addr1 } = await loadFixture(deployTokenFixture);
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
await expect(hardhatToken.connect(addr1).transfer(owner.address, 1))
.to.be.revertedWith("Not enough tokens");
expect(await hardhatToken.balanceOf(owner.address)).to.equal(initialOwnerBalance);
});
});
});
运行完整的测试套件:
$ npx hardhat test
Token contract
Deployment
✔ Should set the right owner (889ms)
✔ Should assign the total supply of tokens to the owner
Transactions
✔ Should transfer tokens between accounts (121ms)
✔ Should emit Transfer events
✔ Should fail if sender doesn't have enough tokens (74ms)
5 passing (1s)
每次运行 npx hardhat test
时,如果合约有变化,它们会被自动编译。
Hardhat 内置了 Hardhat Network,这是一个本地以太坊网络,专为开发而设计。它允许你在本地机器上部署合约、运行测试和调试代码。Hardhat Network 是默认网络,因此无需额外设置即可使用。
console.log
在 Hardhat Network 上运行合约和测试时,你可以使用
console.log()
在 Solidity
代码中打印日志消息或变量。这非常类似于 JavaScript 中的
console.log
,但用于调试 Solidity 合约。
为了使用 console.log()
,你需要先导入
hardhat/console.sol:
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Token {
//...
}
接下来,你可以在 transfer()
函数中添加一些
console.log
调用,类似于在 JavaScript 中使用的方式:
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Not enough tokens");
console.log(
"Transferring from %s to %s %s tokens",
msg.sender,
to,
amount
);
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
当你运行测试时,会显示日志输出,帮助你调试合约行为:
$ npx hardhat test
Token contract
Deployment
✓ Should set the right owner
✓ Should assign the total supply of tokens to the owner
Transactions
Transferring from 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 50 tokens
Transferring from 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 to 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc 50 tokens
✓ Should transfer tokens between accounts (373ms)
✓ Should fail if sender doesn’t have enough tokens
Transferring from 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 50 tokens
Transferring from 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 to 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc 50 tokens
✓ Should update balances after transfers (187ms)
5 passing (2s)
通过使用 console.log()
,你可以轻松地调试 Solidity
合约并查看函数内部的行为和变量值。这在处理复杂的逻辑或排查错误时非常有用。
有关更多信息,请查阅 Hardhat 文档 了解此功能的详细内容。
当你准备好与他人分享你的 dApp 时,你可以将其部署到一个实时网络。这样,其他人就可以访问你的项目,而不需要在本地运行。
以太坊的主网(“mainnet”)处理的是实际的资金,但也有一些独立的测试网络(“testnets”),这些网络不涉及真实货币。测试网络提供了模拟真实环境的共享阶段,而不会有资金风险。以太坊有多个测试网络,例如 Sepolia 和 Goerli。我们建议你将合约部署到 Sepolia 测试网络。
从软件角度看,部署到测试网络与部署到主网的步骤是相同的,唯一的区别是连接到的网络不同。我们将展示如何使用 Hardhat Ignition 部署合约。
在 Hardhat Ignition 中,部署是通过 Ignition Modules 来定义的。这些模块是用于描述部署的抽象,即指定要部署的内容的 JavaScript 函数。
首先,在项目根目录中创建一个 ignition
文件夹,接着在其中创建 modules
目录。在
modules
目录中创建一个名为 Token.js
的文件,并粘贴以下代码:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const TokenModule = buildModule("TokenModule", (m) => {
const token = m.contract("Token");
return { token };
});
module.exports = TokenModule;
要指定部署的网络,可以在执行任何任务时使用 --network
参数,例如:
npx hardhat ignition deploy ./ignition/modules/Token.js --network <network-name>
如果你不使用 --network
参数,Hardhat 会默认连接到
Hardhat
Network,这是一个本地的嵌入式网络。在这种情况下,部署会在
Hardhat 运行结束时丢失,但这仍然对测试部署代码是否工作有用。
运行以下命令来测试本地部署:
$ npx hardhat ignition deploy ./ignition/modules/Token.js
Compiled 1 Solidity file successfully (evm target: paris).
You are running Hardhat Ignition against an in-process instance of Hardhat Network.
This will execute the deployment, but the results will be lost.
You can use --network <network-name> to deploy to a different network.
Hardhat Ignition 🚀
Deploying [ TokenModule ]
Batch #1
Executed TokenModule#Token
[ TokenModule ] successfully deployed 🚀
Deployed Addresses
TokenModule#Token - 0x5FbDB2315678afecb367f032d93F642f64180aa3
要部署到如 Sepolia 或主网这样的远程网络,你需要在
hardhat.config.js
文件中添加网络条目。我们使用 Sepolia
作为示例。
require("@nomicfoundation/hardhat-toolbox");
const { vars } = require("hardhat/config");
const INFURA_API_KEY = vars.get("INFURA_API_KEY");
const SEPOLIA_PRIVATE_KEY = vars.get("SEPOLIA_PRIVATE_KEY");
module.exports = {
solidity: "0.8.27",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,
accounts: [SEPOLIA_PRIVATE_KEY],
},
},
};
要部署到 Sepolia 测试网络,你需要一些 Sepolia 的测试以太币(测试网络的免费 ETH)。你可以通过以下水龙头获得:
确保将你的钱包切换到 Sepolia 网络,然后你就可以进行交易了。
使用以下命令在 Sepolia 网络上部署:
npx hardhat ignition deploy ./ignition/modules/Token.js --network sepolia
如果一切正常,你将看到已部署合约的地址。
更多关于 Hardhat Ignition 的信息,以及如何通过 Etherscan 验证部署,请参考 Hardhat Ignition 文档.
如果你想快速开始开发你的 dApp,或者想看看完整的项目包含哪些内容并带有前端界面,你可以使用我们的项目模板仓库。
git clone https://github.com/NomicFoundation/hardhat-boilerplate.git
cd hardhat-boilerplate
npm install
npx hardhat node
npx hardhat run scripts/deploy.js --network localhost
下一步,运行前端
cd frontend
npm install
npm start
打开 http://localhost:3000/看看你的 Dapp
在项目的根目录中,你会找到我们在教程中构建的 Hardhat 项目,其中包含了 Token 合约。为了帮助你回顾,合约实现了以下功能:
在 frontend
文件夹中,你会找到一个简单的应用程序,允许用户执行以下两项操作:
这是一个独立的 npm 项目,使用 create-react-app 创建,因此它依赖 webpack 和 babel。
src/
包含所有代码src/components/
包含 React 组件Dapp.js
是唯一包含业务逻辑的文件,如果你想使用该项目模板,这就是你要替换代码的地方。src/contracts/
包含合约的 ABI
和地址信息,这些是通过部署脚本自动生成的。首先克隆仓库,然后准备部署合约:
cd hardhat-boilerplate
npm install
npx hardhat node
在这里,我们首先安装 npm 项目的依赖项,并通过运行
npx hardhat node
启动一个本地的 Hardhat Network
实例。然后,在另一个终端窗口中,运行:
npx hardhat run scripts/deploy.js --network localhost
这将把合约部署到 Hardhat Network。完成部署后,启动 React Web 应用程序:
cd frontend
npm install
npm run start
然后在浏览器中打开
http://127.0.0.1:3000/
,你应该会看到如下界面:
点击按钮连接你的钱包。
提示
如果你使用的是 MetaMask,请确保已配置并选择了 Localhost 8545 网络。
连接钱包后,你应该会看到如下界面:
此时,前端代码会检测到当前钱包的余额为 0,因此你无法试用转账功能。通过运行以下命令:
npx hardhat --network localhost faucet <你的地址>
你将运行一个我们包含的自定义 Hardhat 任务,该任务使用部署账户的余额向你的地址发送 100 MHT 和 1 ETH。这将允许你将代币发送到其他地址。交易完成后,前端显示的余额将更新。
你可以在 tasks/faucet.js
文件中查看该任务的代码,该文件通过 hardhat.config.js
引入。
例如,运行以下命令:
$ npx hardhat --network localhost faucet 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90
输出如下:
Transferred 1 ETH and 100 tokens to 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90
你在运行 npx hardhat node
的终端中也应看到类似输出:
eth_sendTransaction
Contract call: Token#transfer
Transaction: 0x460526d98b86f7886cd0f218d6618c96d27de7c745462ff8141973253e89b7d4
From: 0xc783df8a850f42e7f7e57013759c285caa701eb6
To: 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f
Value: 0 ETH
Gas used: 37098 of 185490
Block #8: 0x6b6cd29029b31f30158bfbd12faf2c4ac4263068fd12b6130f5655e70d1bc257
console.log:
Transferring from 0xc783df8a850f42e7f7e57013759c285caa701eb6 to 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90 100 tokens
这是合约中的 transfer()
函数的 console.log
输出,以下是运行 faucet 任务后网页应用的外观:
你可以尝试使用它,并阅读代码。代码中有许多注释,解释了发生了什么,并清楚地指出哪些是以太坊的模板代码,哪些是实际的 dApp 逻辑。这使得该项目模板易于复用到你的项目中。
恭喜你完成了本教程!
以下是一些你在未来开发旅程中可能会用到的有用链接:
进一步使用 Hardhat 与你的智能合约进行互动的操作: - 与智能合约交互 - 将你的智能合约提交到 Etherscan
祝你在区块链开发之路上一帆风顺,Happy hacking! 😊