一.安装node.js
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bashnvm install 18nvm use 18nvm alias default 18npm install npm --global # Upgrade npm to the latest version
二. 安装hardhat
2.1 创建hardhat安装目录
mkdir hardhatcd hardhat
2.2 安装hardhat
npm install --save-dev hardhatls# 安装后的目录# node_modulespackage-lock.json package.json
2.3 在安装hardhat的目录下运行hardhat
npx hardhat
选择创建hardhat配置文件
2.4 hardhat架构
Hardhat是围绕**task(任务)和plugins(插件)**的概念设计的。 **Hardhat **的大部分功能来自插件,作为开发人员,你可以自由选择你要使用的插件。
2.5 Task(任务)
每次你从CLI运行Hardhat时,你都在运行任务。 例如 npx hardhat compile正在运行compile任务。 要查看项目中当前可用的任务,运行npx hardhat。 通过运行npx hardhat help [task],可以探索任何任务。
2.6 Plugins(插件)
Hardhat 不限制选择哪种工具,但是它确实内置了一些插件,所有这些也都可以覆盖。 大多数时候,使用给定工具的方法是将其集成到Hardhat中作为插件。
在本教程中,我们将使用hardhat-toolbox插件。 通过他们与以太坊进行交互并测试合约。 稍后将解释它们的用法。 要安装它们,请在项目目录中运行:
npm install --save-dev @nomicfoundation/hardhat-toolbox
在hardhar.config.js文件中添加下面的代码
require("@nomicfoundation/hardhat-toolbox");/** @type import('hardhat/config').HardhatUserConfig */module.exports = {solidity: "0.8.18",};
三. 编写、编译、测试、调试智能合约
3.1 写一个简单的智能合约
在hardhat目录下创建一个新的目录
mkdir contractscd contracts
添加Token.sol智能合约
//SPDX-License-Identifier: UNLICENSED// Solidity files have to start with this pragma.// It will be used by the Solidity compiler to validate its version.pragma solidity ^0.8.9;// This is the main building block for smart contracts.contract Token {// Some string type variables to identify the token.string public name = "My Hardhat Token";string public symbol = "MHT";// 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's balance.mapping(address => uint256) balances;// The Transfer event helps off-chain applications understand// what happens within your contract.event Transfer(address indexed _from, address indexed _to, uint256 _value);/** * Contract initialization. */constructor() {// The totalSupply is assigned to the transaction sender, which is the// account that is deploying the contract.balances[msg.sender] = totalSupply;owner = msg.sender;}/** * 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;// Notify off-chain applications of the transfer.emit Transfer(msg.sender, 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];}}
3.2 编译
npx hardhat compile
3.3 测试
创建test文件夹,创建测试文件Token.js
mkdir testcd test
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 Token = await ethers.getContractFactory("Token");const hardhatToken = await Token.deploy();const ownerBalance = await hardhatToken.balanceOf(owner.address);expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);});});
运行test命令
npx hardhat test
3.4 调试智能合约
Hardhat内置了Hardhat Network ,这是一个专为开发而设计的以太坊网络。 它允许你部署合约,运行测试和调试代码。 这是Hardhat所连接的默认网络,因此你无需进行任何设置即可工作。 你只需运行测试就好。
Solidity 中使用 console.log
在Hardhat Network上运行合约和测试时,你可以在Solidity代码中调用console.log()打印日志信息和合约变量。 你必须先从合约代码中导入**Hardhat **的console.log再使用它。
修改只能合约
pragma solidity ^0.8.9;import "hardhat/console.sol";contract Token {//...}
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);}
修改测试文件
// This is an example test file. Hardhat will run every *.js file in `test/`,// so feel free to add new ones.// Hardhat tests are normally written with Mocha and Chai.// We import Chai to use its asserting functions here.const { expect } = require("chai");// We use `loadFixture` to share common setups (or fixtures) between tests.// Using this simplifies your tests and makes them run faster, by taking// advantage of Hardhat Network's snapshot functionality.const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");// `describe` is a Mocha function that allows you to organize your tests.// 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 () {// 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 deployTokenFixture() {// Get the ContractFactory and Signers here.const Token = await ethers.getContractFactory("Token");const [owner, addr1, addr2] = await ethers.getSigners();// To deploy our contract, we just have to call Token.deploy() and await// its deployed() method, which happens once its transaction has been// mined.const hardhatToken = await Token.deploy();await hardhatToken.deployed();// Fixtures can return anything you consider useful for your testsreturn { Token, hardhatToken, owner, addr1, addr2 };}// You can nest describe calls to create subsections.describe("Deployment", function () {// `it` is another Mocha function. This is the one you use to define each// of 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 () {// We use loadFixture to setup our environment, and then assert that// things went wellconst { hardhatToken, owner } = await loadFixture(deployTokenFixture);// `expect` receives a value and wraps it in an assertion object. These// objects have a lot of utility methods to assert values.// This test expects the owner variable stored in the contract to be// equal to our Signer's owner.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);// Transfer 50 tokens from owner to addr1await expect(hardhatToken.transfer(addr1.address, 50)).to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);// Transfer 50 tokens from addr1 to addr2// We use .connect(signer) to send a transaction from another accountawait 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);// Transfer 50 tokens from owner to addr1await expect(hardhatToken.transfer(addr1.address, 50)).to.emit(hardhatToken, "Transfer").withArgs(owner.address, addr1.address, 50);// Transfer 50 tokens from addr1 to addr2// We use .connect(signer) to send a transaction from another accountawait 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);// Try to send 1 token from addr1 (0 tokens) to owner.// `require` will evaluate false and revert the transaction.await expect(hardhatToken.connect(addr1).transfer(owner.address, 1)).to.be.revertedWith("Not enough tokens");// Owner balance shouldn't have changed.expect(await hardhatToken.balanceOf(owner.address)).to.equal(initialOwnerBalance);});});});
运行测试
npx hardhat test
看到输出了log
四. 部署合约到网络
4.1 创建scripts文件夹,添加deploy.js文件
async function main() {const [deployer] = await ethers.getSigners();console.log("Deploying contracts with the account:", deployer.address);console.log("Account balance:", (await deployer.getBalance()).toString());const Token = await ethers.getContractFactory("Token");const token = await Token.deploy();console.log("Token address:", token.address);}main().then(() => process.exit(0)).catch((error) => {console.error(error);process.exit(1);});
4.2 发布到hardhat自己的网络实例
npx hardhat run scripts/deploy.js
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Account balance: 10000000000000000000000
Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
4.3 发布到Ganache网络
修改配置文件hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");/** @type import('hardhat/config').HardhatUserConfig */module.exports = {solidity: "0.8.18",networks: {deployment: {url: `http://127.0.0.1:7545`,accounts: ["bc549ba74b85bf9cc89309fc094cd868ff39215570b64271e19be9c1977df279"]}}};
npx hardhat run scripts/deploy.js --network deployment
Deploying contracts with the account: 0xEb110D13835ff1e9B65320682601634D041dD505
Account balance: 98996267410475486878
Token address: 0x3f4f9B074bfd9B0C4920FD28b374e2B8E7B618f4
看到Ganache已经有记录
4.4 发布到sepolia测试网
修改配置文件hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");// Go to https://infura.io, sign up, create a new API key// in its dashboard, and replace "KEY" with itconst INFURA_API_KEY = "KEY";// Replace this private key with your Sepolia account private key// To export your private key from Metamask, open Metamask and// go to Account Details > Export Private Key// Beware: NEVER put real Ether into testing accountsconst SEPOLIA_PRIVATE_KEY = "YOUR SEPOLIA PRIVATE KEY";module.exports = {solidity: "0.8.9",networks: {sepolia: {url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,accounts: [SEPOLIA_PRIVATE_KEY]}}};
npx hardhat run scripts/deploy.js --network sepolia
这个我没有试,有兴趣的可以试一下
参考文档:
hardhat官网教程
[译] Hardhat 入门教程 | 登链社区 | 区块链技术社区