{"content":{"title":"Web3系列教程之进阶篇---10. Chainlink VRF(可验证的随机函数)","body":"## 简介\r\n\r\n在计算机处理计算时，由于计算机的确定性，随机性是一个重要但难以处理的问题。说到区块链，情况更是如此，因为计算机不仅是确定性的，而且是透明的。因此，由于随机性将在链上计算，而链是所有矿工和用户的公共信息，因此无法生成可靠的本机随机数。\r\n\r\n所以我们可以使用一些web2技术来生成随机性，然后在链上使用它们。\r\n\r\n## 什么是[oracle](https:\/\/learnblockchain.cn\/article\/1019)(预言机)?\r\n\r\n- 预言机是将数据从外部世界发送到区块链的智能合约，反之亦然。\r\n\r\n- 然后，智能合约可以使用这些数据做出决定并改变其状态。\r\n\r\n- 它们作为区块链和外部世界之间的桥梁。\r\n\r\n- 然而，需要注意的是，区块链预言机本身不是数据源，它的工作是查询、验证和认证外部数据，然后再将其传递给智能合约。\r\n\r\n今天我们将学习其中一个名为Chainlink VRF的预言机。\r\n\r\n开始吧🚀\r\n\r\n## 介绍\r\n\r\n- Chainlink VRF 用来产生随机值的预言机。\r\n\r\n- 这些数值是用加密证明来验证的。\r\n- 这些证明验证了结果没有被预言机操作员、用户、矿工等篡改或操纵。\r\n- 证明是在链上公布的，因此可以被验证。\r\n- 在验证成功后，它们会被要求随机性的智能合约所使用。\r\n\r\n官方的Chainlink文档将VRF描述为。\r\n\r\n> Chainlink VRF（可验证的随机函数）是一个为智能合约设计的可证明的公平和可验证的随机性来源。智能合约开发者可以使用Chainlink VRF作为防篡改的随机数发生器（RNG），为任何依赖不可预测结果的应用构建可靠的智能合约。\r\n\r\n##  它是如何工作的？\r\n\r\n- Chainlink有两个合约，我们主要关注的是[VRFConsumerBase.sol](https:\/\/github.com\/smartcontractkit\/chainlink\/blob\/master\/contracts\/src\/v0.8\/VRFConsumerBase.sol)和VRFCoordinator。\r\n\r\n- VRFConsumerBase是将调用VRF协调员的合同，它最终负责发布随机性。\r\n\r\n- 我们将继承VRFConsumerBase，并将使用其中的两个函数。\r\n  - requestRandomness，它对随机性提出初始请求。\r\n  - fulfillRandomness，这是一个接收并对经过验证的随机性做一些事情的函数。\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727220152.png)\r\n\r\n- 如果你看一下图，你就可以理解这个流程，RandomGameWinner合约将继承VRFConsumerBase合约，并将在VRFConsumerBase中调用requestRandomness函数。\r\n\r\n- 在调用该函数时，对随机性的请求开始了，VRFConsumerBase进一步调用VRFCoordinator合约，该合约负责从外部世界获取随机性。\r\n- 在VRFCoordinator获得随机性后，它调用VRFConsumerBase中的fullFillRandomness函数，然后进一步选择赢家。\r\n- 注意重要的部分是，尽管你调用了 requestRandomness 函数，但你在 fullFillRandomness 函数中获得了随机性。\r\n\r\n## 先决条件\r\n\r\n- 你已经完成了[[Hardhat Verification](https:\/\/github.com\/LearnWeb3DAO\/hardhat-verification) ](https:\/\/github.com\/LearnWeb3DAO\/hardhat-verification)模块\r\n\r\n- 你已经完成了Layer 2教程\r\n\r\n## 要求\r\n\r\n- 我们今天将开发一个彩 票游戏\r\n- 每场比赛将有一个最大的玩家人数和参赛费\r\n- 在最大数量的玩家进入游戏后，将随机选择一名获胜者。\r\n- 赢家将获得`maxplayers*entryfee`数额的ether ，以赢得游戏。\r\n\r\n## 构建\r\n\r\n- 最初开始时，在你的电脑中创建一个名为`RandomWinnerGame`的文件夹\r\n\r\n- 为了构建智能合约，我们将使用Hardhat。Hardhat是一个Ethereum开发环境和框架，为Solidity的全栈开发而设计。简单地说，你可以编写你的智能合约，部署它们，运行测试，并调试你的代码。\r\n- 要设置一个Hardhat项目，打开终端并在`RandomWinnerGame`文件夹内执行这些命令\r\n\r\n```bash\r\nmkdir hardhat-tutorial\r\ncd hardhat-tutorial\r\nnpm init --yes\r\nnpm install --save-dev hardhat\r\n```\r\n\r\n- 在你安装Hardhat的同一目录下运行。\r\n\r\n```bash\r\nnpx hardhat\r\n```\r\n\r\n- 选择 `Create a Javascript project`\r\n- 在`Hardhat Project root`下按下回车\r\n- 如果你想添加一个`.gitignore`，请按回车键询问。\r\n- `Do you want to install this sample project's dependencies with npm (@nomicfoundation\/hardhat-toolbox)?`按下回车\r\n\r\n现在你有一个准备好的hardhat项目了!\r\n\r\n如果你不是在mac上，请做这个额外的步骤，也安装这些库 :)\r\n\r\n```bash\r\nnpm install --save-dev @nomicfoundation\/hardhat-toolbox\r\n```\r\n\r\n并对所有问题按回车键。\r\n\r\n- 在同一个终端中，现在安装`@openzeppelin\/contracts`，因为我们要导入Openzeppelin的合同。\r\n\r\n```\r\nnpm install @openzeppelin\/contracts\r\n```\r\n\r\n- 我们还将验证我们的合同，所以让我们安装hardhat etherscan库\r\n\r\n```bash\r\nnpm install --save-dev @nomiclabs\/hardhat-etherscan\r\n```\r\n\r\n- 最后，我们将安装chainlink 合约，以使用chainlink  VRF\r\n\r\n```bash\r\nnpm install --save @chainlink\/contracts\r\n```\r\n\r\n- 现在在`contracts`目录内创建一个新文件，名为`RandomWinnerGame.sol`，并粘贴以下几行代码。\r\n\r\n```solidity\r\n\/\/ SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.4;\r\n\r\nimport \"@openzeppelin\/contracts\/access\/Ownable.sol\";\r\nimport \"@chainlink\/contracts\/src\/v0.8\/VRFConsumerBase.sol\";\r\n\r\ncontract RandomWinnerGame is VRFConsumerBase, Ownable {\r\n\r\n    \/\/Chainlink variables\r\n    \/\/ The amount of LINK to send with the request\r\n    uint256 public fee;\r\n    \/\/ ID of public key against which randomness is generated\r\n    bytes32 public keyHash;\r\n\r\n    \/\/ Address of the players\r\n    address[] public players;\r\n    \/\/Max number of players in one game\r\n    uint8 maxPlayers;\r\n    \/\/ Variable to indicate if the game has started or not\r\n    bool public gameStarted;\r\n    \/\/ the fees for entering the game\r\n    uint256 entryFee;\r\n    \/\/ current game id\r\n    uint256 public gameId;\r\n\r\n    \/\/ emitted when the game starts\r\n    event GameStarted(uint256 gameId, uint8 maxPlayers, uint256 entryFee);\r\n    \/\/ emitted when someone joins a game\r\n    event PlayerJoined(uint256 gameId, address player);\r\n    \/\/ emitted when the game ends\r\n    event GameEnded(uint256 gameId, address winner,bytes32 requestId);\r\n\r\n   \/**\r\n   * constructor inherits a VRFConsumerBase and initiates the values for keyHash, fee and gameStarted\r\n   * @param vrfCoordinator address of VRFCoordinator contract\r\n   * @param linkToken address of LINK token contract\r\n   * @param vrfFee the amount of LINK to send with the request\r\n   * @param vrfKeyHash ID of public key against which randomness is generated\r\n   *\/\r\n    constructor(address vrfCoordinator, address linkToken,\r\n    bytes32 vrfKeyHash, uint256 vrfFee)\r\n    VRFConsumerBase(vrfCoordinator, linkToken) {\r\n        keyHash = vrfKeyHash;\r\n        fee = vrfFee;\r\n        gameStarted = false;\r\n    }\r\n\r\n    \/**\r\n    * startGame starts the game by setting appropriate values for all the variables\r\n    *\/\r\n    function startGame(uint8 _maxPlayers, uint256 _entryFee) public onlyOwner {\r\n        \/\/ Check if there is a game already running\r\n        require(!gameStarted, \"Game is currently running\");\r\n        \/\/ empty the players array\r\n        delete players;\r\n        \/\/ set the max players for this game\r\n        maxPlayers = _maxPlayers;\r\n        \/\/ set the game started to true\r\n        gameStarted = true;\r\n        \/\/ setup the entryFee for the game\r\n        entryFee = _entryFee;\r\n        gameId += 1;\r\n        emit GameStarted(gameId, maxPlayers, entryFee);\r\n    }\r\n\r\n    \/**\r\n    joinGame is called when a player wants to enter the game\r\n     *\/\r\n    function joinGame() public payable {\r\n        \/\/ Check if a game is already running\r\n        require(gameStarted, \"Game has not been started yet\");\r\n        \/\/ Check if the value sent by the user matches the entryFee\r\n        require(msg.value == entryFee, \"Value sent is not equal to entryFee\");\r\n        \/\/ Check if there is still some space left in the game to add another player\r\n        require(players.length < maxPlayers, \"Game is full\");\r\n        \/\/ add the sender to the players list\r\n        players.push(msg.sender);\r\n        emit PlayerJoined(gameId, msg.sender);\r\n        \/\/ If the list is full start the winner selection process\r\n        if(players.length == maxPlayers) {\r\n            getRandomWinner();\r\n        }\r\n    }\r\n\r\n    \/**\r\n    * fulfillRandomness is called by VRFCoordinator when it receives a valid VRF proof.\r\n    * This function is overrided to act upon the random number generated by Chainlink VRF.\r\n    * @param requestId  this ID is unique for the request we sent to the VRF Coordinator\r\n    * @param randomness this is a random unit256 generated and returned to us by the VRF Coordinator\r\n   *\/\r\n    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual override  {\r\n        \/\/ We want out winnerIndex to be in the length from 0 to players.length-1\r\n        \/\/ For this we mod it with the player.length value\r\n        uint256 winnerIndex = randomness % players.length;\r\n        \/\/ get the address of the winner from the players array\r\n        address winner = players[winnerIndex];\r\n        \/\/ send the ether in the contract to the winner\r\n        (bool sent,) = winner.call{value: address(this).balance}(\"\");\r\n        require(sent, \"Failed to send Ether\");\r\n        \/\/ Emit that the game has ended\r\n        emit GameEnded(gameId, winner,requestId);\r\n        \/\/ set the gameStarted variable to false\r\n        gameStarted = false;\r\n    }\r\n\r\n    \/**\r\n    * getRandomWinner is called to start the process of selecting a random winner\r\n    *\/\r\n    function getRandomWinner() private returns (bytes32 requestId) {\r\n        \/\/ LINK is an internal interface for Link token found within the VRFConsumerBase\r\n        \/\/ Here we use the balanceOF method from that interface to make sure that our\r\n        \/\/ contract has enough link so that we can request the VRFCoordinator for randomness\r\n        require(LINK.balanceOf(address(this)) >= fee, \"Not enough LINK\");\r\n        \/\/ Make a request to the VRF coordinator.\r\n        \/\/ requestRandomness is a function within the VRFConsumerBase\r\n        \/\/ it starts the process of randomness generation\r\n        return requestRandomness(keyHash, fee);\r\n    }\r\n\r\n     \/\/ Function to receive Ether. msg.data must be empty\r\n    receive() external payable {}\r\n\r\n    \/\/ Fallback function is called when msg.data is not empty\r\n    fallback() external payable {}\r\n}\r\n```\r\n\r\n- 构造函数接收以下参数。\r\n  - `vrfCoordinator`，是VRFCoordinator合同的地址。\r\n  - `linkToken`是链接令牌的地址，它是chainlink 获取其付款的令牌。\r\n  - `vrfFee`是发送随机性请求所需的链接令牌的数量\r\n  - `vrfKeyHash`，这是生成随机性的公钥的ID。这个值负责为我们的随机性请求生成一个唯一的ID，称为`requestId`。\r\n\r\n(所有这些数值都是由**Chainlink**提供给我们的)\r\n\r\n```solidity\r\n\/**\r\n* startGame starts the game by setting appropriate values for all the variables\r\n*\/\r\nfunction startGame(uint8 _maxPlayers, uint256 _entryFee) public onlyOwner {\r\n    \/\/ Check if there is a game already running\r\n    require(!gameStarted, \"Game is currently running\");\r\n    \/\/ empty the players array\r\n    delete players;\r\n    \/\/ set the max players for this game\r\n    maxPlayers = _maxPlayers;\r\n    \/\/ set the game started to true\r\n    gameStarted = true;\r\n    \/\/ setup the entryFee for the game\r\n    entryFee = _entryFee;\r\n    gameId += 1;\r\n    emit GameStarted(gameId, maxPlayers, entryFee);\r\n}\r\n```\r\n\r\n- `onlyOwner`函数意味着它只能由所有者调用。\r\n- 这个函数用于开始游戏，在这个函数被调用后，玩家可以进入游戏，直到达到极限。\r\n- 它还会触发`GameStarted`事件\r\n\r\n```solidity\r\n\/**\r\njoinGame is called when a player wants to enter the game\r\n *\/\r\nfunction joinGame() public payable {\r\n    \/\/ Check if a game is already running\r\n    require(gameStarted, \"Game has not been started yet\");\r\n    \/\/ Check if the value sent by the user matches the entryFee\r\n    require(msg.value == entryFee, \"Value sent is not equal to entryFee\");\r\n    \/\/ Check if there is still some space left in the game to add another player\r\n    require(players.length < maxPlayers, \"Game is full\");\r\n    \/\/ add the sender to the players list\r\n    players.push(msg.sender);\r\n    emit PlayerJoined(gameId, msg.sender);\r\n    \/\/ If the list is full start the winner selection process\r\n    if(players.length == maxPlayers) {\r\n        getRandomWinner();\r\n    }\r\n}\r\n```\r\n\r\n- 当用户想进入一个游戏时，这个函数将被调用\r\n\r\n- 如果达到`maxPlayers`限制，它将调用`getRandomWinner`函数。\r\n\r\n```solidity\r\n\/**\r\n* getRandomWinner is called to start the process of selecting a random winner\r\n*\/\r\nfunction getRandomWinner() private returns (bytes32 requestId) {\r\n    \/\/ LINK is an internal interface for Link token found within the VRFConsumerBase\r\n    \/\/ Here we use the balanceOF method from that interface to make sure that our\r\n    \/\/ contract has enough link so that we can request the VRFCoordinator for randomness\r\n    require(LINK.balanceOf(address(this)) >= fee, \"Not enough LINK\");\r\n    \/\/ Make a request to the VRF coordinator.\r\n    \/\/ requestRandomness is a function within the VRFConsumerBase\r\n    \/\/ it starts the process of randomness generation\r\n    return requestRandomness(keyHash, fee);\r\n}\r\n```\r\n\r\n- 这个函数首先检查我们的合同是否有Link token，然后再申请随机，因为chainlink 合约以Link token的形式申请费用。\r\n\r\n- 然后这个函数调用我们从`VRFConsumerBase`继承的`requestRandomness`，并开始随机数的生成过程。\r\n\r\n```solidity\r\n  \/**\r\n    * fulfillRandomness is called by VRFCoordinator when it receives a valid VRF proof.\r\n    * This function is overrided to act upon the random number generated by Chainlink VRF.\r\n    * @param requestId  this ID is unique for the request we sent to the VRF Coordinator\r\n    * @param randomness this is a random unit256 generated and returned to us by the VRF Coordinator\r\n   *\/\r\n    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual override  {\r\n        \/\/ We want out winnerIndex to be in the length from 0 to players.length-1\r\n        \/\/ For this we mod it with the player.length value\r\n        uint256 winnerIndex = randomness % players.length;\r\n        \/\/ get the address of the winner from the players array\r\n        address winner = players[winnerIndex];\r\n        \/\/ send the ether in the contract to the winner\r\n        (bool sent,) = winner.call{value: address(this).balance}(\"\");\r\n        require(sent, \"Failed to send Ether\");\r\n        \/\/ Emit that the game has ended\r\n        emit GameEnded(gameId, winner,requestId);\r\n        \/\/ set the gameStarted variable to false\r\n        gameStarted = false;\r\n    }\r\n```\r\n\r\n- 这个函数从`VRFConsumerBase`继承而来。它由`VRFCoordinator`合约在接收到外部世界的随机性后调用。\r\n\r\n- 在接收到随机性（可以是uint256范围内的任何数字）后，我们使用mod 操作符从范围从`0 to players.length-1`取值\r\n\r\n- 这就为我们选择了一个索引，我们用这个索引从玩家数组中检索出赢家。\r\n\r\n- 它将合约中所有的ether 发送给赢家，并发出一个`GameEnded`事件。\r\n\r\n- 现在我们要安装`dotenv`包，以便能够导入env文件并在我们的配置中使用它。打开一个终端，指向`hardhat-tutorial`目录，执行以下命令\r\n\r\n```\r\nnpm install dotenv\r\n```\r\n\r\n- 现在在`hardhat-tutorial`文件夹下创建一个`.env`文件，并添加以下几行，使用注释中的说明来获得你的`ALCHEMY_API_KEY_URL`、`MUMBAI_PRIVATE_KEY`和`POLYGONSCAN_KEY.`如果你的MetaMask上没有mumbai ，你可以按照[这个](https:\/\/portal.thirdweb.com\/guides\/get-matic-on-polygon-mumbai-testnet-faucet)来把它添加到你的MetaMask上，确保你获得mumbai 私钥的账户有mumbai  Matic资金，你可以从[这里](https:\/\/faucet.polygon.technology\/)得到一些。\r\n\r\n```bash\r\n\/\/ Go to https:\/\/www.alchemyapi.io, sign up, create\r\n  \/\/ a new App in its dashboard and select the network as Mumbai, and replace \"add-the-alchemy-key-url-here\" with its key url\r\n  ALCHEMY_API_KEY_URL=\"add-the-alchemy-key-url-here\"\r\n\r\n  \/\/ Replace this private key with your Mumbai account private key\r\n  \/\/ To export your private key from Metamask, open Metamask and\r\n  \/\/ go to Account Details > Export Private Key\r\n  \/\/ Be aware of NEVER putting real Ether into testing accounts\r\n  MUMBAI_PRIVATE_KEY=\"add-the-mumbai-private-key-here\"\r\n\r\n  \/\/ Go to https:\/\/polygonscan.com\/, sign up, on your account overview page,\r\n  \/\/ click on `API Keys`, add a new API key and copy the\r\n  \/\/ `API Key Token`\r\n  POLYGONSCAN_KEY=\"add-the-polygonscan-api-token-here\"\r\n```\r\n\r\n- 现在打开`hardhat.config.js`文件，我们将在这里添加`mumbai`网络，这样我们就可以把我们的合同部署到mumbai，还有一个`etherscan`对象，这样我们就可以在`polygonscan`上验证我们的合同。将`hardhat.config.js`文件中的所有行替换为下面给出的行。\r\n\r\n```javascript\r\nrequire(\"@nomicfoundation\/hardhat-toolbox\");\r\nrequire(\"dotenv\").config({ path: \".env\" });\r\nrequire(\"@nomiclabs\/hardhat-etherscan\");\r\n\r\nconst ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL;\r\n\r\nconst MUMBAI_PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;\r\n\r\nconst POLYGONSCAN_KEY = process.env.POLYGONSCAN_KEY;\r\n\r\nmodule.exports = {\r\n  solidity: \"0.8.9\",\r\n  networks: {\r\n    mumbai: {\r\n      url: ALCHEMY_API_KEY_URL,\r\n      accounts: [MUMBAI_PRIVATE_KEY],\r\n    },\r\n  },\r\n  etherscan: {\r\n    apiKey: {\r\n      polygonMumbai: POLYGONSCAN_KEY,\r\n    },\r\n  },\r\n};\r\n```\r\n\r\n- 创建一个名为`constants`的新文件夹，并在其中添加一个名为`index.js`的新文件。在index.js文件中添加这些行。\r\n\r\n```javascript\r\nconst { ethers, BigNumber } = require(\"hardhat\");\r\n\r\nconst LINK_TOKEN = \"0x326C977E6efc84E512bB9C30f76E30c160eD06FB\";\r\nconst VRF_COORDINATOR = \"0x8C7382F9D8f56b33781fE506E897a4F1e2d17255\";\r\nconst KEY_HASH =\r\n  \"0x6e75b569a01ef56d18cab6a8e71e6600d6ce853834d4a5748b720d06f878b3a4\";\r\nconst FEE = ethers.utils.parseEther(\"0.0001\");\r\nmodule.exports = { LINK_TOKEN, VRF_COORDINATOR, KEY_HASH, FEE };\r\n```\r\n\r\n我们从[这里](https:\/\/docs.chain.link\/docs\/vrf-contracts\/v1\/#polygon-matic-mumbai-testnet)得到的数值，已经由Chainlink提供给我们。\r\n\r\n- 让我们把合同部署到`mumbai`网络。在`scripts`文件夹下创建一个新文件，或替换默认的现有文件，命名为`deploy.js`。\r\n\r\n```javascript\r\nconst { ethers } = require(\"hardhat\");\r\nrequire(\"dotenv\").config({ path: \".env\" });\r\nrequire(\"@nomiclabs\/hardhat-etherscan\");\r\nconst { FEE, VRF_COORDINATOR, LINK_TOKEN, KEY_HASH } = require(\"..\/constants\");\r\n\r\nasync function main() {\r\n  \/*\r\n A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,\r\n so randomWinnerGame here is a factory for instances of our RandomWinnerGame contract.\r\n *\/\r\n  const randomWinnerGame = await ethers.getContractFactory(\"RandomWinnerGame\");\r\n  \/\/ deploy the contract\r\n  const deployedRandomWinnerGameContract = await randomWinnerGame.deploy(\r\n    VRF_COORDINATOR,\r\n    LINK_TOKEN,\r\n    KEY_HASH,\r\n    FEE\r\n  );\r\n\r\n  await deployedRandomWinnerGameContract.deployed();\r\n\r\n  \/\/ print the address of the deployed contract\r\n  console.log(\r\n    \"Verify Contract Address:\",\r\n    deployedRandomWinnerGameContract.address\r\n  );\r\n\r\n  console.log(\"Sleeping.....\");\r\n  \/\/ Wait for etherscan to notice that the contract has been deployed\r\n  await sleep(30000);\r\n\r\n  \/\/ Verify the contract after deploying\r\n  await hre.run(\"verify:verify\", {\r\n    address: deployedRandomWinnerGameContract.address,\r\n    constructorArguments: [VRF_COORDINATOR, LINK_TOKEN, KEY_HASH, FEE],\r\n  });\r\n}\r\n\r\nfunction sleep(ms) {\r\n  return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n\/\/ Call the main function and catch if there is any error\r\nmain()\r\n  .then(() => process.exit(0))\r\n  .catch((error) => {\r\n    console.error(error);\r\n    process.exit(1);\r\n  });\r\n```\r\n\r\n- 编译合同，打开终端，指向`hardhat-tutorial`目录，执行以下命令\r\n\r\n```\r\n  npx hardhat compile\r\n```\r\n\r\n- 要进行部署，请打开终端，指向hardhat-tutorial目录，并执行以下命令\r\n\r\n```\r\n  npx hardhat run scripts\/deploy.js --network mumbai\r\n```\r\n\r\n- 它应该已经打印了一个到 mumbai  polygonscan的链接，你的合同现在已经验证了。点击polygonscan链接，在那里与你的合同互动。\r\n\r\n- 让我们现在在polygonscan上玩游戏吧\r\n\r\n- 在你的终端，他们应该已经打印了一个链接到你的合同，如果没有，那么去[ [Mumbai Polygon Scan](https:\/\/mumbai.polygonscan.com\/)](https:\/\/mumbai.polygonscan.com\/)并搜索你的合同地址，它应该被验证。\r\n\r\n- 我们现在将用一些Chainlink来资助这个合同，这样我们就可以要求随机性，去[Polygon Faucet](https:\/\/faucet.polygon.technology\/)，从下拉菜单中选择Link，然后输入你的合同地址\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223246.png)\r\n\r\n- 现在，通过点击`Connect To Web3`，将你的钱包连接到[[Mumbai Polygon Scan](https:\/\/mumbai.polygonscan.com\/) ](https:\/\/mumbai.polygonscan.com\/)。 确保你的账户有一些mumbai matic  tokens\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223358.png)\r\n\r\n- 一旦连接，它将看起来像这样\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223422.png)\r\n\r\n- 然后在startGame函数中输入一些数值，并点击Write按钮\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223446.png)\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223459.png)\r\n\r\n- 现在你可以用你的地址加入游戏了。注意：我在这里输入的数值是10WEI，因为这是我指定的报名费的数值，但是因为加入游戏接受ether 而不是WEI，我必须将10WEI转换成ether 。你也可以用[eth转换器](https:\/\/eth-converter.com\/)将你的参赛费转换成ether \r\n\r\n- 现在刷新页面，并连接一个新的钱包，其中有一些matic，这样你就可以让另一个玩家加入。 注：我将最大玩家数设置为2，这样在我让另一个地址加入游戏后，它将选择赢家。\r\n\r\n- 如果你现在进入你的事件选项卡并不断刷新（`VRFCoordinator`调用`fullFillRandomness`函数需要一些时间，因为它必须从外部世界获得数据），在某一时刻你将能够看到一个事件，上面写着`GameEnded`\r\n\r\n- 从下拉菜单中为`GameEnded`事件中的第一个值转换Hex为地址，因为那是赢家的地址。\r\n\r\n![](https:\/\/hicoldcat.oss-cn-hangzhou.aliyuncs.com\/img\/20220727223739.png)\r\n\r\n它完成了  🚀\r\n\r\n你现在知道如何玩这个游戏了。在下一个教程中，我们将为此创建一个用户界面，并将学习如何使用代码本身来跟踪这些事件。\r\n\r\n准备开始吧🚀🚀\r\n\r\n\r\n> 原文： [https:\/\/www.learnweb3.io\/tracks\/junior\/chainlink-vrf](https:\/\/www.learnweb3.io\/tracks\/junior\/chainlink-vrf)\r\n\r\n\r\n![my.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/TlvqiFnA630fffb99a7f0.png!\/scale\/15)"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/7748","address":null},"history":"QmNx8CMEyrYiHMUKZNcRwqFcNcdTu4YQ17rgCnqNQ3vkVe","timestamp":1661999296,"version":1}