I'm doing the blockchain course by Patrick Collins on FreeCodeCamp and I'm really enjoying it. Unfortunately I got stuck at my unit test in lesson 9 and after reading a lot of discussions on Github, I cannot come to a solution.

Can somebody please help me? :)

The issue is that I'm unable to successfully call the performUpkeep() function on my Raffle.sol smart contract.

I'm using:

I have a test file where I manipulate the time and block mined and call the performUpkeep() function as follows:

describe("enterRaffle", async () => {
  it("should not allow entrance when the raffle state is set to calculating", async () => {
    await raffle.enterRaffle({ value: raffleEntranceFee })
    const increasedInterval = Number(interval) + 10
    // manipulate time on evm
    await network.provider.send("evm_increaseTime", [increasedInterval])
    await network.provider.send("evm_mine", []);
    // calling chainlink keeper
    const zeroBytes = new Uint8Array()
    await raffle.performUpkeep(zeroBytes) // revert on this line
    await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.be.revertedWithCustomError(raffle, "Raffle__NotOpen")

raffle.performUpkeep(zeroBytes) reverts with the following error:

Error: Transaction reverted: function returned an unexpected amount of data

Interval is coming from my hardhat-helper-config.ts file that looks something like this:

    "31337": {
        name: "hardhat",
        entranceFee: ethers.parseEther("0.01"),
        gasLane: "0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c", // doesn't matter on hh
        subscriptionId: "0",
        callbackGasLimit: "500000",
        interval: 30

My 01-deploy-raffle.ts file looks like this:

const deployRaffle: DeployFunction = async ({ getNamedAccounts, deployments, network }) => {
  const { deploy, log } = deployments;
  const { deployer, player } = await getNamedAccounts();
  const chainId: number = network.config.chainId!;

  let vrfCoordinatorV2Adress: (string | Addressable) | (string | undefined),
    subscriptionId: number | string | undefined,

  if (developmentChains.includes(network.name)) {
    vrfCoordinatorV2Mock = await ethers.getContractAt("VRFCoordinatorV2Mock", deployer);

    vrfCoordinatorV2Adress = vrfCoordinatorV2Mock.target;

    // create a subscription programmatically
    const transactionResponse: ContractTransactionResponse = await vrfCoordinatorV2Mock.createSubscription();
    const transactionReceipt: ContractTransactionReceipt = (await transactionResponse.wait()) as ContractTransactionReceipt;

    // NOT WORKING => transactionReceipt.events[0].args.subId;
    subscriptionId = 1;

    // fund the subscription
    await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, FUND_AMOUNT);

  } else {
    vrfCoordinatorV2Adress = networkConfig[chainId].vrfCoordinatorV2;
    subscriptionId = networkConfig[chainId].subscriptionId;

  // constructer arguments
  const args = [
  const deployOptions = {
    from: deployer,
    args: args,
    log: true,
    waitConfirmations: 1,
  const raffle: DeployResult = await deploy("Raffle", deployOptions);
  if (!developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) {
    log("[deploy script] verifying contract...");
    await verify(raffle.address, args);
export default deployRaffle;
deployRaffle.tags = ["all", "raffle"];

Note: I had some trouble earlier getting the subscriptionId(i.e. subId) from the tx logs so I opted to hardcode the subId to the anticipated value instead. For some reason transactionReceipt.events[0] does not exist on the ContractTransactionReceipt nor does transactionReceipt.logs[0].args in my local hardhat setup.

My Raffle.sol solidity contract looks something like this:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";

// * errors
error Raffle__NotOpen();
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);

contract Raffle is VRFConsumerBaseV2, AutomationCompatible {
    // * type declarations
    enum RaffleState {
    // * state variables
    uint256 private immutable i_entranceFee;
    address payable[] private s_players;
    VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
    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;
    uint256 private s_lastTimestamp;
    uint256 private immutable i_interval;
    // * lottery variables
    RaffleState private s_raffleState;

    // * functions
        address vrfCoordinatorV2, //contract
        uint256 entranceFee,
        bytes32 gasLane,
        uint64 subscriptionId,
        uint32 callbackGasLimit,
        uint256 interval
    ) VRFConsumerBaseV2(vrfCoordinatorV2) {
        i_entranceFee = entranceFee;
        i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
        i_gasLane = gasLane;
        i_subscriptionId = subscriptionId;
        i_callbackGasLimit = callbackGasLimit;
        s_raffleState = RaffleState.OPEN;
        s_lastTimestamp = block.timestamp;
        i_interval = interval;

    function checkUpkeep(
        bytes memory /* checkData */
    ) public view override returns (bool upkeepNeeded, bytes memory /* performData */) {
        bool isOpen = (RaffleState.OPEN == s_raffleState);
        // (block.timestamp - lastBlockTimestamp) > interval
        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 && hasPlayers);
        return (upkeepNeeded, "0x0");

    function performUpkeep(bytes calldata) external override /* performData */ {
        // check if upkeep is needed
        (bool upkeepNeeded, ) = checkUpkeep("");
        if (!upkeepNeeded) {
            revert Raffle__UpkeepNotNeeded(
        // request random number
        // revert if subscription is not set and funded.
        s_raffleState = RaffleState.CALCULATING;
        uint256 requestId = i_vrfCoordinator.requestRandomWords(
            i_gasLane, // gasLane
        // once we get it, do something with it
        emit RequestedRaffleWinner(requestId);

    // * view, pure functions
    function getInterval() public view returns (uint256) {
        return i_interval;

performUpkeep() seems to revert when attempting to call requestRandomWords(). Is it because I don't have a valid subscriptionId from ChainLink Keepers because I'm working locally with hardhat?


1 Answer 1


Update: I was not able to fix my issue with ethers@v6.

I found out that my subscription simply wasn't created.

For some reason I'm unable to successfully create a subscription using this piece of code:

await vrfCoordinatorV2Mock.createSubscription()

With ethers@v5 I was able to create the subscription but got another error:

reverted with custom error 'InvalidConsumer()'

For everybody reading this: Make sure to add your smart contract as a consumer at the bottom of your 01-deploy-raffle.ts script, right after you deploy the contract.

Like this:

    const deployOptions = {
        from: deployer,
        args: args,
        log: true,
        waitConfirmations: 1,

    const raffle: DeployResult = await deploy("Raffle", deployOptions);

    if(developmentChains.includes(network.name)) {
        console.log("adding raffle contract as consumer");
        await vrfCoordinatorV2Mock?.addConsumer(subscriptionId.toNumber(), raffle.address)
        console.log("consumer added!");

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.