Hardhat

【Hardhat】編譯&測試&部署智能合約 區塊鏈環境配置

【Hardhat】編譯&測試&部署智能合約 區塊鏈環境配置

Hardhat 是一個開源的工具箱,用於支持 Ethereum 智能合約開發工作流程。它提供了一組用於編譯、測試和部署智能合約的工具。 Hardhat 可以與 Solidity 和 Vyper 編譯器配合使用,並支持使用 Truffle 和 Buidler 框架的測試。它還提供了一個環境,可以在本地部署和測試智能合約,以及與網絡通信。

要使用 Hardhat 部署智能合約,首先需要安裝 hardhat 並建立專案,然後使用 hardhat compile 命令編譯智能合約,最後使用 hardhat run 部署智能合約到網絡。


文章目錄

  1. 安裝 Hardhat
  2. 創建 Hardhat 專案
  3. 創建 ERC20 合約
  4. 區塊鏈環境配置
  5. 測試合約
  6. 編譯與部署智能合約
  7. 編譯與部署 Proxy 智能合約

1.安裝 Hardhat

md HardhatDemo
cd HardhatDemo
npm init -yes
npm i hardhat

2.創建 Hardhat 專案

npx hardhat
選擇專案類型

創建完成

安裝套件
npm install --save-dev "hardhat@^2.16.1" "@nomicfoundation/hardhat-toolbox@^3.0.0"

3.創建 ERC20 合約

contracts/IERC20.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IERC20 {

    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);

    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint);
    function transfer(address recipient, uint amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint amount) external returns (bool);
    function approve(address spender, uint amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint);

    // ======================================================
    //                        OPTIONAL                       
    // ======================================================
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);

}
contracts/ERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./IERC20.sol";

contract ERC20 is IERC20 {

    //constant
    uint8 constant public decimals = 18;

    //attribute
    string public name;
    string public symbol;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    constructor(string memory _name, string memory _symbol, uint256 _totalSupply) {
        name = _name;
        symbol = _symbol;
        _mint(msg.sender, _totalSupply * 1 ether);
    }

    function transfer(address to, uint256 amount) external virtual returns (bool success) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external virtual returns (bool success) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) external virtual returns (bool success) {
        uint currentAllowance = allowance[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: insufficient allownace");
        _approve(sender, msg.sender, currentAllowance - amount);
        _transfer(sender, recipient, amount);
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer sender the zero address");
        require(recipient != address(0), "ERC20: transfer recipient the zero address");
        require(balanceOf[sender] >= amount, "ERC20: transfer amount exceeds balance");

        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        allowance[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        balanceOf[account] += amount;
        totalSupply += amount;
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");
        require(balanceOf[account] >= amount, "ERC20: burn amount exceeds balance");

        balanceOf[account] -= amount;
        totalSupply -= amount;
        emit Transfer(account, address(0), amount);
    }

}

4.區塊鏈環境配置

hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
//Proxy才需要
require('@openzeppelin/hardhat-upgrades');
const PRIVATE_KEY = "錢包私鑰";

module.exports = {
    solidity: {
        version: "0.8.19",
        settings: {
            optimizer: {
                enabled: true,
                runs: 200
            }
        }
    },
    networks: {
        bsc: {
            url: "https://rpc.ankr.com/bsc",
            accounts: [PRIVATE_KEY]
        },
        bscTestnet: {
            url: "https://data-seed-prebsc-2-s3.binance.org:8545",
            accounts: [PRIVATE_KEY]
        }
    }
};

5.測試合約

npm i chai
test/ERC20.js
const {expect} = require('chai');
const {ethers} = require('hardhat');

describe("ERC20 Test", () => {
    it("Deploy Contract", async () => {
        const [owner, addr1, addr2] = await ethers.getSigners();
        const Token = await ethers.getContractFactory("ERC20");

        const token = await Token.deploy("USDT", "USDT", 50000000);
        const ContractAddress = await token.getAddress();
        expect(ContractAddress).to.properAddress;
    });
})
npx hardhat test

6.編譯與部署智能合約

scripts/deploy.js
const hre = require("hardhat");

async function main() {
    const Contract = await hre.ethers.getContractFactory("ERC20");
    const token  = await Contract.deploy("USDT", "USDT", 50000000);
    await token.waitForDeployment();

    console.log(`deployed to: ${await token.getAddress()}`);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
npx hardhat run --network bscTestnet scripts/deploy.js

BSC Testnet USDT

7.編譯與部署 Proxy 智能合約

安裝套件
npm i @openzeppelin/hardhat-upgrades
npm i @openzeppelin/contracts-upgradeable
contracts/GameV1.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract GameV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {

    string public name;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(address newImplementation) internal onlyOwner override {

    }

    function setName(string memory _name) external {
        name = _name;
    }

}
scripts/deploy.js
const {ethers, upgrades} = require("hardhat");

async function main() {
    const GameV1 = await ethers.getContractFactory("GameV1")
    const proxy = await upgrades.deployProxy(GameV1, {initializer: 'initialize', kind: 'uups'})
    await proxy.waitForDeployment();

    const address = await proxy.getAddress()
    console.log("Proxy Contract Address", address)
    console.log("Logic Contract Address", await upgrades.erc1967.getImplementationAddress(address))
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
npx hardhat run --network bscTestnet scripts/deploy.js

Proxy Contract

GameV1 Contract

升級合約

contracts/GameV2.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract GameV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {

    string public name;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(address newImplementation) internal onlyOwner override {

    }

    function setName(string memory _name) external {
        name = string(abi.encodePacked("Hi ",_name));
    }

}
scripts/deploy.js
const {ethers, upgrades} = require("hardhat");

async function main() {
    const proxyAddress = "0xc9A840Cc26AF7e7F6eE3CC6FCF72Df58B7E08c24"
    const GameV2 = await ethers.getContractFactory("GameV2")
    const proxy = await upgrades.upgradeProxy(proxyAddress, GameV2)
    await proxy.waitForDeployment()

    const address = await proxy.getAddress()
    console.log("Proxy Contract Address", address)
    console.log("Logic Contract Address", await upgrades.erc1967.getImplementationAddress(address))
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
npx hardhat run --network bscTestnet scripts/deploy.js

BSC Testnet Proxy

BSC Testnet GameV1

BSC Testnet GameV2

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *