世界杯竞猜项目Dapp-第二章(hardhat部署合约)

news2024/11/17 23:42:15

创建 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:string
    let 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 = worldcup
        ownerAddr = owner.address
        otherAccountAddr = otherAccount.address
        deadline1 = 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()

            // 调用 finalize
            await 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, mainnet
  networks: {
    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 <netWorkName> 
npx hardhat run scripts/deploy.ts --network goerli

# 执行后得到部署后的合约地址:******

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

# npx hardhat verify <contractAddr> [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 上验证合约

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/72758.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Flink】自定义keyBy的KeySelector

我们通常在写Flink程序的时候都会遇到keyBy,比如按照某条数据的某个字段进行分类计算,或者计算pv,还有需要用到Flink定时器的高级功能。 下面我们根据keyby的源码,看下keyBy的用法(本博客基于Flink1.13) 源码有5种方式定义keyBy, 但是其中的2种已经不用了,如果用户想…

PC的ARM的安全启动

其实还是很不习惯将PC和ARM联系起来&#xff0c;当然主要是因为我刚刚接触嵌入式没有多久&#xff0c;更别说服务器、PC端了。 脑子里固化了的X86。 于是这一篇学习一下&#xff0c;基于ARM的PC&#xff0c;是怎么利用这个ATF框架&#xff0c;进行安全启动的。 文章内容来自…

9个做好动效设计的好方法

动态效果意味着充满生命和兴奋&#xff0c;为静态事物增添生命。然而&#xff0c;在软件方面&#xff0c;动态效果不仅是为了获得乐趣&#xff0c;也是为了解决问题。 作为人类&#xff0c;我们习惯于看到世界在我们周围移动。如果任何软件有连贯的动画&#xff0c;它都会感到…

【Python游戏】Python基于第三方库pygame实现一个魂斗罗小游戏,毕业设计必备 | 附源码

前言 halo&#xff0c;包子们下午好 今天给打击整一个魂斗罗小游戏 很多小伙伴接触魂斗罗应该是在小时候的一个手柄游戏上面吧 我记得作为90后的我&#xff0c;玩这一款游戏是在小学的时候 废话不多说&#xff0c;直接上才艺 今天给大家直接安排 相关文件 关注小编&#xff…

30分钟了解linux操作系统内核总结

【推荐阅读】 概述Linux内核驱动之GPIO子系统API接口 一篇长文叙述Linux内核虚拟地址空间的基本概括 轻松学会linux下查看内存频率,内核函数,cpu频率 纯干货&#xff0c;linux内存管理——内存管理架构&#xff08;建议收藏&#xff09; Linux 内核性能优化的全景指南&#xff…

中学语文杂志中学语文杂志社中学语文编辑部2022年第30期目录

理论_视点《中学语文》投稿&#xff1a;cn7kantougao163.com 追求服务于学的“智慧语文”教育 陈维贤; 3-5 教学_阅读教学 高中古诗词群文阅读教学特点新探 张红娟; 6-8 导读需导思 领读方领悟 罗艳兰; 9-10 学习任务群背景下的群文阅读教学研究 丁雪云; 11-12…

守门员VS软件测试

虽然刚刚为世界杯贡献了点彩票钱&#xff0c;但我依然热爱着足球这项赛事。最近熬夜看球&#xff0c;突然发现我们软件开发团队里&#xff0c;守门员这个职位就像我们公司的那个测试倒霉蛋儿一样。 1、球队配比VS开发团队人员配比 △ 不管你的球队是443&#xff0c;还是552&am…

如何自定义SpringBoot中的starter,并且使用它

目录 1 简介 2 规范 2.1 命名 2.2 模块划分 3 示例 1 简介 SpringBoot中的starter是一种非常重要的机制&#xff0c;能够抛弃以前繁琐的配置&#xff0c;将其统一集成进starter&#xff0c;应用者只需要在maven中引入starter依赖&#xff0c;SpringBoot就自动扫描到要加载…

PyTorch笔记 - A ConvNet for the 2020s (ConvNeXt) 网络

欢迎关注我的CSDN:https://blog.csdn.net/caroline_wendy 本文地址:https://blog.csdn.net/caroline_wendy/article/details/128236899 Paper:A ConvNet for the 2020s,FAIR,Berkeley Code:https://github.com/facebookresearch/ConvNeXt 网络结构: stem layer:骨干层…

【k8s宝典】2022年12月份Kubernetes 认证管理员CKA轻松通过攻坚克难技巧

目标&#xff1a;攻克三大难点 提示&#xff1a;本人是12月近期通过的cka考试&#xff0c;所以总结的技巧是当前最新的&#xff0c;后面如果有稍许变化&#xff0c;这些技巧照样适用&#xff0c;题目请以实际为主 复制键不能用怎么办&#xff1f; 新版考试系统使用Ctrl c不…

【论文阅读笔记】Noise2Noise: Learning Image Restoration without Clean Data

官方代码地址&#xff1a;https://github.com/NVlabs/noise2noise 论文地址&#xff1a;https://arxiv.org/abs/1803.04189 第一次接触这方向&#xff0c;相当于翻译了。 摘要 作者通过机器学习将基础统计推理应用于信号重建上&#xff1a;学习将损坏的观察结果&#xff08;co…

商务与经济统计 | 描述统计学

一.表格法和图形法 条形图 是一种描述已在一个频数&#xff0c;相对频数或百分数频数分布中汇总的品质数据的图形方法 饼形图 是一种常用的描绘品质数据的相对频数分布的图形方法 直方图 横轴上表示所关心的变量&#xff0c;纵轴上表示频数&#xff0c;相对频数或百分数频数…

23软考备考已开始,网络工程师知识点速记~(5)

新一轮软考备考来啦~ 为了帮助大家提高备考效率&#xff0c;将2023上半年软考网络工程师知识点速记分享给大家&#xff0c;快来跟着一起打卡学习吧&#xff01; 进程的同步和互斥 计算机有了操作系统后性能大幅度提升&#xff0c;其根本原因就在于实现了进程的并发运行。多个…

接口多实现

一、什么是接口 接口是抽象类的延伸&#xff0c;可以将它看作是纯粹的对象类 二、接口模式的特性 &#xff08;1&#xff09;接口不可以被实例化。 &#xff08;2&#xff09;实现类必须实现接口的所有方法&#xff08;类似于抽象类和抽象方法&#xff09;。 &#xff08;3&…

基于百度飞桨PaddleOCR的图片文字识别

PaddleOCR项目源码&#xff1a;https://github.com/PaddlePaddle/PaddleOCR 飞桨开源文字识别模型套件PaddleOCR&#xff0c;目标是打造丰富、领先、实用的文本识别模型/工具库。最新开源的超轻量PP-OCRv3模型大小仅为16.2M。同时支持中英文识别&#xff1b;支持倾斜、竖排等多…

[附源码]Python计算机毕业设计Django-中国传统手工艺销售平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

相似度衡量:苏剑林博客-3

本文主要是对苏剑林老师之前的博客中&#xff0c;对相似度相关的内容稍作整理。 Sentence-bert 是利用bert对两个句子判断相似度。 左图是训练期间的相似度计算方法&#xff0c;右图是推来过程中的相似度计算方法。 训练过程中使用时dense-linear方法&#xff0c;推理过程中…

vue 实现通过字符串关键字符动态渲染 input 输入框

vue 实现通过字符串关键字符动态渲染 input 输入框 今天做一个简单的demo&#xff0c;就是有一个字符串&#xff0c;字符串里面有标识符&#xff0c;前端检测到标识符之后&#xff0c;需要将这个标识符转换成一个 input 输入框并且进行数据输入和绑定功能。 问题描述 就比如现…

SOLIDWORKS 2023新功能揭秘!SOLIDWORKS Simulation 2023版本

SOLIDWORKS 2023新版本已经与大家见面&#xff0c;今天众联亿诚与大家分享SOLIDWORKS Simulation 2023新功能&#xff0c;让我们先一起来看看视频—— 点击观看SOLIDWORKS Simulation 2023新功能 一直以来&#xff0c;SOLIDWORKS Simulation以其易学易用和与设计深度集成深受广…

Docker自定义jdk镜像与上传阿里云

目录 自定义jdk镜像 制作jdk8 :v1.0镜像 alpine制作jdk镜像 alpine简介 基于Alpine制作jdk镜像 Alpine制作jre镜像 Docker镜像上传至阿里云 由于官方没有提供jdk&#xff0c;所以需要自定义jdk来配置java环境&#xff0c;以便于后期部署项目 自定义jdk镜像 制作jdk8 :v1.0…