soliditychainlinkfoundry

Chainlink VRF Subscription ID Issue with Foundry Deployment on Sepolia


I'm deploying a smart contract on the Ethereum Sepolia testnet that integrates Chainlink VRF v2.5 using Foundry. However, I encountered an issue where Foundry provided an invalid Subscription ID that does not exist on the Chainlink VRF Subscription Manager.

Here is my Git Repo for this project: https://github.com/kartik01112004/smart-contract-lottery

What I did:

  1. I wrote a Foundry script called CreateSubscription as given in Interactions.s.sol to create a subscription and got the Subscription ID.

  2. I deployed my Raffle contract using Foundry script called DeployRaffle as given in DeployRaffle.s.sol, passing the Subscription ID (that I got in step 1) as one of the constructor params.

  3. After deployment, I checked the Sepolia Chainlink VRF Subscription Manager, but the Subscription ID provided by Foundry (that I got in step 1) does not exist on the VRF Dashboard.

  4. My contract is now deployed with an incorrect Subscription ID, making it non-functional.

The contract relies on subscriptionId in performUpkeep, but since the ID is invalid, the contract fails when calling Chainlink VRF.

Minimal Steps To Reproduce:

  1. Create a Foundry project.

  2. Install the chainlink-brownie-contracts library:

    forge install smartcontractkit/chainlink-brownie-contracts
    
  3. Inside the script folder, create a file named CreateSubscription.s.sol and paste the following content:

    //SPDX-License-Identifier: MIT
    pragma solidity 0.8.26;
    
    import {Script, console} from "forge-std/Script.sol";
    import {VRFCoordinatorV2_5Mock} from
        "lib/chainlink-brownie-contracts/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
    
    contract CreateSubscription is Script {
        /**
         * vrf mock values
         */
        uint96 constant MOCK_BASE_FEE = 0.25 ether;
        uint96 constant MOCK_GAS_PRICE_LINK = 1e9;
        //link eth price
        int256 constant MOCK_WEI_PER_UINT_LINK = 4e15;
    
        address constant DEFAULT_ANVIL_ACCOUNT = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    
        function createSubscription() public {
            console.log("Creating subscription on chainID:", block.chainid);
            vm.startBroadcast(DEFAULT_ANVIL_ACCOUNT);
            uint256 subId =
                new VRFCoordinatorV2_5Mock(MOCK_BASE_FEE, MOCK_GAS_PRICE_LINK, MOCK_WEI_PER_UINT_LINK).createSubscription();
            vm.stopBroadcast();
            console.log("Your Subscription ID is:", subId);
        }
    
        function run() public {
            createSubscription();
        }
    }
    
  4. Run the Foundry Anvil Node.

  5. Execute the script using the following command:

    forge script script/CreateSubscription.s.sol:CreateSubscription --rpc-url http://127.0.0.1:8545 -vvvvv
    

Questions:

  1. Why did Foundry return an invalid Subscription ID?
  2. How can I properly create a Chainlink VRF subscription using Foundry and pass the correct ID to my contract?
  3. Is there a way to update the Subscription ID in an already deployed contract, or do I have to redeploy?
  4. I entered the raffle twice while testing. Is there any way to recover the ETH from the contract?

Would appreciate any insights from anyone who has faced a similar issue!


Solution

  • The reason you're getting an invalid subscription ID is that you're creating the subscription using VRFCoordinatorV2_5Mock, which is designed to create a mock subscription locally on Anvil rather than on-chain on Ethereum Sepolia.

    If you want to create the subscription on-chain, you need to instantiate IVRFCoordinatorV2Plus using the vrfCoordinator address, like:

    IVRFCoordinatorV2Plus(vrfCoordinator).createSubscription();
    

    Here's your revised CreateSubscription contract script:

    //SPDX-License-Identifier: MIT
    pragma solidity 0.8.19;
    
    import {Script, console} from "forge-std/Script.sol";
    import {HelperConfig, CodeConstants} from "script/HelperConfig.s.sol";
    import {VRFCoordinatorV2_5Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
    import {IVRFCoordinatorV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol";
    import {Vm} from "forge-std/Vm.sol";
    import {LinkToken} from "test/mocks/LinkToken.sol";
    import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol";
    
    contract CreateSubscription is Script, CodeConstants {
        function createSubscriptionUsingConfig() public returns (uint256, address) {
            HelperConfig helperConfig = new HelperConfig();
            address vrfCoordinator = helperConfig.getConfig().vrfCoordinator;
            address account = helperConfig.getConfig().account;
            (uint256 subId,) = createSubscription(vrfCoordinator, account);
            return (subId, vrfCoordinator);
        }
    
        function createSubscription(address vrfCoordinator, address account) public returns (uint256, address) {
            console.log("Creating subscription on chainID: ", block.chainid);
    
            if (block.chainid == LOCAL_CHIAIN_ID) {
                vm.startBroadcast(account);
                uint256 subId = VRFCoordinatorV2_5Mock(vrfCoordinator).createSubscription();
                vm.stopBroadcast();
    
                console.log("your subscription id is: ", subId);
                console.log("please update your sub id in HelperConfig.s.sol");
                return (subId, vrfCoordinator);
            } else {
                vm.startBroadcast(account);
                IVRFCoordinatorV2Plus(vrfCoordinator).createSubscription();
                vm.stopBroadcast();
                return (0, vrfCoordinator);
            }
        }
    
        function run() public {
            createSubscriptionUsingConfig();
        }
    }
    

    NOTE: The subscription ID returned by VRFCoordinatorV2_5Mock(vrfCoordinator).createSubscription(); is not necessarily the actual subscription ID created on the VRF Dashboard. This is because the returned value comes from a simulated execution before the transaction is confirmed on-chain. Foundry simulates the return value before the transaction is mined, which can result in a discrepancy between the simulated value and the actual on-chain value.

    Therefore, you must retrieve the actual value either from the transaction logs or directly from the VRF Dashboard.


    As for your questions 3 and 4, there is no way to update the subscription ID in your already deployed Raffle contract. This is because the subscription ID is passed as a constructor argument at deployment, and there is no function to update it. Therefore, you need to redeploy your Raffle contract with a new subscription ID created on Ethereum Sepolia.

    Additionally, there is no way to recover the ETH from the Raffle contract, as the only outgoing ETH transaction occurs during winner selection inside the fulfillRandomWords function.