智能合约开发、测试、部署全流程(实操篇)

533 阅读5分钟

前言

本文主要对hardhat框架实操的介绍,通过一个简单的智能合约案例,用handhat把开发、测试、部署全流程过一遍。

前期准备

  • 构建工具:hardhat
  • 前端技术栈:React+Ethersjs+Web3UI Kit
  • 钱包:MetaMask
  • 合约层:Solidity
  • ehterscan区块链浏览器

开始

项目构建

# 合约部分
# 创建空文件夹
 mkdir Web3
# 进入工程目录中
cd Web3
# 项目初始化
npm init
# 安装hardhat
npm install --save-dev hardhat
# hardhat框架初始化选择项目模板
npx hardhat init
# 下载项目插件
npm install --save-dev @nomicfoundation/hardhat-toolbox
# 运行校验
# 在工程目录下创建文件前端工程目录
# <<<<<<<<<<<<<<<<<<<<<<<<<
# 前端部分
# 创建一个dapp项目用于编写前端程序
 create-react-app dapp
# 进入dapp文件夹
cd dapp
# 安装相应的包
# ethers与合约交互
# @web3uikit/core @web3uikit/web3 @web3uikit/icons 钱包相关包
npm install  xxx

项目目录结构介绍

web3
   |_contracts//智能合约目录
   |_test//合约测试目录
   |_scripts//合约部署目录
   |_artifacts//编译合约自动生成
   |___build-info
   |___contracts//前端调用合约生成的json文件
   |_ignition//
   |_cache//
   |_deploy//部署合约文件目录
   |_tasts//自定任务简化流程
   |_front_end//前端项目工程目录
   |_hardhat.config.js//hardhat配置文件
   |_helper-hardhat-config.js//hardhat集中常用变量汇总文件
   |_package.json//工程目录
Hardhat常用指令
# 获取所有指令
npx hardhat
# 获取区块网络节点
npx hardhat node
# 编译合约
npx hardhat compile
# 清除缓存并删除所有工件
npx hardhat clean
# 测试合约
npx hardhat test
# 部署合约
npx hardhat run scripts/xx.js 
# 控制台
npx hardhat console
# 查看所有指令
npx hardhat 

合约开发

// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import {ERC20} from  "@openzeppelin/contracts/token/ERC20/ERC20.sol"
import {ERC20Burnable} from  "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 
import {ERC20Permit} from  "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; 
import {Ownable} from  "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
    constructor(address initialOwner)
        ERC20("MyToken", "MTK")
        Ownable(initialOwner)
        ERC20Permit("MyToken")
    {
        _mint(msg.sender, 1000 * 10 ** decimals());
    }
  //铸造方法
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}
/**
* 合约说明:
* 使用openzeppelin库快速构建一个具备铸造和销毁的ERC20标准的代币合约
* 代币名称 MyToken,符号 MTK,总量:1000MTK
* 方法:铸造,销毁,转账,授权...
*/

合约编译

# 编译合约指令
npx hardhat compile

合约测试

# 合约测试指令
npx hardhat test

合约测试样例

说明:hardhat框架集成了chai测试的工具

const { ethers,getNamedAccounts,deployments} = require("hardhat");
const {assert,expect } = require("chai");
let firstAccount;
let nft;
// 部署合约
beforeEach(async function () {
  firstAccount = (await getNamedAccounts()).firstAccount;
  await deployments.fixture(["all"]);
  nft=await ethers.getContract("MyNFT",firstAccount);
});
// 测试单元 参数说明:测试单元命名
describe("xxx", async () => {
  it("mint nft", async function () {
    await nft.safeMint(firstAccount);
    let owner=await nft.ownerOf(0);
    expect(owner).to.equal(firstAccount);
  });
//.........
});

合约部署指令

# 部署合约
# 说明:部署一个在scripts目录下的deploy.js 网络节点
# network-name可以在hardhat.json中配置网络:localhost,
npx hardhat run scripts/xxx.js --network <network-name>
# 例如
npx hardhat run scripts/deploy.js 
 or 
npx hardhat run scripts/deploy.js --network localhost//指定网络

合约部署样例1

# 直接在scripts目录下编写部署脚本
const { ethers } = require("hardhat");

async function main() {
    const [deployer] = await ethers.getSigners();//获取网络节点账号
    // console.log(deployer)
    console.log("Deploying contracts with the account:", deployer.address);
    const MyToken = await ethers.getContractFactory("MyToken");//获取合约
    console.log(MyToken)//获取合约实例
    const myToken = await MyToken.deploy();//部署合约
    console.log("Token address:", myToken.target);//打印合约地址

}
main().then(()=>{
    process.exit(0)
}).catch((err)=>{
console.error(err)
process.exit(1)
});
# 使用指令
npx hardhat run scripts/xxx.js --network 指定网络节点

合约部署样例2

# 在deploy目录下部署脚本(推荐此操作)
# 使用hardhat部署插件hardhat-deploy
#1 在配置文件hardhat.confing.js中引入插件
require("hardhat-deploy");
require("@nomicfoundation/hardhat-ethers");
require("hardhat-deploy-ethers");
# 在deploy目录下的部署文件:
// module.exports = async (hre) => {
//     const getNamedAccounts = hre.getNamedAccounts
//     const deployments = hre.deployments
//  }
const { network } =require("hardhat");

 module.exports = async ({getNamedAccounts,deployments}) => {
    const firstAccount = (await getNamedAccounts()).firstAccount;//获取节点1
    const secondAccount = (await getNamedAccounts()).secondAccount;//获取节点2
    const {deploy}= deployments//获取部署实例
//部署合约
    const FundMe= await deploy("FundMe",{
        from: firstAccount,//节点
        args: [xx,xxx],//要传的参数xx,xxx
        log: true,//是否打印log
        //waitConfirmations: confirmations,//等待的区块数
    })
  //验证合约是否成功的部署的链上
        await hre.run("verify:verify", {
            address: FundMe.address,
            constructorArguments: [xx,xxx],//合约要传的参数xx,xxx
            });
 }
 module.exports.tags = ["all","fundme"];//在执行部署指令时可选参数
# 例如
npx hardhat deploy --tag fundme or all
# 等同于
npx hardhat run scripts/funfme.js

自定义task样例

# 说明
# 文件目录
tasks
  |_deploy_nft.js
  |_index.js
  |_ deploy_tonken.js  
# deploy_nft.js代码如下
const {task} = require("hardhat/config");//hardhat集成的插件
//参数说明,deploy-nft是指令名
task("deploy-fundme", "deploy-fundme").setAction(async (taskArgs, hre) => {
  const FundMeFactory = await ethers.getContractFactory("MyNFT");//获取合约
  const fundMe = await FundMeFactory.deploy(300);//部署需要传递的参数
  await fundMe.waitForDeployment();//几个区块
  console.log(`address : ${fundMe.target}`)
  if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY){
      console.log("Waiting for 5 confirmations")
      await fundMe.deploymentTransaction().wait(5) //等待5个区块交易完成
      await verifyFundMe(fundMe.target, [300])
  }else{
      console.log("verification skipped..")
  }

});
async function verifyFundMe(factoryAddr,args){
  await hre.run("verify:verify", {
      address: factoryAddr,
      constructorArguments: [50],
      });
}
exports.default = {};
# deploy_token.js代码同上简单修改代码即可
# index.js如下
exports.deployfundme=require("./deploy_nft.js")
exports.interactfundme=require("./deploy_token.js")
把自定task引入hardhat.config.jsrequire(./tasks);
# 可以通过npx hardhat 查看是否生成
# 导入成功后
可以在控制台面板中的AVAILABLE TASKS查看到
# deploy-token
npx hardhat deploy-fundme --network xxx  //如果有参数传递就后面跟参数 键名 键值
# deploy-nft 
同上修改一下关键参数即可

hardhat.config.js文件配置

require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-verify");//验证合约
// require("@chainlink/env-enc").config();//可以采用此包对.env常量进行加密处理
require("dotenv").config();//读取.env的内容
require("./tasks");//自定义task,作用主要快加高效的执行自定指令,便于高效的开发。
//引入部署插件
require("hardhat-deploy");
require("@nomicfoundation/hardhat-ethers");
require("hardhat-deploy-ethers");
//读取env的常量
process.env.PRIVATE_KEY = process.env.PRIVATE_KEY;
process.env.PRIVATE_KEY_1 = process.env.PRIVATE_KEY_1;
process.env.ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;
process.env.ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;
module.exports = {
  solidity: "0.8.27",
  defaultNetwork: "hardhat",
  mocha: {
    timeout: 300000//设置mocha的超时时间为300秒,解决集成测试时间不够问题
  },
  networks: {
    // localhost: {
    //   url:'http://127.0.0.1:8545/',
    //   chainId: 31337,
    // },
    sepolia: {
      url: `https://sepolia.infura.io/v3/${process.env.ALCHEMY_API_KEY}`,
      accounts: [process.env.PRIVATE_KEY,process.env.PRIVATE_KEY_1],
      chainId: 11155111
    },
  },//网络节点设置
  etherscan: {
    apiKey: {
      sepolia: process.env.ETHERSCAN_API_KEY
    }
  },//设置 Etherscan API 密钥的,用于与 Etherscan 服务进行交互
  sourcify: {
    enabled: true
  },//启动 Hardhat 插件 @nomicfoundation/hardhat-verify,用户验证部署在以太坊区块链上的智能合约的源代码
  namedAccounts: {
    firstAccount: {
      default: 0
    },
    secondAccount: {
      default: 1
    }
  },//通过名字获取区块节点
  gasReporter: {
    enabled: false,//控制gas报告的显示
  }
};

前端调用合约

# 前端主要通过Ethers.js或者Web3.js和合约进行交互。
#关于Ehers.js库的使用。
* Provider:提供者
* Signer:签名者
* Contract:用于合约的获取
* 其他(单位转换,)
# 前端获取合约
import myToken  from "../json/MyToken.json";//不是之后生成的json文件
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider();//获取provider 
let TokenAddress="0x......"//合约地址
let signer=await provider.signer();//获取签名
# 获取合约 参数:合约地址 合约abi,签名如果是signer可读写,provider可读
const contractWETH = new ethers.Contract(TokenAddress, myToken.abi, signer);
# 获取合约后进行交互操作

插件篇

# 推荐工具
# hardhat-deploy合约部署相关的插件
# hardhat-gas-reporter部署合约后会生成一个gas费的报告文件

总结

以上就是使用hardhat构建工具,把编写的智能合约从开发,测试,部署上链的整个流程。

  • 使用内嵌测试包chai,快速验证合约的可行性;
  • 使用到了hardhat-deploy插件,实现快速部署合约;
  • 使用@nomicfoundation/hardhat-verify,验证合约是否成功上链;
  • 以及合约部署gas费消耗分析报告hardhat-gas-reporter。便于优化合约;
  • 通过自定task,提高开发效率;
  • 开发中还会用到预言机chainlink相关的方法