smartcontractkit / full-blockchain-solidity-course-js

Learn Blockchain, Solidity, and Full Stack Web3 Development with Javascript
12.25k stars 2.94k forks source link

Lesson 09: Error: VM Exception while processing transaction: reverted with custom error 'InvalidConsumer()' #4870

Closed whoopsoops closed 1 year ago

whoopsoops commented 1 year ago

The solution provided in #4440 can fix the problem, but I have a follow-up question: why does this error happen in the test "doesnt allow entrance when raffle is calculating" only since it doesn't not use vrfCoordinatorV2Mock at all? Why doesn't the test ""emit event on enter" fail?

Discussed in https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/4440

Originally posted by **tibuddh** January 7, 2023 Hi everyone, I'm facing below error in Raffle.test.js ![Screenshot from 2023-01-08 12-52-54](https://user-images.githubusercontent.com/119820485/211181081-876d8166-5843-4d20-99ac-9593e4bed53a.png) How can I fix this error? **Raffle.sol** ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.8; 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"; import "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol"; error Raffle_NotEnoughETHEntered(); error Raffle_TransferFailed(); error Raffle_NotOpen(); error Raffle_UpkeepNotNeeded( uint256 currentbalance, uint256 playersLength, uint256 raffleState ); contract Raffle is VRFConsumerBaseV2, AutomationCompatibleInterface { enum RaffleState { OPEN, CALCULATING } /* State variables */ VRFCoordinatorV2Interface private immutable i_vrfCoordinator; bytes32 private immutable i_gasLane; uint64 private immutable i_subscriptionId; uint32 private immutable i_callbackGasLimit; uint16 private constant REQUEST_CONFIRMATIONS = 3; uint32 private constant NUM_WORDS = 1; uint256 private s_lastTimeStamp; /* Lottery variables */ uint256 private immutable i_entranceFee; address payable[] private s_players; address private s_recentWinner; RaffleState private s_raffleState; event RaffleEnter(address indexed player); event RequestedRaffleWinner(uint256 indexed requestId); event WinnerPicked(address indexed winner); uint256 private immutable i_interval; constructor( address vrfCoordinatorV2, uint64 subscriptionId, bytes32 gasLane, uint256 interval, uint256 entranceFee, uint32 callbackGasLimit ) VRFConsumerBaseV2(vrfCoordinatorV2) { i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2); i_gasLane = gasLane; i_interval = interval; i_subscriptionId = subscriptionId; i_entranceFee = entranceFee; s_raffleState = RaffleState.OPEN; s_lastTimeStamp = block.timestamp; i_callbackGasLimit = callbackGasLimit; } function enterRaffle() public payable { if (msg.value < i_entranceFee) { revert Raffle_NotEnoughETHEntered(); } if (s_raffleState != RaffleState.OPEN) { revert Raffle_NotOpen(); } s_players.push(payable(msg.sender)); emit RaffleEnter(msg.sender); } function checkUpkeep( bytes memory /* checkData */ ) public 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"); } function performUpkeep(bytes calldata /*performData*/) external override { (bool upkeepNeeded, ) = checkUpkeep(""); 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 ); emit RequestedRaffleWinner(requestId); } 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; s_raffleState = RaffleState.OPEN; s_players = new address payable[](0); s_lastTimeStamp = block.timestamp; (bool success, ) = recentWinner.call{value: address(this).balance}(""); if (!success) { revert Raffle_TransferFailed(); } emit WinnerPicked(recentWinner); } /** Getter Functions */ function getRaffleState() public view returns (RaffleState) { return s_raffleState; } function getNumWords() public pure returns (uint256) { return NUM_WORDS; } function getRequestConfirmations() public pure returns (uint256) { return REQUEST_CONFIRMATIONS; } function getRecentWinner() public view returns (address) { return s_recentWinner; } function getPlayer(uint256 index) public view returns (address) { return s_players[index]; } function getLastTimeStamp() public view returns (uint256) { return s_lastTimeStamp; } function getInterval() public view returns (uint256) { return i_interval; } function getEntranceFee() public view returns (uint256) { return i_entranceFee; } function getNumberOfPlayers() public view returns (uint256) { return s_players.length; } } ``` **01-deploy-raffle.js** ```javascript const { network, ethers } = require("hardhat") const { developmentChains, networkConfig, VERIFICATION_BLOCK_CONFIRMATIONS, } = 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, vrfCoordinatorV2Mock if (developmentChains.includes(network.name)) { vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock") vrfCoordinatorV2Address = vrfCoordinatorV2Mock.address const transactionResponse = await vrfCoordinatorV2Mock.createSubscription() const transactionReceipt = await transactionResponse.wait() subscriptionId = transactionReceipt.events[0].args.subId await vrfCoordinatorV2Mock.fundSubscription( subscriptionId, VRF_SUB_FUND_AMOUNT ) // await vrfCoordinatorV2Mock.addConsumer( // subscriptionId, // vrfCoordinatorV2Address // ) } else { vrfCoordinatorV2Address = networkConfig[chainId]["VRFCoordinatorV2"] subscriptionId = networkConfig[chainId]["subscriptionId"] } const waitBlockConfirmations = developmentChains.includes(network.name) ? 1 : VERIFICATION_BLOCK_CONFIRMATIONS log("----------------------------------------------------") const entranceFee = networkConfig[chainId]["entranceFee"] const gasLane = networkConfig[chainId]["gasLane"] const callbackGasLimit = networkConfig[chainId]["callbackGasLimit"] const interval = networkConfig[chainId]["interval"] const args = [ vrfCoordinatorV2Address, subscriptionId, gasLane, interval, entranceFee, callbackGasLimit, ] const raffle = await deploy("Raffle", { from: deployer, args: args, log: true, waitConfirmations: waitBlockConfirmations, }) if ( !developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY ) { log("Verifying...") await verify(raffle.address, [args]) } log("----------------------------------") } module.exports.tags = ["all", "raffle"] ``` **Raffle.test.js** ```javascript const { assert, expect } = require("chai") const { getNamedAccounts, deployments, ethers, network } = require("hardhat") const { developmentChains, networkConfig, } = require("../../helper-hardhat-config") !developmentChains.includes(network.name) ? describe.skip : describe("Raffle Unit Tests", async function () { let raffle, vrfCoordinatorV2Mock, raffleEntranceFee, deployer, interval const chainId = network.config.chainId beforeEach(async function () { deployer = (await getNamedAccounts()).deployer await deployments.fixture(["all"]) raffle = await ethers.getContract("Raffle", deployer) vrfCoordinatorV2Mock = await ethers.getContract( "VRFCoordinatorV2Mock", deployer ) raffleEntranceFee = await raffle.getEntranceFee() interval = await raffle.getInterval() }) describe("constructor", async function () { it("initializes the raffle correctly", async function () { const raffleState = await raffle.getRaffleState() assert.equal(raffleState.toString(), "0") assert.equal( interval.toString(), networkConfig[chainId]["interval"] ) }) }) describe("enterRaffle", async function () { it("reverts when you don't pay enough", async function () { await expect(raffle.enterRaffle()).to.be.revertedWith( "Raffle_NotEnoughETHEntered" ) }) it("records players when they enter", async function () { await raffle.enterRaffle({ value: raffleEntranceFee }) const playerFromContract = await raffle.getPlayer(0) assert.equal(playerFromContract, deployer) }) it("emits event on enter", async function () { await expect( raffle.enterRaffle({ value: raffleEntranceFee }) ).to.emit(raffle, "RaffleEnter") }) it("does not allow entrance when raffle is calculating", async function () { await raffle.enterRaffle({ value: raffleEntranceFee }) await network.provider.send("evm_increaseTime", [ interval.toNumber() + 1, ]) await network.provider.send("evm_mine", []) await raffle.performUpkeep([]) await expect( raffle.enterRaffle({ value: raffleEntranceFee }) ).to.be.revertedWith("Raffle_NotOpen") }) }) }) ``` Much thanks in advance...
alymurtazamemon commented 1 year ago

@whoopsoops This issue is answered in many discussions (here is one #1375), please make sure to first search through discussion tabs and then if you do not find the solution then create a discussion or issue. Thanks.

whoopsoops commented 1 year ago

@alymurtazamemon thanks for your reply. But I did search the discussions. If you noticed, I even quoted one of them. I knew there were solutions provided in other discussions, but no one actually clearly explained how the error happened. Anyway, I figured

alymurtazamemon commented 1 year ago

closing #4870