You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试Chainlink VRF抽奖合约时遇EvmError: Revert问题

使用Foundry测试基于Chainlink VRF的Raffle合约,在验证合约处于CALCULATING状态时阻止用户参与的测试用例中,出现EvmError: Revert报错。

测试输出

Ran 1 test for test/uint/RaffleTest.t.sol:RaffleTest
[FAIL: EvmError: Revert] testDontAllowPlayersToEnterWhenRaffleIsCalculating() (gas: 102379)
Logs:
  please update the subscriptionid in your HelperConfig.s.sol
  uising vrfCoordinator:  0x34A1D3fff3958843C43aD80F30b94c510645C316
  Adding consumer contract  0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76
  To vrfCoordinator  0x34A1D3fff3958843C43aD80F30b94c510645C316

Traces:
  [102379] RaffleTest::testDontAllowPlayersToEnterWhenRaffleIsCalculating()
    ├─ [0] VM::prank(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   └─ ← [Return]
    ├─ [48011] Raffle::enterRaffle{value: 10000000000000000}()
    │   ├─ emit RaffleEntered(player: player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   └─ ← [Stop]
    ├─ [0] VM::warp(32)
    │   └─ ← [Return]
    ├─ [0] VM::roll(2)
    │   └─ ← [Return]
    ├─ [30682] Raffle::performUpkeep(0x)
    │   ├─ [270] VRFCoordinatorV2Mock::requestRandomWords(RandomWordsRequest({ keyHash: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, subId: 1, requestConfirmations: 3, callbackGasLimit: 500000 [5e5], numWords: 1, extraArgs: 0x92fd13380000000000000000000000000000000000000000000000000000000000000000 }))
    │   │   └─ ← [Revert] EvmError: Revert
    │   └─ ← [Revert] EvmError: Revert
    └─ ← [Revert] EvmError: Revert

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 25.56ms (2.02ms CPU time)

Ran 1 test suite in 495.49ms (25.56ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/uint/RaffleTest.t.sol:RaffleTest
[FAIL: EvmError: Revert] testDontAllowPlayersToEnterWhenRaffleIsCalculating() (gas: 102379)

Encountered a total of 1 failing tests, 0 tests succeeded

测试代码

function testDontAllowPlayersToEnterWhenRaffleIsCalculating() public {
        vm.prank(PLAYER);
        raffle.enterRaffle{value: _entranceFee}();

        vm.warp(block.timestamp + _interval + 1);
        vm.roll(block.number + 1);
        raffle.performUpkeep("");

        vm.expectRevert(Raffle.Raffle__RaffleNotOpened.selector);
        vm.prank(PLAYER);
        raffle.enterRaffle{value: _entranceFee}();
    }

部署脚本(DeployRaffle.sol)

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Script} from "forge-std/Script.sol";
import {Raffle} from "src/Raffle.sol";
import {HelperConfig} from "./HelperConfig.s.sol";
import {CreateSubscription, FundSubscription, AddConsumer} from "./interactions.s.sol";

contract DeployRaffle is Script {
    function run() external {
        deployContract();
    }

    function deployContract() public returns (Raffle, HelperConfig) {
        HelperConfig helperConfig = new HelperConfig();
        HelperConfig.NetworkConfig memory config = helperConfig.getConfig();

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

            FundSubscription fundSubscription = new FundSubscription();
            fundSubscription.fundSubscription(config.vrfCoordinator, config.subscriptionId, config.link);
        }

        vm.startBroadcast();
        Raffle raffle = new Raffle(
            config._entranceFee,
            config._interval,
            config.vrfCoordinator,
            config.gasLane,
            config.subscriptionId,
            config.callBackGasData
        );
        vm.stopBroadcast();

        AddConsumer addConsumer = new AddConsumer();
        addConsumer.addConsumer(address(raffle), config.vrfCoordinator, config.subscriptionId);

        return (raffle, helperConfig);
    }
}

交互脚本(interactions.s.sol)

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Script, console} from "forge-std/Script.sol";
import {HelperConfig, CodeConstants} from "./HelperConfig.s.sol";
import {VRFCoordinatorV2Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2Mock.sol";
import {LinkToken} from "../test/mocks/LinkToken.sol";
import {DevOpsTools} from "../lib/foundry-devops/src/DevOpsTools.sol";

contract CreateSubscription is Script {
    function createSubscriptionUsingConfig() public returns (uint256, address) {
        HelperConfig helperConfig = new HelperConfig();
        address vrfCoordinator = helperConfig.getConfig().vrfCoordinator;
        (uint64 subId,) = createSubscription(vrfCoordinator);
        return (subId, vrfCoordinator);
    }

    function createSubscription(address vrfCoordinator) public returns (uint64, address) {
        console.log("creating subscription on chain: ", block.chainid);
        vm.startBroadcast();
        uint64 subId = VRFCoordinatorV2Mock(vrfCoordinator).createSubscription();
        vm.stopBroadcast();

        console.log("your subscription id is: ", subId);
        console.log("please update the subscriptionid in your HelperConfig.s.sol");
        return (subId, vrfCoordinator);
    }

    function run() public {
        createSubscriptionUsingConfig();
    }
}

contract FundSubscription is Script, CodeConstants {
    uint256 public constant FUND_AMOUNT = 3 ether;

    function fundSubscriptionUsingConfig() public {
        HelperConfig helperConfig = new HelperConfig();
        address vrfCoordinator = helperConfig.getConfig().vrfCoordinator;
        uint256 subscriptionId = helperConfig.getConfig().subscriptionId;
        address linkToken = helperConfig.getConfig().link;
        fundSubscription(vrfCoordinator, subscriptionId, linkToken);
    }

    function fundSubscription(address vrfCoordinator, uint256 subscriptionId, address linkToken) public {
        console.log("funding subscription ", subscriptionId);
        console.log("uising vrfCoordinator: ", vrfCoordinator);
        console.log("on Chain: ", block.chainid);

        if (block.chainid == LOCAL_CHAIN_ID) {
            vm.startBroadcast();
            VRFCoordinatorV2Mock(vrfCoordinator).fundSubscription(uint64(subscriptionId), uint96(FUND_AMOUNT));
            vm.stopBroadcast();
        } else {
            vm.startBroadcast();
            LinkToken(linkToken).transferAndCall(vrfCoordinator, FUND_AMOUNT, abi.encode(subscriptionId));
            vm.stopBroadcast();
        }
    }

    function run() public {
        fundSubscriptionUsingConfig();
    }
}

contract AddConsumer is Script {
    function addConsumerUsingConfig(address mostRecentlyDeployed) public {
        HelperConfig helperConfig = new HelperConfig();
        uint256 subId = helperConfig.getConfig().subscriptionId;
        address vrfCoordinator = helperConfig.getConfig().vrfCoordinator;
        addConsumer(mostRecentlyDeployed, vrfCoordinator, subId);
    }

    function addConsumer(address contractToAddToVrf, address vrfCoordinator, uint256 subId) public {
        console.log("Adding consumer contract ", contractToAddToVrf);
        console.log("To vrfCoordinator ", vrfCoordinator);
        console.log("on ChainId: ", block.chainid);

        vm.startBroadcast();
        VRFCoordinatorV2Mock(vrfCoordinator).addConsumer(uint64(subId), contractToAddToVrf);
        vm.stopBroadcast();
    }

    function run() public {
        address mostRecentlyDeployed = DevOpsTools.get_most_recent_deployment("raffle", block.chainid);
        addConsumerUsingConfig(mostRecentlyDeployed);
    }
}

问题分析与修复方案

从Trace日志可知,报错发生在performUpkeep调用VRFCoordinatorV2Mock.requestRandomWords时,核心原因是测试环境中VRF订阅配置不匹配:

问题根源

  1. 日志明确提示please update the subscriptionid in your HelperConfig.s.sol,说明测试时使用的订阅ID与实际创建的订阅ID不一致。
  2. 本地测试链是临时环境,部署脚本创建的订阅不会持久化,测试用例无法复用该配置。

修复步骤

  1. 手动更新HelperConfig的订阅ID:运行CreateSubscription脚本后,将输出的subId写入HelperConfig.s.sol的本地链配置项中。
  2. 在测试用例中初始化VRF订阅:在测试函数开头,调用VRFCoordinatorV2Mock的createSubscription、fundSubscription、addConsumer方法,确保测试环境的订阅配置正确。
  3. 验证performUpkeep执行条件:确认vm.warp和vm.roll的参数正确触发了checkUpkeep的返回条件,确保performUpkeep能进入正常执行流程。

内容的提问来源于stack exchange,提问作者King Lucky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 20:36:00