区块链全栈以太坊(十)项目实战之彩票

[TOC]

一、简介

彩票项目,用到了随机数 :chainlink VRF,定时器: Chainlink Automation。

(一)源码lottery

hardhat-smartcontract-lottery-fcc

(二)快速开始

安装依赖

#注意node版本,管理员身份打开cmd
nvm install 18.16.0
nvm use 18.16.0
nvm list
# yarn
yarn
# npm
#npm install

安装hardhat-shorthand

hardhat命令缩写, 如:

hh compile 代表 yarn hardhat compile

npm i -g hardhat-shorthand
# 或者
yarn global add hardhat-shorthand

二、源码分析Raffle.sol

(一)购买彩票逻辑

付钱入场
选一个随机数(chainlink VRF)
每几分钟选出一个获奖者(chainlink Automation)

(二)付钱入场


function enterRaffle() public payable {
    // require(msg.value >= i_entranceFee, "Not enough value sent");
    // require(s_raffleState == RaffleState.OPEN, "Raffle is not open");
    if (msg.value < i_entranceFee) {
        revert Raffle__SendMoreToEnterRaffle();
    }
    if (s_raffleState != RaffleState.OPEN) {
        revert Raffle__RaffleNotOpen();
    }
    s_players.push(payable(msg.sender));
    // Emit an event when we update a dynamic array or mapping
    // Named events with the function name reversed
    emit RaffleEnter(msg.sender);
    }

1) Logging

Evm中的功能。

1.当区块链上发生某些事情Events的时候,Evm 会把这些事情写入到一种名为Logs 的特殊数据结构中。

2.我们可以从我们运行的区块链节点中阅读这些Logs。
(运行、连接到一个节点,可以调用eth_getLogs来获取Logs)

3.Logging中有一个很重要的部分,就是Events
Events可以让你将信息保存到Logging结构中。
(这种方式比保存到storage 变量 更节省gas

4.events和 logs存在于一种 智能合约无法访问的特殊数据结构中。
(这就是节省gas的原因)

2) Events

当我们更新动态对象,例如 数组或者映射时,总是希望能触发Event。
https://docs.soliditylang.org/en/v0.8.24/contracts.html#events
https://learnblockchain.cn/docs/solidity/structure-of-a-contract.html#event

每一个 Event 都与在交易中发出该 Event 的智能合约或账户地址相关联。The Graph会监听并存储这些Event,以便以后可以查询。

js中可以对event进行监听。

 /* Events */
event RaffleEnter(address indexed player);

1. Topics

即:indexed参数,是一种更容易搜索的参数。

eth_getlogs函数,有一个参数可以让我们直接搜索特定的Topic
相对于非 indexed参数而言,搜索性更强。

  1. indexed 参数,
    最多可以有三个 。
  2. 非index 参数搜索困难。
    因为被ABI编码过(需要ABI才能解码)。

2. emit

触发事件,和调用函数非常类似。

emit RaffleEnter(msg.sender);

3.查看日志

etherscan搜索当前合约的地址,在Event tab中能看到事件日志列表。
日志数据结构如下:

event_1.png

因为这个合约,已经验证过代码、验证过了合约,所以etherscan知道它的ABI 是什么
所以,我们可以使用 Dec(解码模式)来查看log。

(三)随机数决定中奖

使用 Chainlink VSF 来进行随机数的获取。

(三) 随机开奖-chainlinkVSF

1)为什么要使用VSF

如果用区块链上的随机数,可能会被矿工攻击,因为矿工可以放弃区块,来多次重试以获取对自己最有利的随机数。

VSF Version2有些不同:
https://docs.chain.link/vrf

与Version1 最大的区别
不再需要通过Link向你的合约注入资金,而是新建一个订阅,往里存钱(link token), 每次调用VSF都会扣掉费用。
它基本上就是一个允许你为多个用户合约提供资金和维持余额的账户。

2)创建Subscriptions

我们需要…个用于请求随机数并支付“Oracle gas(“预言机 gas”)所需 Link的订阅ID。

中文说明:
https://zhuanlan.zhihu.com/p/522356922

官网参考
https://docs.chain.link/vrf/v2/subscription/examples/get-a-random-number

  1. 确保MetaMask连上测试网Sepolia,并且有eth 和 link的余额。

  2. https://vrf.chain.link/ 点击 connect ,连接钱包(account1)。

    基本上,这就是你放置资金的地方,以便你能够进行跨链使用。

  3. Create Subscription

    Admin adress: account1

    现在我们有了一个订阅,也就是用来注入资金的账户。

    接着就可以i用这个账户来进行我们所有的随机数请求。

  4. fund a Subscriptions

vsf_1.png

vsf_2.png
vsf_3.png
vsf_4.png
  1. add Consumer address( 合约地址)。

    创建一个合约,用来请求随机数。

    https://docs.chain.link/vrf/v2/subscription/examples/get-a-random-number#create-and-deploy-a-vrf-v2-compatible-contract

    根据上面的文档 用 remix 部署 样本合约(Subscription ID 要改)。

    合约地址填写到 该Subscription里。

    到这里,已经授权用户合约可以发起随机数请求了。

3)导入

import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
#添加依赖
yarn add --dev @chainlink/contracts

4)构造函数说明

 /* Functions */
constructor(
    //这个"vrfCoordinator"是…个负责随机数验证的合约的地址
    address vrfCoordinatorV2,
    //
    uint64 subscriptionId,
    //gas通道 ,能接受的以 wel 为单位支付的 “最高 gas 价格”。
    bytes32 gasLane, // keyHash
    uint256 interval,
    uint256 entranceFee,
    uint32 callbackGasLimit
)

1.vrfCoordinator

一个负责随机数验证的合约的地址

2.gasLane (keyHash)

https://docs.chain.link/vrf/v2/subscription/supported-networks#ethereum-mainnet
gas通道,即 keyHash
它告诉 Chainlink 节点你所能接受的以 wel 为单位支付的最高 gas 价格

如果gas价格飙升,获取随机数将花费你大量的资金,通过设定上限,我们
确保随机数不会返回

主网有多个通道可以选,测试网一般只有一个通道。

3.subscriptionId

合约用于支付随机数请求费用的订阅id。

实际上,他是一个链上合约,
我们可以使用它来为外部数据和外部计算的订阅提供资金

4.callbackGasLimit

'callbackGasLimit”是指回调函数""fulfillRandomWords" 能使用的 gas上限。

如果gas非常昂贵,他就会锁定随机数的响应,所以需要设置上限。

5)请求随机数。

requestId = vrfCoordinator.requestRandomWords()

6)获得随机数(回调).

//固定
fulfillRandomWords(uint256, /* requestId */ uint256[] memory randomWords )internal override

分两个交易比一个交易好得多。
因为:如果只有一个交易,就可以通过暴力尝试模拟调用这个交易。

(四) 定时开奖 -chainlinkAutomation

1)counter 定时任务Demo学习

计时器demo官方文档https://docs.chain.link/chainlink-automation/guides/compatible-contracts#vyper-example)

a.源码分析

checkUpkeep这实际上是一个不在链上运行的方法,

他是由一个 chainlink keepers/automations 网络节点 离链运行的。
并且很好的是,这里用到的 gas 实际上并不是链上的 gas。

如果你的"checkUpkeep"方法返回了"upkeepNeeded",它就会在链上运行
performUpkeep()

所以你可以在链外生成数据,然后传进来,称为"checkData。

然后这些数据将成为"performData"并传递给"performUpkeep。

performUpkeep"方法是验证正确性的地方,
你需要确认这些东西是否真的应该在链上进行修改和运行。

b.注册合约

  1. 打开 chainlink automation网页: https://automation.chain.link/

  2. register new upkeep(连接钱包),这个表单新版改动较大。

    1. 填写基本信息。 gas limit 200000, 预先存入 link
  3. upkeep address :粘贴counter合约地址。

    1. 从remix 或者 vscode复制 abi信息填入, 选择abi里的方法填入data。
  4. 等待钱包MetaMask确认。

c.查看任务详情

timer_1.png

timer_2.png
timer_3.png

2) 定时开奖实现

// 定时任务的业务执行条件是否具备,reture true则执行 定时任务performUpkeep。 
function checkUpkeep(bytes memory /* checkData */ )
        public
        view
        override
        returns (bool upkeepNeeded, bytes memory /* performData */ )
    {
        bool isOpen = RaffleState.OPEN == s_raffleState;
        bool timePassed = ((block.timestamp - s_lastTimeStamp) > i_interval);
        bool hasPlayers = s_players.length > 0;
        bool hasBalance = address(this).balance > 0;
        upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers);
        return (upkeepNeeded, "0x0"); // can we comment this out?
    }

    /**
     * @dev Once `checkUpkeep` is returning `true`, this function is called
     * and it kicks off a Chainlink VRF call to get a random winner.
     */
//performUpkeep 里再 手工调用一下 checkUpkeep。
    function performUpkeep(bytes calldata /* performData */ ) external override {
        (bool upkeepNeeded,) = checkUpkeep("");
        // require(upkeepNeeded, "Upkeep not needed");
        if (!upkeepNeeded) {
            revert Raffle__UpkeepNotNeeded(address(this).balance, s_players.length, uint256(s_raffleState));
        }
        s_raffleState = RaffleState.CALCULATING;
        uint256 requestId = i_vrfCoordinator.requestRandomWords(
            i_gasLane, i_subscriptionId, REQUEST_CONFIRMATIONS, i_callbackGasLimit, NUM_WORDS
        );
        // Quiz... is this redundant?
        emit RequestedRaffleWinner(requestId);
    }

三、部署

(一)编译

hh compile
yarn hardhat compile

(二)本地部署

mock

vrfCoordinator 合约mock,以及创建 mock 订阅,mock不需要link币。

// create VRFV2 Subscription
vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock")
vrfCoordinatorV2Address = vrfCoordinatorV2Mock.address
const transactionResponse = await vrfCoordinatorV2Mock.createSubscription()
 const transactionReceipt = await transactionResponse.wait()
subscriptionId = transactionReceipt.events[0].args.subId
// Fund the subscription
// Our mock makes it so we don't actually have to worry about sending fund
await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, FUND_AMOUNT)

yarn hardhat deploy

四、chainlink

注意,pricefeed的调用不需要费用,因为有很多公司自主。

随机数、定时任务的调用时需要支付 link币的。
随机数:创建Subscriptions
定时任务:register new upkeep

五、hardhat单测

evm_increaseTime

可以让我们增加区块链的时间。

evm_mine

可以让我们挖矿,或者说创建一个新的区块。

callStatic

hardhat中,调用合约public函数时,会认为是要发送一笔交易 了(public view不会)。
如下例子中的情况,实际并不用发送交易,而是要 看下函数返回值 是什么。

就可以用callStatic来模拟发送交易,但是又能直接拿到函数返回值。

it("returns false if people haven't sent any ETH", async () => {
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1])
await network.provider.request({ method: "evm_mine", params: [] })
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x") // upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers)
assert(!upkeepNeeded)
})

txReceipt.events[1].

https://stackoverflow.com/questions/73230175/does-etherjs-transactionreceipt-have-an-events-object

六、hardhat集成测试

监听chainlink VSF

单元测试 时,对 vsf进行mock模拟调用,
集成测试可不行 。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容