测试Chainlink VRF抽奖合约时遇EvmError: Revert问题
问题:Foundry测试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订阅配置不匹配:
问题根源
- 日志明确提示
please update the subscriptionid in your HelperConfig.s.sol,说明测试时使用的订阅ID与实际创建的订阅ID不一致。 - 本地测试链是临时环境,部署脚本创建的订阅不会持久化,测试用例无法复用该配置。
修复步骤
- 手动更新HelperConfig的订阅ID:运行
CreateSubscription脚本后,将输出的subId写入HelperConfig.s.sol的本地链配置项中。 - 在测试用例中初始化VRF订阅:在测试函数开头,调用
VRFCoordinatorV2Mock的createSubscription、fundSubscription、addConsumer方法,确保测试环境的订阅配置正确。 - 验证
performUpkeep执行条件:确认vm.warp和vm.roll的参数正确触发了checkUpkeep的返回条件,确保performUpkeep能进入正常执行流程。
内容的提问来源于stack exchange,提问作者King Lucky
相关产品推荐
相关产品推荐

