Solidity

我進入彩票後,Chainlink Keepers 不維護。你能發現導致這種情況的問題嗎?

  • August 11, 2022

我在這個項目上花了好幾個星期試圖弄清楚為什麼“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-c​​onfig.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 之間的匯率變化等造成的)。所以可能是氣體限制的兩倍或三倍,看看這是否有效。

引用自:https://ethereum.stackexchange.com/questions/133374