DEV Community

fangjun
fangjun

Posted on • Updated on

A Concise Hardhat Tutorial: Part 2 - ERC20 Token

This concise hardhat tutorial has four sections and this is section 3.

  1. Introduction of Hardhat features
  2. Installation and sample project
  3. Write ERC20 token with OpenZeppelin
  4. Write ERC72 NFT token with SVG image on-chain

Hardhat is an Ethereum development tool suite to compile, unit test, debug and deploy smart contracts.


3. Write an ERC20 Token with OpenZeppelin

In the hardhat official tutorial at https://hardhat.org/tutorial/ , it write an ERC20 Token smart contract from scratch. Here we will write one using OpenZeppelin library. You can find more information about OpenZeppelin at: https://docs.openzeppelin.com/contracts/4.x/ . The sample contract is adapted from OpenZeppelin documents.

Compile-Test-Deploy circle

Step 1: Install OpenZeppelin

First, let's install OpenZeppelin contracts.

yarn add @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Step 2: Write an ERC20 Token with OpenZeppelin

You can write an ERC20 Token smart contract by inheriting OpenZepplin contracts. Create a smart contract GLDToken.sol as follows:

// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract GLDToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("Gold", "GLD") {
        _mint(msg.sender, initialSupply);
    }
}
Enter fullscreen mode Exit fullscreen mode

Compile the contract:

yarn hardhat compile
//Output: 
// Compiling 5 files with 0.8.4
// Compilation finished successfully
Enter fullscreen mode Exit fullscreen mode

Step 3: Write deploy script

Create a deploy script which is adapted from https://docs.openzeppelin.com/learn/deploying-and-interacting . You can find a detailed explanation there.

 // scripts/GLDToken_deploy.js

const hre = require("hardhat");

async function main() {

  const GLDToken = await hre.ethers.getContractFactory("GLDToken");
  console.log('Deploying GLDToken...');
  const token = await GLDToken.deploy('10000000000000000000000');

  await token.deployed();
  console.log("GLDToken deployed to:", token.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
Enter fullscreen mode Exit fullscreen mode

Try to deploy it to in-process local blockchain:

yarn hardhat run scripts/GLDToken_deploy.js
//Output: 
// GLDToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Enter fullscreen mode Exit fullscreen mode

Step 4: Write Unit Test

In the hardhat official tutorial, there is a detailed explaination of unit test at https://hardhat.org/tutorial/testing-contracts.html . We adapted the test script in it with several necessary changes.

Create file GLDToken-test.js:

// We import Chai to use its asserting functions here.
const { expect } = require("chai");

describe("GLDToken contract", function () {
  let totalSupply = '10000000000000000000000'; // 10000 * 1e18
  let Token;
  let hardhatToken;
  let owner;
  let addr1;
  let addr2;
  let addrs;

  beforeEach(async function () {
    // Get the ContractFactory and Signers here.
    Token = await ethers.getContractFactory("GLDToken");
    [owner, addr1, addr2, ...addrs] = await ethers.getSigners();

    hardhatToken = await Token.deploy(totalSupply);
  });

  // You can nest describe calls to create subsections.
  describe("Deployment", function () {

    it("Should assign the total supply of tokens to the owner", async function () {
      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 ownerBalance = await hardhatToken.balanceOf(owner.address);

        // Transfer 50 tokens from owner to addr1
      await hardhatToken.transfer(addr1.address, 50);
      const addr1Balance = await hardhatToken.balanceOf(addr1.address);
      expect(addr1Balance).to.equal(50);

      // Transfer 50 tokens from addr1 to addr2
      // We use .connect(signer) to send a transaction from another account
      await hardhatToken.connect(addr1).transfer(addr2.address, 50);
      const addr2Balance = await hardhatToken.balanceOf(addr2.address);
      expect(addr2Balance).to.equal(50);
    });

    it("Should fail if sender doesn’t have enough tokens", async function () {
      const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);

      // Try to send 1 token from addr1 (0 tokens) to owner (1000000 tokens).
      // `require` will evaluate false and revert the transaction.
      await expect(
        hardhatToken.connect(addr1).transfer(owner.address, 1)
      ).to.be.revertedWith("ERC20: transfer amount exceeds balance");

      // Owner balance shouldn't have changed.
      expect(await hardhatToken.balanceOf(owner.address)).to.equal(
        initialOwnerBalance
      );
    });

  });
});
Enter fullscreen mode Exit fullscreen mode

Run test:

yarn hardhat test
//Output
//  GLDToken contract Deployment
//      ✓ Should assign the total supply of tokens to the owner
//    Transactions
//      ✓ Should transfer tokens between accounts
//      ✓ Should fail if sender doesn’t have enough tokens
//
//  3 passing (496ms)
Enter fullscreen mode Exit fullscreen mode

As OpenZeppelin Contracts are fully covered and widely used and verified by web3 community, we can write a simple test in GLDToken-simple-test.js with only the first unit test.

Step 5: Deploy smart contract to localhost and interact with it

By compiling, testing and deploying in in-process blockchain, we have a smart contract which works correctly. We would like to deploy it to localhost blockchain and interact with it interactively from Hardhat console.

Open another terminal and run in the project directory:

yarn hardhat node
Enter fullscreen mode Exit fullscreen mode

In your working terminal, deploy the contract to localhost by running:

yarn hardhat run --network localhost scripts/GLDToken.js
//Output:
// GLDToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Enter fullscreen mode Exit fullscreen mode

Please note that you need to set localhost network in hardhat.config.js.

Run hardhat console connecting to localhost:

yarn hardhat console --network localhost
Enter fullscreen mode Exit fullscreen mode

Interact with GLDToken smart contract instance in hardhat console as follows.

Get a smart contract instance and retrieve read-only data:

fromWei = ethers.utils.formatEther;
toWei = ethers.utils.parseEther;

const address = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const token = await ethers.getContractAt("GLDToken", address);

const accounts = await hre.ethers.getSigners();
owner = accounts[0].address;
toAddress = accounts[1].address;

await token.symbol()
//'GLD'

totalSupply = await token.totalSupply();
fromWei(totalSupply)
//10000.0
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We can get contract instance in ethers(docs): new ethers.Contract( address , abi , signerOrProvider )

  • ethers.getContractAt("GLDToken", address) is helper added by hardhat-ethers, a Hardhat plugin for integration with ethers.js.

Once we get an instance of the ERC20 contract, we can call its functions (ERC20 docs by OpenZeppelin).

You can transfer token from your current address to another address by sending transfer(recipient, amount).

await token.transfer(toAddress,toWei('100'))

ownerBalance = await token.balanceOf(owner);
fromWei(ownerBalance);
//'9900.0'

toBalance = await token.balanceOf(toAddress);
fromWei(toBalance)
//'100.0'
Enter fullscreen mode Exit fullscreen mode

Similarly, we can write ERC721 token smart contract using OpenZepplin. You can try it. The Hardhat official tutorial provides a boilerplate project with a frontend app, which you can find at: https://hardhat.org/tutorial/hackathon-boilerplate-project.html . In the next section, we will write a loot-like ERC721 which's image is on-chain in SVG format encoded with Base64.


If you feel this tutorial is helpful and would like to know more, follow me Twitter: @fjun99 . DM is open.

Discussion (0)