{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\u003e 本文主要对hardhat框架实操的介绍，通过一个简单的智能合约案例，用handhat把开发、测试、部署全流程过一遍。\r\n# 前期准备\r\n* **构建工具**：hardhat\r\n* **前端技术栈**：React+Ethersjs+Web3UI Kit\r\n* **钱包**：MetaMask\r\n* **合约层**：Solidity \r\n* **ehterscan区块链浏览器**\u003cbr\u003e\r\n# 开始\r\n### 项目构建\r\n```\r\n# 合约部分\r\n# 创建空文件夹\r\n mkdir Web3\r\n# 进入工程目录中\r\ncd Web3\r\n# 项目初始化\r\nnpm init\r\n# 安装hardhat\r\nnpm install --save-dev hardhat\r\n# hardhat框架初始化选择项目模板\r\nnpx hardhat init\r\n# 下载项目插件\r\nnpm install --save-dev @nomicfoundation/hardhat-toolbox\r\n# 运行校验\r\n# 在工程目录下创建文件前端工程目录\r\n# \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\r\n# 前端部分\r\n# 创建一个dapp项目用于编写前端程序\r\n create-react-app dapp\r\n# 进入dapp文件夹\r\ncd dapp\r\n# 安装相应的包\r\n# ethers与合约交互\r\n# @web3uikit/core @web3uikit/web3 @web3uikit/icons 钱包相关包\r\nnpm install  xxx\r\n```\r\n### 项目目录结构介绍\r\n```\r\nweb3\r\n   |_contracts//智能合约目录\r\n   |_test//合约测试目录\r\n   |_scripts//合约部署目录\r\n   |_artifacts//编译合约自动生成\r\n   |___build-info\r\n   |___contracts//前端调用合约生成的json文件\r\n   |_ignition//\r\n   |_cache//\r\n   |_deploy//部署合约文件目录\r\n   |_tasts//自定任务简化流程\r\n   |_front_end//前端项目工程目录\r\n   |_hardhat.config.js//hardhat配置文件\r\n   |_helper-hardhat-config.js//hardhat集中常用变量汇总文件\r\n   |_package.json//工程目录\r\n```\r\n#### Hardhat常用指令\r\n```\r\n# 获取所有指令\r\nnpx hardhat\r\n# 获取区块网络节点\r\nnpx hardhat node\r\n# 编译合约\r\nnpx hardhat compile\r\n# 清除缓存并删除所有工件\r\nnpx hardhat clean\r\n# 测试合约\r\nnpx hardhat test\r\n# 部署合约\r\nnpx hardhat run scripts/xx.js \r\n# 控制台\r\nnpx hardhat console\r\n# 查看所有指令\r\nnpx hardhat \r\n```\r\n# 合约开发\r\n```\r\n// SPDX-License-Identifier: MIT\r\n// Compatible with OpenZeppelin Contracts ^5.0.0\r\npragma solidity ^0.8.22;\r\nimport {ERC20} from  \"@openzeppelin/contracts/token/ERC20/ERC20.sol\"\r\nimport {ERC20Burnable} from  \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol\"; \r\nimport {ERC20Permit} from  \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol\"; \r\nimport {Ownable} from  \"@openzeppelin/contracts/access/Ownable.sol\";\r\ncontract MyToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {\r\n    constructor(address initialOwner)\r\n        ERC20(\"MyToken\", \"MTK\")\r\n        Ownable(initialOwner)\r\n        ERC20Permit(\"MyToken\")\r\n    {\r\n        _mint(msg.sender, 1000 * 10 ** decimals());\r\n    }\r\n  //铸造方法\r\n    function mint(address to, uint256 amount) public onlyOwner {\r\n        _mint(to, amount);\r\n    }\r\n}\r\n/**\r\n* 合约说明：\r\n* 使用openzeppelin库快速构建一个具备铸造和销毁的ERC20标准的代币合约\r\n* 代币名称 MyToken,符号 MTK,总量：1000MTK\r\n* 方法：铸造，销毁，转账，授权...\r\n*/\r\n```\r\n# 合约编译\r\n```\r\n# 编译合约指令\r\nnpx hardhat compile\r\n```\r\n# 合约测试\r\n```\r\n# 合约测试指令\r\nnpx hardhat test\r\n```\r\n# 合约测试样例\r\n说明：hardhat框架集成了chai测试的工具\r\n```\r\nconst { ethers,getNamedAccounts,deployments} = require(\"hardhat\");\r\nconst {assert,expect } = require(\"chai\");\r\nlet firstAccount;\r\nlet nft;\r\n// 部署合约\r\nbeforeEach(async function () {\r\n  firstAccount = (await getNamedAccounts()).firstAccount;\r\n  await deployments.fixture([\"all\"]);\r\n  nft=await ethers.getContract(\"MyNFT\",firstAccount);\r\n});\r\n// 测试单元 参数说明：测试单元命名\r\ndescribe(\"xxx\", async () =\u003e {\r\n  it(\"mint nft\", async function () {\r\n    await nft.safeMint(firstAccount);\r\n    let owner=await nft.ownerOf(0);\r\n    expect(owner).to.equal(firstAccount);\r\n  });\r\n//.........\r\n});\r\n\r\n```\r\n# 合约部署指令\r\n```\r\n# 部署合约\r\n# 说明：部署一个在scripts目录下的deploy.js 网络节点\r\n# network-name可以在hardhat.json中配置网络：localhost，\r\nnpx hardhat run scripts/xxx.js --network \u003cnetwork-name\u003e\r\n# 例如\r\nnpx hardhat run scripts/deploy.js \r\n or \r\nnpx hardhat run scripts/deploy.js --network localhost//指定网络\r\n```\r\n# 合约部署样例1\r\n```\r\n# 直接在scripts目录下编写部署脚本\r\nconst { ethers } = require(\"hardhat\");\r\n\r\nasync function main() {\r\n    const [deployer] = await ethers.getSigners();//获取网络节点账号\r\n    // console.log(deployer)\r\n    console.log(\"Deploying contracts with the account:\", deployer.address);\r\n    const MyToken = await ethers.getContractFactory(\"MyToken\");//获取合约\r\n    console.log(MyToken)//获取合约实例\r\n    const myToken = await MyToken.deploy();//部署合约\r\n    console.log(\"Token address:\", myToken.target);//打印合约地址\r\n\r\n}\r\nmain().then(()=\u003e{\r\n    process.exit(0)\r\n}).catch((err)=\u003e{\r\nconsole.error(err)\r\nprocess.exit(1)\r\n});\r\n# 使用指令\r\nnpx hardhat run scripts/xxx.js --network 指定网络节点\r\n```\r\n# 合约部署样例2\r\n```\r\n# 在deploy目录下部署脚本（推荐此操作）\r\n# 使用hardhat部署插件hardhat-deploy\r\n#1 在配置文件hardhat.confing.js中引入插件\r\nrequire(\"hardhat-deploy\");\r\nrequire(\"@nomicfoundation/hardhat-ethers\");\r\nrequire(\"hardhat-deploy-ethers\");\r\n# 在deploy目录下的部署文件：\r\n// module.exports = async (hre) =\u003e {\r\n//     const getNamedAccounts = hre.getNamedAccounts\r\n//     const deployments = hre.deployments\r\n//  }\r\nconst { network } =require(\"hardhat\");\r\n\r\n module.exports = async ({getNamedAccounts,deployments}) =\u003e {\r\n    const firstAccount = (await getNamedAccounts()).firstAccount;//获取节点1\r\n    const secondAccount = (await getNamedAccounts()).secondAccount;//获取节点2\r\n    const {deploy}= deployments//获取部署实例\r\n//部署合约\r\n    const FundMe= await deploy(\"FundMe\",{\r\n        from: firstAccount,//节点\r\n        args: [xx,xxx],//要传的参数xx，xxx\r\n        log: true,//是否打印log\r\n        //waitConfirmations: confirmations,//等待的区块数\r\n    })\r\n  //验证合约是否成功的部署的链上\r\n        await hre.run(\"verify:verify\", {\r\n            address: FundMe.address,\r\n            constructorArguments: [xx,xxx],//合约要传的参数xx，xxx\r\n            });\r\n }\r\n module.exports.tags = [\"all\",\"fundme\"];//在执行部署指令时可选参数\r\n# 例如\r\nnpx hardhat deploy --tag fundme or all\r\n# 等同于\r\nnpx hardhat run scripts/funfme.js\r\n```\r\n# 自定义task样例\r\n```\r\n# 说明\r\n# 文件目录\r\ntasks\r\n  |_deploy_nft.js\r\n  |_index.js\r\n  |_ deploy_tonken.js  \r\n# deploy_nft.js代码如下\r\nconst {task} = require(\"hardhat/config\");//hardhat集成的插件\r\n//参数说明，deploy-nft是指令名\r\ntask(\"deploy-fundme\", \"deploy-fundme\").setAction(async (taskArgs, hre) =\u003e {\r\n  const FundMeFactory = await ethers.getContractFactory(\"MyNFT\");//获取合约\r\n  const fundMe = await FundMeFactory.deploy(300);//部署需要传递的参数\r\n  await fundMe.waitForDeployment();//几个区块\r\n  console.log(`address : ${fundMe.target}`)\r\n  if(hre.network.config.chainId == 11155111 \u0026\u0026 process.env.ETHERSCAN_API_KEY){\r\n      console.log(\"Waiting for 5 confirmations\")\r\n      await fundMe.deploymentTransaction().wait(5) //等待5个区块交易完成\r\n      await verifyFundMe(fundMe.target, [300])\r\n  }else{\r\n      console.log(\"verification skipped..\")\r\n  }\r\n\r\n});\r\nasync function verifyFundMe(factoryAddr,args){\r\n  await hre.run(\"verify:verify\", {\r\n      address: factoryAddr,\r\n      constructorArguments: [50],\r\n      });\r\n}\r\nexports.default = {};\r\n# deploy_token.js代码同上简单修改代码即可\r\n# index.js如下\r\nexports.deployfundme=require(\"./deploy_nft.js\")\r\nexports.interactfundme=require(\"./deploy_token.js\")\r\n把自定task引入hardhat.config.js中\r\nrequire(./tasks);\r\n# 可以通过npx hardhat 查看是否生成\r\n# 导入成功后\r\n可以在控制台面板中的AVAILABLE TASKS查看到\r\n# deploy-token\r\nnpx hardhat deploy-fundme --network xxx  //如果有参数传递就后面跟参数 键名 键值\r\n# deploy-nft \r\n同上修改一下关键参数即可\r\n```\r\n# hardhat.config.js文件配置\r\n```\r\nrequire(\"@nomicfoundation/hardhat-toolbox\");\r\nrequire(\"@nomicfoundation/hardhat-verify\");//验证合约\r\n// require(\"@chainlink/env-enc\").config();//可以采用此包对.env常量进行加密处理\r\nrequire(\"dotenv\").config();//读取.env的内容\r\nrequire(\"./tasks\");//自定义task，作用主要快加高效的执行自定指令，便于高效的开发。\r\n//引入部署插件\r\nrequire(\"hardhat-deploy\");\r\nrequire(\"@nomicfoundation/hardhat-ethers\");\r\nrequire(\"hardhat-deploy-ethers\");\r\n//读取env的常量\r\nprocess.env.PRIVATE_KEY = process.env.PRIVATE_KEY;\r\nprocess.env.PRIVATE_KEY_1 = process.env.PRIVATE_KEY_1;\r\nprocess.env.ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;\r\nprocess.env.ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;\r\nmodule.exports = {\r\n  solidity: \"0.8.27\",\r\n  defaultNetwork: \"hardhat\",\r\n  mocha: {\r\n    timeout: 300000//设置mocha的超时时间为300秒，解决集成测试时间不够问题\r\n  },\r\n  networks: {\r\n    // localhost: {\r\n    //   url:'http://127.0.0.1:8545/',\r\n    //   chainId: 31337,\r\n    // },\r\n    sepolia: {\r\n      url: `https://sepolia.infura.io/v3/${process.env.ALCHEMY_API_KEY}`,\r\n      accounts: [process.env.PRIVATE_KEY,process.env.PRIVATE_KEY_1],\r\n      chainId: 11155111\r\n    },\r\n  },//网络节点设置\r\n  etherscan: {\r\n    apiKey: {\r\n      sepolia: process.env.ETHERSCAN_API_KEY\r\n    }\r\n  },//设置 Etherscan API 密钥的，用于与 Etherscan 服务进行交互\r\n  sourcify: {\r\n    enabled: true\r\n  },//启动 Hardhat 插件 @nomicfoundation/hardhat-verify，用户验证部署在以太坊区块链上的智能合约的源代码\r\n  namedAccounts: {\r\n    firstAccount: {\r\n      default: 0\r\n    },\r\n    secondAccount: {\r\n      default: 1\r\n    }\r\n  },//通过名字获取区块节点\r\n  gasReporter: {\r\n    enabled: false,//控制gas报告的显示\r\n  }\r\n};\r\n\r\n```\r\n# 前端调用合约\r\n```\r\n# 前端主要通过Ethers.js或者Web3.js和合约进行交互。\r\n#关于Ehers.js库的使用。\r\n* Provider：提供者\r\n* Signer：签名者\r\n* Contract：用于合约的获取\r\n* 其他（单位转换，）\r\n# 前端获取合约\r\nimport myToken  from \"../json/MyToken.json\";//不是之后生成的json文件\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.JsonRpcProvider();//获取provider \r\nlet TokenAddress=\"0x......\"//合约地址\r\nlet signer=await provider.signer();//获取签名\r\n# 获取合约 参数：合约地址 合约abi，签名如果是signer可读写，provider可读\r\nconst contractWETH = new ethers.Contract(TokenAddress, myToken.abi, signer);\r\n# 获取合约后进行交互操作\r\n```\r\n# 插件篇\r\n```\r\n# 推荐工具\r\n# hardhat-deploy合约部署相关的插件\r\n# hardhat-gas-reporter部署合约后会生成一个gas费的报告文件\r\n```\r\n# 总结 \r\n以上就是使用hardhat构建工具，把编写的智能合约从开发，测试，部署上链的整个流程。\r\n* 使用内嵌测试包chai，快速验证合约的可行性；\r\n* 使用到了hardhat-deploy插件，实现快速部署合约；\r\n* 使用@nomicfoundation/hardhat-verify，验证合约是否成功上链；\r\n* 以及合约部署gas费消耗分析报告hardhat-gas-reporter。便于优化合约；\r\n* 通过自定task，提高开发效率；\r\n* 开发中还会用到预言机chainlink相关的方法","title":"智能合约开发、测试、部署全流程（实操篇）"},"history":null,"timestamp":1740387977,"version":1}