我進入彩票後,Chainlink Keepers 不維護。你能發現導致這種情況的問題嗎?
我在這個項目上花了好幾個星期試圖弄清楚為什麼“Chainlink Keepers”不起作用。我可以在 Rinkeby 上成功部署和驗證合約。然後我執行以下操作… 1.) 成功資助並註冊消費者契約 (VFR) 2.) 成功資助並註冊 Keepers 並看到我的聯繫人列為有效維護 3.) 我轉到“寫契約”選項卡在 etherscan 上並通過 enterLottery 函式輸入我自己的彩票。tx 成功並在 Etherscan 上可見。
現在,在我進入這個彩票之後,應該設置守門員,在經過一定數量的 block.timestamps 後,它將選擇彩票的中獎者並支付中獎者。即使只有一名玩家,也將選擇獲勝者。不幸的是,守門員不是“保養”,我在歷史上沒有看到任何保養。任何精通契約開發和 ChainLink VRF 和 keppers 的人都可以發現問題所在。契約和部署腳本都在下面。非常感謝您提供的任何幫助….
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol"; //Goals for our lotttery contract... //We want users to be able to enter the lottery (paying some fee to enter) //Users can pick a random number (verifiably random) //Winner to be selected every X minutes or at some frequency we decide on //We want everything to be completely automated, not requiring manual maintenance //Must use a chainlink oracle = Randomness (automated execution) Chainlink keepers error Raffle_NotEnoughETHEntered(); error Raffle_TransferFailed(); error Raffle_NotOpen(); error Raffle_UpkeepNotNeeded( uint256 currentBalance, uint256 numPlayers, uint256 raffleState ); /** @title A sample Lottery Contract * @author AroundTheBlock7 * @notice This contract is for creating an untamperable decentralized smart contract * @dev This implements Chainlink VRF v2 and Chainlink Keepers */ contract LotteryVRF is VRFConsumerBaseV2, KeeperCompatibleInterface { enum RaffleState { OPEN, //0 CALCULATING //1 } //State Variables VRFCoordinatorV2Interface private immutable i_vrfCoordinator; uint256 public immutable i_entranceFee; address payable[] public s_players; bytes32 private immutable i_gasLane; uint64 private immutable i_subscriptionId; uint16 private constant REQUEST_CONFIRMATIONS = 3; uint32 private immutable i_callbackGasLimit; uint32 private constant NUM_WORDS = 1; //Lottery variables address private s_recentWinner; RaffleState private s_raffleState; uint256 private s_lastTimeStamp; uint256 private immutable i_interval; event LotteryEnter(address indexed player); event RequestedLotteryWinner(uint256 indexed requestId); event WinnerPicked(address indexed winner); constructor( address vrfCoordinatorV2, uint256 entranceFee, bytes32 gasLane, uint64 subscriptionId, uint32 callbackGasLimit, uint256 interval ) VRFConsumerBaseV2(vrfCoordinatorV2) { i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2); i_entranceFee = entranceFee; i_gasLane = gasLane; i_subscriptionId = subscriptionId; i_callbackGasLimit = callbackGasLimit; s_raffleState = RaffleState.OPEN; s_lastTimeStamp = block.timestamp; i_interval = interval; } function enterLottery() public payable { //require (msg.value > i_entranceFee, "Not enough ETH!") Instead of this, we can use the if and revert which is cheaper if (msg.value < i_entranceFee) { revert Raffle_NotEnoughETHEntered(); } if (s_raffleState != RaffleState.OPEN) { revert Raffle_NotOpen(); } s_players.push(payable(msg.sender)); emit LotteryEnter(msg.sender); } //This is the function that the Chainlink Keeper Nodes call. It is run offchain! //They look for the `upkeepNeeded' to return true. //The following should be true in order to return true.... //1.) Our time interval should have passed //2.) The lottery should have at least 1 player and have some ETH //3.) Our subscription is funded with LINK //4.) The lottery should be in an "open" state. //The input bytes memory checkData allows us to input any data we want. We wont need for this so we can take out checkData //We also don't need performData in the return statement which woulc allow us to do other stuff. We want bool upkeepNeeded! 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 = (isOpen && timePassed && hasPlayers && hasBalance); return (upkeepNeeded, "0x0"); } //This function interacts interacts VRFConsumerBaseV2 Contract/Oracle. //If checkUpKeep returns true, than the node automatically calls performUpkeep here. //When this is executed by the node it returns a random number & triggers the fulfillRandomWords //Initally we called this function "requestRandomWinner", but after we incorporated keepers and wrote... //...the "checkUpkeep" function we renamed this function to performUpkeep and gave it proper inputs (bytes calldata performData) function performUpkeep(bytes calldata /* performData */) external override { //Remember upkeepNeeded and performData were the 2 things returned to us in checkUpkeep function above //We pass in upkeepNeeded here but we do not need performData so leave that out (bool upkeepNeeded, ) = checkUpkeep(""); if (!upkeepNeeded) { revert Raffle_UpkeepNotNeeded( address(this).balance, s_players.length, uint256(s_raffleState) ); } //We want to make sure we set the RaffleState to calculating so to avoid new entries while we pick a winner s_raffleState = RaffleState.CALCULATING; uint256 requestId = i_vrfCoordinator.requestRandomWords( i_gasLane, i_subscriptionId, REQUEST_CONFIRMATIONS, i_callbackGasLimit, NUM_WORDS ); emit RequestedLotteryWinner(requestId); } //This function is filled by the VRFCoordinator via the VRFCoordinatorV2Interface. //This is automatically called after the requestRandomWinner/performUpkeep function is triggered. The VRFCoordinator fills the request here. function fulfillRandomWords( uint256, /*requestId */ uint256[] memory randomWords ) internal override { uint256 indexOfWinner = randomWords[0] % s_players.length; address payable recentWinner = s_players[indexOfWinner]; s_recentWinner = recentWinner; //After we pick the winner we want to set the RaffleState to open again and reset the players array! s_raffleState = RaffleState.OPEN; s_players = new address payable[](0); //resets the array //We also want to reset the timestamp here so to keep things running smooth with the interval and picking next winner s_lastTimeStamp = block.timestamp; (bool success, ) = recentWinner.call{value: address(this).balance}(""); //require(success, etc.) chepaer way to do it... if (!success) { revert Raffle_TransferFailed(); } emit WinnerPicked(recentWinner); } function getEntranceFee() public view returns (uint256) { return i_entranceFee; } function getPlayer(uint256 index) public view returns (address) { return s_players[index]; } function getRecentWinner() public view returns (address) { return s_recentWinner; } function getRaffleState() public view returns (RaffleState) { return s_raffleState; } //When retreiving constant variables in storage we can use "pure" instead of "view" for visibility function getNumWords() public pure returns (uint256) { return NUM_WORDS; } function getNumberOfPlayers() public view returns (uint256) { return s_players.length; } function getLatestTimeStamp() public view returns (uint256) { return s_lastTimeStamp; } //Again, can use "pure" instead of "view" when returning constants function getRequestConfirmations() public pure returns (uint256) { return REQUEST_CONFIRMATIONS; } }
這是部署腳本。我也有一個模擬契約和模擬部署腳本,但是由於我正在部署到 Rinkeby,所以我沒有將它們包括在內,因為它們僅適用於通過安全帽進行的本地部署。
const { network, ethers } = require("hardhat") const { developmentChains, networkConfig } = require("../helper-hardhat-config") const { verify } = require("../utils/verify") const VRF_SUB_FUND_AMOUNT = ethers.utils.parseEther("30") module.exports = async function ({ getNamedAccounts, deployments }) { const { deploy, log } = deployments const { deployer } = await getNamedAccounts() const chainId = network.config.chainId let vrfCoordinatorV2Address, subscriptionId if (developmentChains.includes(network.name)) { const vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock") vrfCoordinatorV2Address = vrfCoordinatorV2Mock.address const transactionResponse = await vrfCoordinatorV2Mock.createSubscription() const transactionReceipt = await transactionResponse.wait(1) subscriptionId = transactionReceipt.events[0].args.subId //Fund the subscription //Usually you'd need the link token on a real network await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, VRF_SUB_FUND_AMOUNT) } else { vrfCoordinatorV2Address = networkConfig[chainId]["vrfCoordinatorV2"] subscriptionId = networkConfig[chainId]["subscriptionId"] } const entranceFee = networkConfig[chainId]["entranceFee"] const gasLane = networkConfig[chainId]["gasLane"] const callbackGasLimit = networkConfig[chainId]["callbackGasLimit"] const interval = networkConfig[chainId]["interval"] const args = [vrfCoordinatorV2Address, entranceFee, gasLane, subscriptionId, callbackGasLimit, interval] const lotteryVRF = await deploy("LotteryVRF", { from: deployer, args: args, log: true, waitConfirmations: network.config.blockConfirmations || 1, }) if (!developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) { log("Verifying...") await verify(lotteryVRF.address, args) } log("--------------------------------------------") } module.exports.tags = ["all", "lotteryVRF"]
這是 helper-hardhat-config.js 文件…
const { ethers } = require("hardhat") //Note all these below are being derived from our constructor. These are the inputs needed upon deployment. const networkConfig = { 4: { name: "rinkeby", vrfCoordinatorV2: "0x6168499c0cFfCaCD319c818142124B7A15E857ab", entranceFee: ethers.utils.parseEther("0.01"), gasLane: "0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc", subscriptionId: "0", callbackGasLimit: "500000", // 500,000 interval: "30", }, //We don't need to put the vrfCoordinator below because we are using our mock on hardhat //For gasLane we can put the same as above or any filler as we'll be using the mock so it won't matter 31337: { name: "hardhat", entranceFee: ethers.utils.parseEther("0.01"), gasLane: "0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc", subscriptionId: "0", callbackGasLimit: "500000", // 500,000 interval: "30", }, } const developmentChains = ["hardhat", "localhost"] module.exports = { networkConfig, developmentChains, }
你能分享你在rinkeby上部署合約的地址嗎?
PerformUpkeep Tx 似乎已失敗並已恢復(請參見此處:https ://rinkeby.etherscan.io/address/0x4b3499aac5A0f96E2DFB6e31c28f7fd92DFA3a31 )。如果您點擊左側的 tx 雜湊,它將打開更詳細的視圖。
Tx 似乎來自你的錢包——你是在打電話給 performUpkeep 嗎?這就是 Tx 似乎表明的(https://rinkeby.etherscan.io/tx/0xaf66dec83393f27f4112c6d1f0fdee094d564f253ef7a6ba57b0d6663a6368e3)
Rinkeby 最近有點“不穩定”,這可能是部分原因。我也看到了一些問題。
TX 可能失敗的另一個原因是 gaslimit 太低(這可能是由於 LINK 和 ETH 之間的匯率變化等造成的)。所以可能是氣體限制的兩倍或三倍,看看這是否有效。