世界杯竞猜项目Dapp-第二章(hardhat部署合约)
创始人
2024-04-19 16:41:49
0

创建 hardhat 项目

# 创建 npm 空项目
npm init 
# 安装
npm install --save-dev hardhat@2.11.1
# 创建工程
npx hardhat -> 选择高级ts项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a4PCkf7E-1670485321227)(media/16702110821588/16702110902702.jpg)]

运行测试

# 编译合约
npx hardhat compile
# 单元测试
npx hardhat test

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t3pbyQvr-1670485321228)(media/16702110821588/16702130138656.jpg)]

添加合约

将 Worldcup.sol(上节编写的合约)添加到 contracts 目录,并进行编译

单元测试

创建 test/WorldCup.ts,用于编写测试文件:

import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { expect } from "chai";
import { ethers } from "hardhat";
import hre from "hardhat";
import { WorldCup } from "../typechain-types";describe("WorldCup", function () {enum Country {GERMANY,FRANCH,CHINA,BRAZIL,KOREA}// const 声明常量const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;const ONE_GEWI = 1_000_000_000;const ONE_ETHER = ethers.utils.parseEther("1");// let 声明的变量只在 let 命令所在的代码块内有效let worldcupIns: WorldCup// 管理员地址let ownerAddr:string// 其他地址let otherAccountAddr:stringlet deadline1:number// 定义一个 fixture,每次测试可重复使用相同的设置// 利用 loadFixture 运行这个设置async function deployWorldcupFixture() {// 获取第一个钱包对象,用于发起交易const [owner, otherAccount] = await ethers.getSigners();// 获取合约对象const WorldCup = await ethers.getContractFactory("WorldCup");// 下注截止时间const deadline = (await time.latest()) + TWO_WEEKS_IN_SECS;// 部署合约const worldcup = await WorldCup.deploy(deadline);return {worldcup, deadline, owner, otherAccount};}// Mocha 库:beforeEach() 在测试前会调用该钩子this.beforeEach(async () => {// loadFixture -waffle 语法// 从内存中获取合约状态快照(仅用于测试),执行每个单元测试的时候,状态都会回到最初const {worldcup, owner, otherAccount, deadline} = await loadFixture(deployWorldcupFixture);worldcupIns = worldcupownerAddr = owner.addressotherAccountAddr = otherAccount.addressdeadline1 = deadline})// async ES7 异步关键字// await 关键字仅在 async function 中有效// await 返回值:1- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值;2- 非 Promise 对象:直接返回对应的值;let preparePlay = async () => {const [A, B, C, D] = await ethers.getSigners();await worldcupIns.connect(A).play(Country.GERMANY, {value: ONE_GEWI})await worldcupIns.connect(B).play(Country.GERMANY, {value: ONE_GEWI})await worldcupIns.connect(C).play(Country.GERMANY, {value: ONE_GEWI})await worldcupIns.connect(D).play(Country.FRANCH, {value: ONE_GEWI})}/*** 编写测试逻辑*/// 部署相关测试describe("Deployment", function () {// 检查部署时 “下注截止时间”是否正确 it() 属于 Mocha 库it("Should set the right deadline", async function () {console.log('deadline:', deadline1);// chai.js 语法:expect,使用构造函数创建断言对象实例expect(await worldcupIns.deadline()).to.equal(deadline1);});// 检查部署时 管理员是否正确it("Should set the right owner", async function () {expect(await worldcupIns.admin()).to.equal(ownerAddr);});// 检查部署时 如果时间不是在当前时间之后 是否会抛出异常it("Should fail if the deadline is not in the future", async function () {const latestTime = await time.latest();const WorldCup = await ethers.getContractFactory("WorldCup");await expect(WorldCup.deploy(latestTime)).to.be.revertedWith("WorldCupLottery: invalid deadline!");});});// 玩家下注相关测试describe("Play", function () {// 测试奖金池是否正确it("Should deposit 1 gwei", async function () {// 调用合约await worldcupIns.play(Country.CHINA, {value: ONE_GEWI})// 校验let bal = await worldcupIns.getVaultBalance()console.log("bal:", bal);console.log("bal.toString():", bal.toString());expect(bal).to.equal(ONE_GEWI)})// 测试传入非法下注值it("Should faild with invalid eth", async function () {await expect(worldcupIns.play(Country.CHINA, {value: ONE_GEWI * 2})).to.revertedWith("invalid funds provided")})// 至少选择一个正确的球队it("Should have 1 player for selected country", async function () {await expect(worldcupIns.play(10, {value: ONE_GEWI})).to.revertedWithoutReason()})// 测试是否发出事件it("Should emit Event Play", async function () {await expect(worldcupIns.play(Country.BRAZIL, {value:ONE_GEWI})).to.emit(worldcupIns, "Play").withArgs(0, ownerAddr, Country.BRAZIL)})})// 测试开奖过程describe("Finalize", function () {// 测试开奖人权限it("Should failed when called by other account", async function () {let otherAccount = await ethers.getSigner(otherAccountAddr)await expect(worldcupIns.connect(otherAccount).finialize(Country.BRAZIL)).to.revertedWith("not authorized!")})// 测试奖金分配it("Should distribute with correct reward", async function () {const [A, B, C, D] = await ethers.getSigners();// 玩家下注await preparePlay()// 调用 finalizeawait worldcupIns.finialize(Country.GERMANY)let rewardForA = await worldcupIns.winnerVaults(A.address)let rewardForB = await worldcupIns.winnerVaults(B.address)let rewardForC = await worldcupIns.winnerVaults(C.address)let rewardForD = await worldcupIns.winnerVaults(D.address)expect(rewardForA).to.equal(ethers.BigNumber.from(1333333334))expect(rewardForB).to.equal(ethers.BigNumber.from(1333333333))expect(rewardForC).to.equal(ethers.BigNumber.from(1333333333))expect(rewardForD).to.equal(ethers.BigNumber.from(0))})// 测试是否发出事件it("Should emit Finalize Event", async function () {const [A, B, C, D] = await ethers.getSigners();await preparePlay()let winners = [A.address, B.address, C.address]// 这里的事件入参故意设置成 4 个 应该是 2 个await expect(worldcupIns.finialize(Country.GERMANY)).to.emit(worldcupIns, "Finialize").withArgs(0, winners, 4 * ONE_GEWI, 1)})})// 测试领奖相关describe("ClaimReward", function () {// 测试领奖者是否有兑换资格it("Should fail if the claimer has no reward", async function () {await expect(worldcupIns.claimReward()).to.revertedWith("nothing to claim!")})// 玩家领完奖金后 合约奖金池应对应减少it("Should clear reward after claim", async function () {const [A, B, C, D] = await ethers.getSigners();await preparePlay()// A B C 中奖了await worldcupIns.finialize(Country.GERMANY)// B 地址余额let balBefore_B = await ethers.provider.getBalance(B.address)// 奖金池let balBefore_WC = await worldcupIns.getVaultBalance()// 待兑现奖金let balBefore_lockedAmts = await worldcupIns.lockedAmts()console.log("balBefore_A: ", balBefore_B.toString());console.log("balBefore_WC: ", balBefore_WC.toString())console.log("balBefore_lockedAmts: ", balBefore_lockedAmts.toString())// B 领奖let rewardForB = await worldcupIns.winnerVaults(B.address)await worldcupIns.connect(B).claimReward()// 领完奖后let balAfter_B = await ethers.provider.getBalance(B.address)let balAfter_WC = await worldcupIns.getVaultBalance()let balAfter_lockedAmts = await worldcupIns.lockedAmts()console.log("balAfter_B :  ", balAfter_B.toString());console.log("balAfter_WC: ", balAfter_WC.toString())console.log("balAfter_lockedAmts: ", balAfter_lockedAmts.toString())// 合约奖金池中金额减少expect(balBefore_WC.sub(balAfter_WC)).to.equal(rewardForB)// 待兑现金额减少expect(balBefore_lockedAmts.sub(balAfter_lockedAmts)).to.equal(rewardForB)})})
});

编写完,运行单元测试:npm hardhat test,效果如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HD722ydK-1670485321229)(media/16702110821588/16704078484894.jpg)]

部署到本地网络

编写部署脚本 scripts/deploy.ts:

import { ethers } from "hardhat";async function main() {const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;const timestamp = Math.floor(Date.now() / 1000)const deadline = timestamp + TWO_WEEKS_IN_SECS;console.log('deadline:', deadline)// 获取合约对象const WorldCup = await ethers.getContractFactory("WorldCup");// 部署const worldcup = await WorldCup.deploy(deadline);// 等待部署完成await worldcup.deployed();console.log(`new worldcup deployed to ${worldcup.address}`);
}// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {console.error(error);process.exitCode = 1;
});

hardhat 内部实现了一个本地 EVM,可以运行一个本地节点,开发过程,我们可以选择启动节点,并在上面部署,具体如下:

# 运行脚本,部署合约
npx hardhat run scripts/deploy.ts# 启动节点 node
npx hardhat node#部署合约到本地 node 节点
npx hardhat run scripts/deploy.ts --network localhost

部署成功后,效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dpUJXsQ3-1670485321229)(media/16702110821588/16704095309379.jpg)]

部署到测试网络

首先修改配置文件 hardhat.config.ts,具体如下:

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";// 需要先单独安装再引用:npm install dotenv
require('dotenv').config()let ALCHEMY_KEY = process.env.ALCHEMY_KEY || ''
let INFURA_KEY = process.env.INFURA_KEY || ''
let PRIVATE_KEY = process.env.PRIVATE_KEY || ''
// 用于在 Etherscan 验证合约
let ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ''console.log(ALCHEMY_KEY);
console.log(INFURA_KEY);
console.log(PRIVATE_KEY);
console.log(ETHERSCAN_API_KEY);const config: HardhatUserConfig = {// solidity: "0.8.9",// 配置网络 kovan, bsc, mainnetnetworks: {hardhat: {},// 配置 goerli 网络goerli: {// 注意 url 是 ``,而不是 ''url : `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_KEY}`,accounts: [PRIVATE_KEY]},kovan: {url: `https://kovan.infura.io/v3/${INFURA_KEY}`,accounts: [PRIVATE_KEY]}},// 配置自动化 verify 相关etherscan: {apiKey: {goerli: ETHERSCAN_API_KEY}},// 配置编译器版本solidity: {version: "0.8.9",settings: {optimizer: {enabled: true,runs: 200}}},
};export default config;

然后在项目根目录下添加 .env 文件,以配置连接用到的 key,先获取 key
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78X8DQ9F-1670485321230)(media/16702110821588/16704826910759.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDqsbIJY-1670485321230)(media/16702110821588/16704827956255.jpg)]

// 在 etherscan.io 官网获取
ETHERSCAN_API_KEY=
// 在 Alchemy 官网仪表板获取
ALCHEMY_KEY= "*****"(记住结尾不能加冒号)
INFURA_KEY=
// 测试网钱包私钥
PRIVATE_KEY=

接着部署到 goerli 测试网络(注意将 Worldcup.sol 中 console.sol 相关内容注释掉):

# npx hardhat run scripts/deploy.ts --network  
npx hardhat run scripts/deploy.ts --network goerli# 执行后得到部署后的合约地址:******

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zorrP7Dw-1670485321231)(media/16702110821588/16704830950509.jpg)]
再自动验证合约:

# npx hardhat verify  [para1] [para2] ...  --network goerli
npx hardhat verify 0x06515F07F0B9c85Df8c5Cb745e9A24EA2f6e7882 1671691242 --network goerli

验证这一步,如果是国内使用梯子的朋友可能会报错,比如类似于:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuAhowKw-1670485321231)(media/16702110821588/16704832776716.jpg)]
根本之一可能是电脑设置的代理只针对浏览器,终端没有设置代理,这个问题我并没有真正解决,虽然我尝试在 hosts 文件中添加了地址映射,解决了连接超时的问题,但最后结果就像上面这样报另外一个错误,不知道如何解决了,如果有解决了的小伙伴可以留言。最后采取的方案是直接在 https://goerli.etherscan.io/ 页面上执行验证,具体验证过程可以参考另一篇文章:如何在 goerli.etherscan.io 上验证合约

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...