1

I am going through the Foundry Course by Patrick Collins (@Big Shoutout) and I have just completed the section on creating the Raffle.

I deployed the Raffle successfully on Eth Sepolia and created the VRF Subscription, Chainlink Automation and everything was working great! Happy days.

I then decided I wanted to try and deploy on Arbitrum Sepolia. So I created the Arbitrum helper config for the network:

    function getArbSepEthConfig() public view returns (NetworkConfig memory) {
        return
            NetworkConfig({
                entranceFee: 0.01 ether,
                interval: 30,
                vrfCoordinator: 0x50d47e4142598E3411aA864e08a44284e471AC6f,
                gasLane: 0x027f94ff1465b3525f9fc03e9ff7d6d2c0953482246dd6ae07570c45d6631414,
                subscriptionId: 211,
                callbackGasLimit: 2500000,
                link: 0xb1D4538B4571d411F07960EF2838Ce337FE1E80E,
                deployerKey: vm.envUint("PRIVATE_KEY")
            });
    }

All of the parameters are correct as far as I can see.

Here is the DeployRaffle.s.sol contract that is using the config:

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import {Script, console} from "forge-std/Script.sol";
import {Raffle} from "../src/Raffle.sol";
import {HelperConfig} from "./HelperConfig.s.sol";
import {VRFCoordinatorV2Mock} from "../test/mock/VRFCoordinatorV2Mock.sol";
import {CreateSubscription, FundSubscription, AddConsumer} from "./Interactions.s.sol";

contract DeployRaffle is Script {
    function run() external returns (Raffle, HelperConfig) {
        HelperConfig helperConfig = new HelperConfig();
        (
            uint256 entranceFee,
            uint256 interval,
            address vrfCoordinator,
            bytes32 gasLane,
            uint64 subscriptionId,
            uint32 callbackGasLimit,
            address link,
            uint256 deployerKey
        ) = helperConfig.activeNetworkConfig();

        if (subscriptionId == 0) {
            CreateSubscription createSubscription = new CreateSubscription();
            subscriptionId = createSubscription.createSubscription(
                vrfCoordinator,
                deployerKey
            );

            FundSubscription fundSubscription = new FundSubscription();
            fundSubscription.fundSubscription(
                vrfCoordinator,
                link,
                subscriptionId,
                deployerKey
            );
        }
        AddConsumer addComsumer = new AddConsumer();

        if (block.chainid == 31337) {
            vm.startBroadcast();
        } else {
            vm.startBroadcast(deployerKey);
            console.log("Deploying Raffle on ChainId: ", block.chainid);
        }

        Raffle raffle = new Raffle(
            entranceFee,
            interval,
            vrfCoordinator,
            gasLane,
            subscriptionId,
            callbackGasLimit
        );

        vm.stopBroadcast();
        addComsumer.addConsumer(
            vrfCoordinator,
            subscriptionId,
            address(raffle),
            deployerKey
        );
        return (raffle, helperConfig);
    }
}

And finally, here is the Raffle.sol that I have deployed:

// Layout of Contract:
// version
// imports
// errors
// interfaces, libraries, contracts
// Type declarations
// State variables
// Events
// Modifiers
// Functions

// Layout of Functions:
// constructor
// receive function (if exists)
// fallback function (if exists)
// external
// public
// internal
// private
// internal & private view & pure functions
// external & public view & pure functions

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";

/**
 * @title Raffle
 * @author Charlie Mack
 * @notice This contract is used for a simple raffle
 * @dev Implements Chainlink VRF v2
 */
contract Raffle is VRFConsumerBaseV2 {
    /** Errors */

    error Raffel__NotEnoughETHSent();
    error Raffel__TransferFailed();
    error Raffle__RaffleNotOpen();
    error Raffle__RaffleUpkeepNotNeeded(
        uint256 balance,
        uint256 playersLength,
        RaffleState raffleState,
        uint256 timeSinceLastRaffle
    );

    /** Interfaces */

    VRFCoordinatorV2Interface private immutable i_vrfCoordinator;

    /** Type Declarations */

    enum RaffleState {
        OPEN,
        CALCULATING
    }

    /** State Variables */

    uint16 private constant REQUEST_CONFIRMATIONS = 3;
    uint32 private constant NUM_WORDS = 1;

    uint256 private immutable i_entranceFee;
    uint256 private immutable i_interval;
    bytes32 private immutable i_gasLane;
    uint64 private immutable i_subscriptionId;
    uint32 private immutable i_callbackGasLimit;

    address payable[] private s_players;
    uint256 private s_lastTimeStamp;
    address private s_recentWinner;
    RaffleState private s_raffleState;

    /** Events */

    event EnteredRaffle(address indexed player);
    event PickedWinner(address indexed winner);
    event RequestedRaffleWinner(uint256 indexed requestId);

    constructor(
        uint256 entranceFee,
        uint256 interval,
        address vrfCoordinator,
        bytes32 gasLane,
        uint64 subscriptionId,
        uint32 callbackGasLimit
    ) VRFConsumerBaseV2(vrfCoordinator) {
        i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinator);
        i_entranceFee = entranceFee;
        i_interval = interval;
        s_lastTimeStamp = block.timestamp;
        i_gasLane = gasLane;
        i_subscriptionId = subscriptionId;
        i_callbackGasLimit = callbackGasLimit;
        s_raffleState = RaffleState.OPEN;
    }

    function enterRaffle() external payable {
        if (s_raffleState != RaffleState.OPEN) {
            revert Raffle__RaffleNotOpen();
        }

        if (msg.value < i_entranceFee) {
            revert Raffel__NotEnoughETHSent();
        }
        s_players.push(payable(msg.sender));
        emit EnteredRaffle(msg.sender);
    }

    /**
     * @dev This is the function that the Chainlink Automation nodes call
     * to see if it's time to perform an upkeep.
     * The following should be true for this to return true:
     * 1. The raffle is in the OPEN state
     * 2. The time since the last raffle is greater than the interval
     * 3. The contract has ETH (aka, players)
     * 4. (Implicit) The subscription is funded with LINK
     */
    function checkUpkeep(
        bytes memory /*checkData*/
    ) public view returns (bool upkeepNeeded, bytes memory /* performData */) {
        bool timeHasPassed = (block.timestamp - s_lastTimeStamp) > i_interval;
        bool isOpen = RaffleState.OPEN == s_raffleState;
        bool hasBalance = address(this).balance > 0;
        bool hasPlayers = s_players.length > 0;
        upkeepNeeded = timeHasPassed && isOpen && hasBalance && hasPlayers;
        return (upkeepNeeded, "0x0");
    }

    function performUpkeep(bytes calldata /* performData */) external {
        (bool upkeepNeeded, ) = checkUpkeep("");
        if (!upkeepNeeded) {
            revert Raffle__RaffleUpkeepNotNeeded(
                address(this).balance,
                s_players.length,
                s_raffleState,
                block.timestamp - s_lastTimeStamp
            );
        }
        s_raffleState = RaffleState.CALCULATING;

        //1 Request the RNG
        //2 Get the random Number
        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 winner = s_players[indexOfWinner];
        s_recentWinner = winner;

        //reset the raffle
        s_raffleState = RaffleState.OPEN;
        s_players = new address payable[](0);
        s_lastTimeStamp = block.timestamp;
        emit PickedWinner(winner);

        (bool success, ) = winner.call{value: address(this).balance}("");
        if (!success) {
            revert Raffel__TransferFailed();
        }
    }

    /** Getter Functions */

    function getEntranceFee() public view returns (uint256) {
        return i_entranceFee;
    }

    function getInterval() public view returns (uint256) {
        return i_interval;
    }

    function getGasLane() public view returns (bytes32) {
        return i_gasLane;
    }

    function getSubscriptionId() public view returns (uint64) {
        return i_subscriptionId;
    }

    function getCallbackGasLimit() public view returns (uint32) {
        return i_callbackGasLimit;
    }

    function getPlayers() public view returns (address payable[] memory) {
        return s_players;
    }

    function getPlayer(uint256 index) public view returns (address payable) {
        return s_players[index];
    }

    function getLastTimeStamp() public view returns (uint256) {
        return s_lastTimeStamp;
    }

    function getRecentWinner() public view returns (address) {
        return s_recentWinner;
    }

    function getRaffleState() public view returns (RaffleState) {
        return s_raffleState;
    }
}

As you can see I seem to be using the correct vrf coordinator address as stated in the chainlink docs.

The contract was deployed here: 0xd4624f3A28905523AA30609052a20767f434DA28

When I try to call the performUpkeep function, I get the following error:

   ├─ [0] console::log("mostRecentlyDeployed: ", 0xd4624f3A28905523AA30609052a20767f434DA28) [staticcall]
    │   └─ ← ()
    ├─ [0] VM::startBroadcast()
    │   └─ ← ()
    ├─ [8797746687693881515] 0xd4624f3A28905523AA30609052a20767f434DA28::performUpkeep(0x)
    │   ├─ [8797746687693851289] VRFCoordinatorV2::requestRandomWords(0x027f94ff1465b3525f9fc03e9ff7d6d2c0953482246dd6ae07570c45d6631414, 211, 3, 500000 [5e5], 1)
    │   │   ├─ [8797746687693840135] 0x0000000000000000000000000000000000000064::arbBlockNumber() [staticcall]
    │   │   │   └─ ← EvmError: InvalidFEOpcode
    │   │   └─ ← EvmError: Revert
    │   └─ ← EvmError: Revert
    └─ ← EvmError: Revert

Does anyone have an idea on what is going on here? Help is much appreciated :)

1
  • Hey Charlie, I looked at your contract on arb sep blockexplorer [1] but couldnt see anything there to help trouble shoot. You could also use tenderly.co to trace your Tx hash and see what happened. I did some digging around and found this: docs.arbitrum.io/for-devs/… perhaps that will help? [1]sepolia.arbiscan.io/address/…
    – ZeusLawyer
    Commented Mar 18 at 5:23

0

Your Answer

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