在计算机处理计算时,由于计算机的确定性,随机性是一个重要但难以处理的问题。说到区块链,情况更是如此,因为计算机不仅是确定性的,而且是透明的。因此,由于随机性将在链上计算,而链是所有矿工和用户的公共信息,因此无法生成可靠的本机随机数。
所以我们可以使用一些web2技术来生成随机性,然后在链上使用它们。
今天我们将学习其中一个名为Chainlink VRF的预言机。
开始吧
官方的Chainlink文档将VRF描述为。
Chainlink VRF(可验证的随机函数)是一个为智能合约设计的可证明的公平和可验证的随机性来源。智能合约开发者可以使用Chainlink VRF作为防篡改的随机数发生器(RNG),为任何依赖不可预测结果的应用构建可靠的智能合约。
mkdir hardhat-tutorialcd hardhat-tutorialnpm init --yesnpm install --save-dev hardhat
npx hardhat
现在你有一个准备好的hardhat项目了!
如果你不是在mac上,请做这个额外的步骤,也安装这些库 :)
npm install --save-dev @nomicfoundation/hardhat-toolbox
并对所有问题按回车键。
npm install @openzeppelin/contracts
npm install --save-dev @nomiclabs/hardhat-etherscan
npm install --save @chainlink/contracts
// SPDX-License-Identifier: MITpragma solidity ^0.8.4;import "@openzeppelin/contracts/access/Ownable.sol";import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";contract RandomWinnerGame is VRFConsumerBase, Ownable { //Chainlink variables // The amount of LINK to send with the request uint256 public fee; // ID of public key against which randomness is generated bytes32 public keyHash; // Address of the players address[] public players; //Max number of players in one game uint8 maxPlayers; // Variable to indicate if the game has started or not bool public gameStarted; // the fees for entering the game uint256 entryFee; // current game id uint256 public gameId; // emitted when the game starts event GameStarted(uint256 gameId, uint8 maxPlayers, uint256 entryFee); // emitted when someone joins a game event PlayerJoined(uint256 gameId, address player); // emitted when the game ends event GameEnded(uint256 gameId, address winner,bytes32 requestId); /** * constructor inherits a VRFConsumerBase and initiates the values for keyHash, fee and gameStarted * @param vrfCoordinator address of VRFCoordinator contract * @param linkToken address of LINK token contract * @param vrfFee the amount of LINK to send with the request * @param vrfKeyHash ID of public key against which randomness is generated */ constructor(address vrfCoordinator, address linkToken, bytes32 vrfKeyHash, uint256 vrfFee) VRFConsumerBase(vrfCoordinator, linkToken) { keyHash = vrfKeyHash; fee = vrfFee; gameStarted = false; } /** * startGame starts the game by setting appropriate values for all the variables */ function startGame(uint8 _maxPlayers, uint256 _entryFee) public onlyOwner { // Check if there is a game already running require(!gameStarted, "Game is currently running"); // empty the players array delete players; // set the max players for this game maxPlayers = _maxPlayers; // set the game started to true gameStarted = true; // setup the entryFee for the game entryFee = _entryFee; gameId += 1; emit GameStarted(gameId, maxPlayers, entryFee); } /** joinGame is called when a player wants to enter the game */ function joinGame() public payable { // Check if a game is already running require(gameStarted, "Game has not been started yet"); // Check if the value sent by the user matches the entryFee require(msg.value == entryFee, "Value sent is not equal to entryFee"); // Check if there is still some space left in the game to add another player require(players.length < maxPlayers, "Game is full"); // add the sender to the players list players.push(msg.sender); emit PlayerJoined(gameId, msg.sender); // If the list is full start the winner selection process if(players.length == maxPlayers) { getRandomWinner(); } } /** * fulfillRandomness is called by VRFCoordinator when it receives a valid VRF proof. * This function is overrided to act upon the random number generated by Chainlink VRF. * @param requestId this ID is unique for the request we sent to the VRF Coordinator * @param randomness this is a random unit256 generated and returned to us by the VRF Coordinator */ function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual override { // We want out winnerIndex to be in the length from 0 to players.length-1 // For this we mod it with the player.length value uint256 winnerIndex = randomness % players.length; // get the address of the winner from the players array address winner = players[winnerIndex]; // send the ether in the contract to the winner (bool sent,) = winner.call{value: address(this).balance}(""); require(sent, "Failed to send Ether"); // Emit that the game has ended emit GameEnded(gameId, winner,requestId); // set the gameStarted variable to false gameStarted = false; } /** * getRandomWinner is called to start the process of selecting a random winner */ function getRandomWinner() private returns (bytes32 requestId) { // LINK is an internal interface for Link token found within the VRFConsumerBase // Here we use the balanceOF method from that interface to make sure that our // contract has enough link so that we can request the VRFCoordinator for randomness require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK"); // Make a request to the VRF coordinator. // requestRandomness is a function within the VRFConsumerBase // it starts the process of randomness generation return requestRandomness(keyHash, fee); } // Function to receive Ether. msg.data must be empty receive() external payable {} // Fallback function is called when msg.data is not empty fallback() external payable {}}
(所有这些数值都是由Chainlink提供给我们的)
/*** startGame starts the game by setting appropriate values for all the variables*/function startGame(uint8 _maxPlayers, uint256 _entryFee) public onlyOwner { // Check if there is a game already running require(!gameStarted, "Game is currently running"); // empty the players array delete players; // set the max players for this game maxPlayers = _maxPlayers; // set the game started to true gameStarted = true; // setup the entryFee for the game entryFee = _entryFee; gameId += 1; emit GameStarted(gameId, maxPlayers, entryFee);}
/**joinGame is called when a player wants to enter the game */function joinGame() public payable { // Check if a game is already running require(gameStarted, "Game has not been started yet"); // Check if the value sent by the user matches the entryFee require(msg.value == entryFee, "Value sent is not equal to entryFee"); // Check if there is still some space left in the game to add another player require(players.length < maxPlayers, "Game is full"); // add the sender to the players list players.push(msg.sender); emit PlayerJoined(gameId, msg.sender); // If the list is full start the winner selection process if(players.length == maxPlayers) { getRandomWinner(); }}
/*** getRandomWinner is called to start the process of selecting a random winner*/function getRandomWinner() private returns (bytes32 requestId) { // LINK is an internal interface for Link token found within the VRFConsumerBase // Here we use the balanceOF method from that interface to make sure that our // contract has enough link so that we can request the VRFCoordinator for randomness require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK"); // Make a request to the VRF coordinator. // requestRandomness is a function within the VRFConsumerBase // it starts the process of randomness generation return requestRandomness(keyHash, fee);}
/** * fulfillRandomness is called by VRFCoordinator when it receives a valid VRF proof. * This function is overrided to act upon the random number generated by Chainlink VRF. * @param requestId this ID is unique for the request we sent to the VRF Coordinator * @param randomness this is a random unit256 generated and returned to us by the VRF Coordinator */ function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual override { // We want out winnerIndex to be in the length from 0 to players.length-1 // For this we mod it with the player.length value uint256 winnerIndex = randomness % players.length; // get the address of the winner from the players array address winner = players[winnerIndex]; // send the ether in the contract to the winner (bool sent,) = winner.call{value: address(this).balance}(""); require(sent, "Failed to send Ether"); // Emit that the game has ended emit GameEnded(gameId, winner,requestId); // set the gameStarted variable to false gameStarted = false; }
npm install dotenv
// Go to https://www.alchemyapi.io, sign up, create // a new App in its dashboard and select the network as Mumbai, and replace "add-the-alchemy-key-url-here" with its key url ALCHEMY_API_KEY_URL="add-the-alchemy-key-url-here" // Replace this private key with your Mumbai account private key // To export your private key from Metamask, open Metamask and // go to Account Details > Export Private Key // Be aware of NEVER putting real Ether into testing accounts MUMBAI_PRIVATE_KEY="add-the-mumbai-private-key-here" // Go to https://polygonscan.com/, sign up, on your account overview page, // click on `API Keys`, add a new API key and copy the // `API Key Token` POLYGONSCAN_KEY="add-the-polygonscan-api-token-here"
require("@nomicfoundation/hardhat-toolbox");require("dotenv").config({ path: ".env" });require("@nomiclabs/hardhat-etherscan");const ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL;const MUMBAI_PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;const POLYGONSCAN_KEY = process.env.POLYGONSCAN_KEY;module.exports = { solidity: "0.8.9", networks: { mumbai: { url: ALCHEMY_API_KEY_URL, accounts: [MUMBAI_PRIVATE_KEY], }, }, etherscan: { apiKey: { polygonMumbai: POLYGONSCAN_KEY, }, },};
const { ethers, BigNumber } = require("hardhat");const LINK_TOKEN = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB";const VRF_COORDINATOR = "0x8C7382F9D8f56b33781fE506E897a4F1e2d17255";const KEY_HASH = "0x6e75b569a01ef56d18cab6a8e71e6600d6ce853834d4a5748b720d06f878b3a4";const FEE = ethers.utils.parseEther("0.0001");module.exports = { LINK_TOKEN, VRF_COORDINATOR, KEY_HASH, FEE };
我们从这里[6]得到的数值,已经由Chainlink提供给我们。
const { ethers } = require("hardhat");require("dotenv").config({ path: ".env" });require("@nomiclabs/hardhat-etherscan");const { FEE, VRF_COORDINATOR, LINK_TOKEN, KEY_HASH } = require("../constants");async function main() { /* A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts, so randomWinnerGame here is a factory for instances of our RandomWinnerGame contract. */ const randomWinnerGame = await ethers.getContractFactory("RandomWinnerGame"); // deploy the contract const deployedRandomWinnerGameContract = await randomWinnerGame.deploy( VRF_COORDINATOR, LINK_TOKEN, KEY_HASH, FEE ); await deployedRandomWinnerGameContract.deployed(); // print the address of the deployed contract console.log( "Verify Contract Address:", deployedRandomWinnerGameContract.address ); console.log("Sleeping....."); // Wait for etherscan to notice that the contract has been deployed await sleep(30000); // Verify the contract after deploying await hre.run("verify:verify", { address: deployedRandomWinnerGameContract.address, constructorArguments: [VRF_COORDINATOR, LINK_TOKEN, KEY_HASH, FEE], });}function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms));}// Call the main function and catch if there is any errormain() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
npx hardhat compile
npx hardhat run scripts/deploy.js --network mumbai
它完成了
你现在知道如何玩这个游戏了。在下一个教程中,我们将为此创建一个用户界面,并将学习如何使用代码本身来跟踪这些事件。
准备开始吧
[1] VRFConsumerBase.sol: https://github.com/smartcontractkit/chainlink/blob/master/contracts/src/v0.8/VRFConsumerBase.sol
[2] Hardhat Verification: https://github.com/LearnWeb3DAO/hardhat-verification
[3] Hardhat Verification[2] : https://github.com/LearnWeb3DAO/hardhat-verification
[4] 这个: https://portal.thirdweb.com/guides/get-matic-on-polygon-mumbai-testnet-faucet
[5] 这里: https://faucet.polygon.technology/
[6] 这里: https://docs.chain.link/docs/vrf-contracts/v1/#polygon-matic-mumbai-testnet
[7] Mumbai Polygon Scan: https://mumbai.polygonscan.com/
[8] Mumbai Polygon Scan[7]: https://mumbai.polygonscan.com/
[9] Polygon Faucet: https://faucet.polygon.technology/
[10] Mumbai Polygon Scan: https://mumbai.polygonscan.com/
[11] Mumbai Polygon Scan[10] : https://mumbai.polygonscan.com/
[12] eth转换器: https://eth-converter.com/
留言与评论(共有 0 条评论) “” |