Hardhat是一个方便在以太坊上进行构建的任务运行器。使用它可以帮助开发人员管理和自动化构建智能合约和dApp的过程中固有的重复任务,以及轻松地围绕此工作流程引入更多功能。
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
在安装Hardhat的目录下运行:
npx hardhat
使用键盘选择 “创建一个新的hardhat.config.js(Create an empty hardhat.config.js
)”,然后回车。
在运行Hardhat时,它将从当前工作目录开始搜索最接近的hardhat.config.js
文件。 这个文件通常位于项目的根目录下,一个空的hardhat.config.js
足以使Hardhat正常工作。
Hardhat是围绕task(任务)和plugins(插件)的概念设计的。 Hardhat 的大部分功能来自插件,作为开发人员,你可以自由选择你要使用的插件。
每次你从CLI运行Hardhat时,你都在运行任务。 例如 npx hardhat compile
正在运行compile
任务。 要查看项目中当前可用的任务,运行npx hardhat
。 通过运行npx hardhat help [task]
,可以探索任何任务。
Hardhat 不限制选择哪种工具,但是它确实内置了一些插件,所有这些也都可以覆盖。 大多数时候,使用给定工具的方法是将其集成到Hardhat中作为插件。
我们将使用Ethers.js和Waffle插件。 通过他们与以太坊进行交互并测试合约。 稍后将解释它们的用法。 要安装它们,请在项目目录中运行:
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
将高亮行require("@nomiclabs/hardhat-waffle");
添加到你的hardhat.config.js
中,如下所示:
require("@nomiclabs/hardhat-waffle");
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.7.3",
};
这里引入hardhat-waffle
,因为它依赖于hardhat-ethers
,因此不需要同时添加两个。
首先创建一个名为 contracts
的新目录,然后在目录内创建一个名为Token.sol
的文件。
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
// 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 = "MBT";
// 固定发行量
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;
/**
* 合约构造函数
*
* The `constructor` is executed only once when the contract is created.
* The `public` modifier makes a function callable from outside the contract.
*/
constructor() public {
// The totalSupply is assigned to transaction sender, which is the account
// that is deploying the contract.
balances[msg.sender] = totalSupply;
owner = msg.sender;
}
/**
* 代币转账.
*
* 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;
}
/**
* 读取某账号的代币余额
*
* 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];
}
}
要编译合约,请在终端中运行 npx hardhat compile
。 compile
任务是内置任务之一。
$ npx hardhat compile
Compiling...
Compiled 1 contract successfully
合约已成功编译,可以使用了。
我们将使用Hardhat Network,这是一个内置的以太坊网络,专门为开发设计,并且是Hardhat 中的默认网络。 无需进行任何设置即可使用它。 在我们的测试中,我们将使用ethers.js与前面构建的合约进行交互,并使用 Mocha作为测试框架。
在项目根目录中创建一个名为test
的新目录,并创建一个名为Token.js
的新文件。
const { expect } = require("chai");
describe("Token contract", function() {
it("Deployment should assign the total supply of tokens to the owner", async function() {
//Signer 代表以太坊账户对象。 它用于将交易发送到合约和其他帐户。
// 在这里,我们获得了所连接节点中的帐户列表,在本例中节点为Hardhat Network,并且仅保留第一个帐户。
const [owner] = await ethers.getSigners();
//ethers.js中的ContractFactory是用于部署新智能合约的抽象,
// 因此此处的Token是用来实例代币合约的工厂。
const Token = await ethers.getContractFactory("Token");
//在ContractFactory上调用deploy()将启动部署,并返回解析为Contract的Promise。
// 该对象包含了智能合约所有函数的方法。
const hardhatToken = await Token.deploy();
//当你调用deploy()时,将发送交易,但是直到该交易打包出块后,合约才真正部署。
// 调用deployed()将返回一个Promise,因此该代码将阻塞直到部署完成。
await hardhatToken.deployed();
//部署合约后,我们可以在hardhatToken 上调用合约方法,
// 通过调用balanceOf()来获取所有者帐户的余额。
const ownerBalance = await hardhatToken.balanceOf(owner.getAddress());
//在这里,再次使用Contract实例调用Solidity代码中合约函数。
// totalSupply()返回代币的发行量,我们检查它是否等于ownerBalance。
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
在终端上运行npx hardhat test
。 你应该看到以下输出:
Compiled 3 Solidity files successfully
Token contract
√ Deployment should assign the total supply of tokens to the owner (726ms)
1 passing (731ms)
如果你需要从默认帐户以外的其他帐户(或ethers.js 中的 Signer
)发送交易来测试代码,则可以在ethers.js的Contract
中使用connect()
方法来将其连接到其他帐户,像这样:
const { expect } = require("chai");
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const hardhatToken = await Token.deploy();
await hardhatToken.deployed();
// Transfer 50 tokens from owner to addr1
await hardhatToken.transfer(await addr1.getAddress(), 50);
expect(await hardhatToken.balanceOf(await addr1.getAddress())).to.equal(50);
// Transfer 50 tokens from addr1 to addr2
await hardhatToken.connect(addr1).transfer(await addr2.getAddress(), 50);
expect(await hardhatToken.balanceOf(await addr2.getAddress())).to.equal(50);
});
});
请记住,当你运行npx hardhat test
时,如果合约在上次运行测试后发生了修改,则会对其进行重新编译。
部署到测试网与部署到主网是一样的。唯一的区别是你连接到哪个网络。
在项目根目录的目录下创建一个新的目录scripts
,并将以下内容粘贴到 deploy.js
文件中:
async function main() {
const [deployer] = await ethers.getSigners();
console.log(
"Deploying contracts with the account:",
await deployer.getAddress()
);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
await token.deployed();
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
为了在运行任何任务时指示Hardhat连接到特定的以太坊网络,可以使用--network
参数。 像这样:
npx hardhat run scripts/deploy.js --network <network-name>
在这种情况下,如果不使用--network
参数来运行它,则代码将再次部署在**Hardhat network *上,因此,当*Hardhat network 关闭后,部署实际上会丢失,但是它用来测试我们的部署代码时仍然有用:
npx hardhat run scripts/
deploy.js
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922
66
Account balance: 10000000000000000000000
Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
要部署到诸如主网或任何测试网之类的线上网络,你需要在hardhat.config.js
文件中添加一个network
条目。 在此示例中,我们将使用Ropsten,但你可以类似地添加其他网络:
usePlugin("@nomiclabs/hardhat-waffle");
// Go to https://infura.io/ and create a new project
// Replace this with your Infura project ID
const INFURA_PROJECT_ID = "YOUR INFURA PROJECT ID";
// Replace this private key with your Ropsten account private key
// To export your private key from Metamask, open Metamask and
// go to Account Details > Export Private Key
// Be aware of NEVER putting real Ether into testing accounts
const ROPSTEN_PRIVATE_KEY = "YOUR ROPSTEN PRIVATE KEY";
module.exports = {
networks: {
ropsten: {
url: `https://ropsten.infura.io/v3/${INFURA_PROJECT_ID}`,
accounts: [`0x${ROPSTEN_PRIVATE_KEY}`]
}
}
};
我们这里使用Infura,但是你将url指向其他任何以太坊节点或网关都是可以。请你从https://infura.io/网站复制 Project ID,替换INFURA_PROJECT_ID
。
要在Ropsten上进行部署,你需要将ropsten-ETH发送到将要进行部署的地址中。 你可以从水龙头获得一些用于测试网的ETH,水龙头服务免费分发测试使用的ETH。 这是Ropsten的一个水龙头,你必须在进行交易之前将Metamask的网络更改为Ropsten。