网络知识 娱乐 Hardhat快速上手

Hardhat快速上手

Hardhat快速上手

介绍

Hardhat是一个方便在以太坊上进行构建的任务运行器。使用它可以帮助开发人员管理和自动化构建智能合约和dApp的过程中固有的重复任务,以及轻松地围绕此工作流程引入更多功能。

安装

  • 安装nodejs
  • 安装hardhat
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 的大部分功能来自插件,作为开发人员,你可以自由选择你要使用的插件。

Task(任务)

每次你从CLI运行Hardhat时,你都在运行任务。 例如 npx hardhat compile正在运行compile任务。 要查看项目中当前可用的任务,运行npx hardhat。 通过运行npx hardhat help [task],可以探索任何任务。

Plugins(插件)

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 compilecompile任务是内置任务之一。

$ 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 参数来运行它,则代码将再次部署在**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。

参考

[译] Hardhat 入门教程 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)