Lesson 9: testFulfillrandomwordsPicksAWinnerResetsAndSendsMoney
fails with InsufficientBalance()
#2951
-
Hello everyone, I wrote the test function test: function testFulfillrandomwordsPicksAWinnerResetsAndSendsMoney() public raffleEntered {
// Arrange
uint256 additionalEntrances = 3; // so 4 people total since the raffleEntered modifier has PLAYER enter the raffle first.
uint256 startingIndex = 1;
address expectedWinner = address(1);
for (uint256 i = startingIndex; i < startingIndex + additionalEntrances; i++) {
// this is a way to turn any uint into an address. this is like saying address(1)
address newPlayer = address(uint160(i));
// gives newPlayer ether and makes the next transaction come from NewPlayer. `hoax` is a cheatcode that combines vm.deal and vm.prank. Hoax only works with uint160s.
hoax(newPlayer, 1 ether);
// newPlayer enters raffle
raffle.enterRaffle{value: entranceFee}();
}
// get the timestamp of when the contract was deployed
uint256 startingTimeStamp = raffle.getLastTimeStamp();
uint256 winnerStartingBalance = expectedWinner.balance;
// Act
// record all logs(including event data) from the next call
vm.recordLogs();
// call performUpkeep
raffle.performUpkeep("");
// take the recordedLogs from `performUpkeep` and stick them into the entries array
Vm.Log[] memory entries = vm.getRecordedLogs();
// entry 0 is for the VRF coordinator
// entry 1 is for our event data
// topic 0 is always resevered for
// topic 1 is for our indexed parameter
bytes32 requestId = entries[1].topics[1];
// call fulfillRandomWords from the vrfCoordinator and we are inputting the requestId that we got from the logs when we called performUpkeep; and we are also inputting the raffle contract since its the consumer(fulfillRandomWords function takes parameters from the VRFCoordinatorV2_5Mock ).
VRFCoordinatorV2_5Mock(vrfCoordinator).fulfillRandomWords(uint256(requestId), address(raffle)); // we typecast the requestId back into a uint256 since it was stored as bytes
// Assert
// saving the recent winner from the getter function in raffle.sol to a variable named recentWinner
address recentWinner = raffle.getRecentWinner();
// saving the raffleState from the getter function in raffle.sol to a variable type RaffleState named raffleState
Raffle.RaffleState raffleState = raffle.getRaffleState();
// saves the balance of the recentWinner to a variable named winnerBalance
uint256 winnerBalance = recentWinner.balance; // `balance` is a solidity keyword
// fulfillRandomWords updates the timestamp in raffle.sol, so by calling getLastTimeStamp here, we get the new timeStamp that was saved.
uint256 endingTimeStamp = raffle.getLastTimeStamp();
// since there is 4 people in this raffle, it muliplies the number of players times their entrance fee to get how much the prize is worth.
uint256 prize = entranceFee * (additionalEntrances + 1);
// Assert
assert(recentWinner == expectedWinner);
// assert that the raffle restarted
assert(raffleState == Raffle.RaffleState.OPEN);
// assert(uint256(raffleState) == 0); // This is the same as the line above since `OPEN` in index 0 of the enum in Raffle.sol.
// assert that the winners received the funds/prize
assert(winnerBalance == winnerStartingBalance + prize);
assert(endingTimeStamp > startingTimeStamp);
} FundSubscription: contract FundSubscription is Script, CodeConstants {
// this says ether, but it really is (chain)LINK, since there are 18 decimals in the (CHAIN)LINK token as well
uint256 public constant FUND_AMOUNT = 3 ether;
function fundSubscriptionUsingConfig() public {
// deploys a new helperConfig contract so we can interact with it
HelperConfig helperConfig = new HelperConfig();
// calls `getConfig` function from HelperConfig contract, this returns the networkConfigs struct, by but doing `getConfig().vrfCoordinator` it only grabs the vrfCoordinator from the struct. Then we save it as a variable named vrfCoordinator in this contract
address vrfCoordinator = helperConfig.getConfig().vrfCoordinator;
// in our DeployRaffle, we are updating the subscriptionId with the new subscription id we are generating. Here, we call the subscriptionId that we are updating the network configs with(in the deployment script).
uint256 subscriptionId = helperConfig.getConfig().subscriptionId;
// calls the getConfig function from helperConfig and gets the link address and saves it as a variable named linkToken
address linkToken = helperConfig.getConfig().link;
// runs `fundSubscription` function (below) and inputs the following parameters (we just defined these variables in this function)
fundSubscription(vrfCoordinator, subscriptionId, linkToken);
}
function fundSubscription(address vrfCoordinator, uint256 subscriptionId, address linkToken) public {
console.log("Funding subscription: ", subscriptionId);
console.log("Using vrfCoordinator: ", vrfCoordinator);
console.log("On Chain: ", block.chainid);
// if we are on Anvil (local fake blockchain) then deploy a mock and pass it our vrfCoordinator address
if (block.chainid == LOCAL_CHAIN_ID) {
// everything between startBroadcast and stopBroadcast will be broadcasted to the blockchain.
vm.startBroadcast();
// call the fundSubscription function with the subscriptionId and the value amount. This
VRFCoordinatorV2_5Mock(vrfCoordinator).fundSubscription(subscriptionId, FUND_AMOUNT * 100);
vm.stopBroadcast();
} else {
// everything between startBroadcast and stopBroadcast will be broadcasted to the blockchain.
vm.startBroadcast();
// otherwise, if we are on a real blockchain call `transferAndCall` function from the link token contract and pass the vrfCoordinator address, the value amount we are funding it with and encode our subscriptionID so no one else sees it.
LinkToken(linkToken).transferAndCall(vrfCoordinator, FUND_AMOUNT, abi.encode(subscriptionId));
vm.stopBroadcast();
}
}
function run() public {
fundSubscriptionUsingConfig();
}
} DeployRaffle.s.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() public {
deployContract();
}
function deployContract() public returns (Raffle, HelperConfig) {
// deploy a new helpconfig contract that grabs the chainid and networkConfigs
HelperConfig helperConfig = new HelperConfig();
// grab the network configs of the chain we are deploying to and save them as `config`.
// its also the same as doing ` HelperConfig.NetworkConfig memory config = helperConfig.getConfigByChainId(block.chainid);`
HelperConfig.NetworkConfig memory config = helperConfig.getConfig();
// if the subscription id does not exist, create one
if (config.subId == 0) {
// deploys a new CreateSubscription contract from Interactions.s.sol and save it as a variable named createSubscription
CreateSubscription createSubscription = new CreateSubscription();
// calls the createSubscription contract's createSubscription function and passes the vrfCoordinator from the networkConfigs dependent on the chain we are on. This will create a subscription for our vrfCoordinator. Then we save the return values of the subId and vrfCoordinator and vrfCoordinator as the subId and values in our networkConfig.
(config.subId, config.vrfCoordinator) =
createSubscription.createSubscription(config.vrfCoordinator, config.account);
// creates and deploys a new FundSubscription contract from the Interactions.s.sol file.
FundSubscription fundSubscription = new FundSubscription();
// calls the `fundSubscription` function from the FundSubscription contract we just created and pass the parameters that it takes.
fundSubscription.fundSubscription(config.vrfCoordinator, config.subId, config.link, config.account);
}
// everything between startBroadcast and stopBroadcast is broadcasted to a real chain and the account from the helperConfig is the one making the transactions
vm.startBroadcast(config.account);
// create a new raffle contract with the parameters that are in the Raffle's constructor. This HAVE to be in the same order as the constructor!
Raffle raffle = new Raffle(
// we do `config.` before each one because our helperConfig contract grabs the correct config dependent on the chain we are deploying to
config.entranceFee,
config.interval,
config.vrfCoordinator,
config.gasLane,
config.subId,
config.callBackGasLimit
);
vm.stopBroadcast();
// creates and deploys a new AddConsumer contract from the Interactions.s.sol file.
AddConsumer addConsumer = new AddConsumer();
// calls the `addConsumer` function from the `AddConsumer` contract we just created/deplyed and pass the parameters that it takes.
addConsumer.addConsumer(address(raffle), config.vrfCoordinator, config.subId, config.account);
// returns the new raffle and helperconfig that we just defined and deployed so that these new values can be used when this function `deployContracts` is called
return (raffle, helperConfig);
}
} HelperConfig.s.sol: // SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {Script} from "forge-std/Script.sol";
import {Raffle} from "../src/Raffle.sol";
import {VRFCoordinatorV2_5Mock} from
"../lib/chainlink-brownie-contracts/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
import {LinkToken} from "../test/mocks/LinkToken.sol";
abstract contract CodeConstants {
/* VRF Mock Values */
// values that are from chainlinks mock constructor
uint96 public MOCK_BASE_FEE = 0.25 ether; // when we work with chainlink VRF we need to pay a certain amount of link token. The base fee is the flat value we are always going to pay
uint96 public MOCK_GAS_PRICE_LINK = 1e19; // when the vrf responds, it needs gas, so this is the cost of the gas that we spend to cover for it. This calculation is how much link per eth are we going to use?
int256 public MOCK_WEI_PER_UNIT_LINK = 4_15; // link to eth price in wei
// ^ these are just fake values for anvil ^
// chainId for Sepolia
uint256 public constant ETH_SEPOLIA_CHAIN_ID = 11155111;
// chainId for anvil
uint256 public constant LOCAL_CHAIN_ID = 31337;
}
contract HelperConfig is CodeConstants, Script {
error HelperConfig__InvalidChainID();
// these are the items that the constructor in DeployRaffle.s.sol takes
struct NetworkConfig {
uint256 entranceFee;
uint256 interval;
address vrfCoordinator;
bytes32 gasLane;
uint32 callBackGasLimit;
uint256 subId;
address link;
address account;
}
// creating a variable named localNetworkConfig of type struct NetworkConfig
NetworkConfig public localNetworkConfig;
// mapping a chainId to the struct NetworkConfig so that each chainId has its own set of NetworkConfig variables.
mapping(uint256 chainId => NetworkConfig) public networkConfigs;
constructor() {
// mapping the chainId 11155111 to the values in getSepoliaEthConfig
networkConfigs[ETH_SEPOLIA_CHAIN_ID] = getSepoliaEthConfig();
}
function getConfigByChainId(uint256 chainId) public returns (NetworkConfig memory) {
// if the if the vrf.coordinator address does exist on the chain we are on,
if (networkConfigs[chainId].vrfCoordinator != address(0)) {
// then return the all the values in the NetworkConfig struct
return networkConfigs[chainId];
// if we are on the local chain, return the getOrCreateAnvilEthConfig() function
} else if (chainId == LOCAL_CHAIN_ID) {
return getOrCreateAnvilEthConfig();
// otherwise revert with an error
} else {
revert HelperConfig__InvalidChainID();
}
}
// calls getConfigByChainId to grab the chainId of the chain we are deployed on and do the logic in getConfigByChainId
function getConfig() public returns (NetworkConfig memory) {
return getConfigByChainId(block.chainid);
}
// these are the items that are relevant for our raffle constructor if we are on the Sepolia Chain when we deploy.
function getSepoliaEthConfig() public pure returns (NetworkConfig memory) {
return NetworkConfig({
entranceFee: 0.01 ether, // 1e16 // 16 zeros
interval: 30, // 30 seconds
vrfCoordinator: 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B, // got this from the chainlink docs here: https://docs.chain.link/vrf/v2-5/supported-networks
gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, // // got this keyhash from the chainlink docs here: https://docs.chain.link/vrf/v2-5/supported-networks
callBackGasLimit: 500000, // 500,000 gas
subId: 0,
link: 0x779877A7B0D9E8603169DdbD7836e478b4624789, // (chain)LINK token address
account: 0xBe3dDdB70EA16cBfd0cE0A4028902678EECDBe6D
});
}
function getOrCreateAnvilEthConfig() public returns (NetworkConfig memory) {
// if the if the vrf.coordinator address does exist on the anvil chain that we are on,
if (localNetworkConfig.vrfCoordinator != address(0)) {
// then return the all the values in the NetworkConfig struct that is has since it already exists
return localNetworkConfig;
}
// if the if the vrf.coordinator address does NOT exist on the anvil chain that we are on, then deploy a mock vrf.coordinator and deploy a mock linkToken
vm.startBroadcast();
VRFCoordinatorV2_5Mock vrfCoordinatorMock =
new VRFCoordinatorV2_5Mock(MOCK_BASE_FEE, MOCK_GAS_PRICE_LINK, MOCK_WEI_PER_UNIT_LINK);
LinkToken linkToken = new LinkToken();
uint256 subId = vrfCoordinatorMock.createSubscription();
vm.stopBroadcast();
// these are the items that are relevant for our raffle constructor if we are on the Anvil Chain when we deploy.
localNetworkConfig = NetworkConfig({
entranceFee: 0.1 ether, // 1e16 // 16 zeros
interval: 30, // 30 seconds
vrfCoordinator: address(vrfCoordinatorMock), // the address of the vrfCoordinatorMock
gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, // does not matter since this is on anvil
callBackGasLimit: 500000, // 500,000 gas, but it does not matter since this is on anvil
subId: subId,
link: address(linkToken), // (chain)LINK token address
account: 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 // foundry's default sender from lib/forge-std/src/Base.sol
});
vm.deal(localNetworkConfig.account, 100 ether);
// then return the all the values in the NetworkConfig struct when this function is called
return localNetworkConfig;
}
} output of test: Ran 1 test for test/unit/RaffleTest.t.sol:RaffleTest
[FAIL: InsufficientBalance()] testFulfillrandomwordsPicksAWinnerResetsAndSendsMoney() (gas: 369826)
Logs:
Adding consumer contract: 0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3
To vrfCoordinator: 0x34A1D3fff3958843C43aD80F30b94c510645C316
On ChainId: 31337
prevBal: 100000000000000000000
payment: 18040375169461072184580631934
Traces:
[42915204] RaffleTest::setUp()
├─ [26883359] → new DeployRaffle@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 134009 bytes of code
├─ [15650460] DeployRaffle::deployContract()
│ ├─ [4661438] → new HelperConfig@0x104fBc016F4bb334D775a19E8A6510109AC63E00
│ │ └─ ← [Return] 22162 bytes of code
│ ├─ [4111326] HelperConfig::getConfig()
│ │ ├─ [0] VM::startBroadcast()
│ │ │ └─ ← [Return]
│ │ ├─ [2937434] → new VRFCoordinatorV2_5Mock@0x34A1D3fff3958843C43aD80F30b94c510645C316
│ │ │ ├─ emit ConfigSet()
│ │ │ └─ ← [Return] 14320 bytes of code
│ │ ├─ [797693] → new LinkToken@0x90193C961A926261B756D1E5bb255e67ff9498A1
│ │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], amount: 1000000000000000000000000 [1e24])
│ │ │ └─ ← [Return] 3524 bytes of code
│ │ ├─ [120948] VRFCoordinatorV2_5Mock::createSubscription()
│ │ │ ├─ emit SubscriptionCreated(subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], owner: DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
│ │ │ └─ ← [Return] 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75]
│ │ ├─ [0] VM::stopBroadcast()
│ │ │ └─ ← [Return]
│ │ ├─ [0] VM::deal(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], 100000000000000000000 [1e20])
│ │ │ └─ ← [Return]
│ │ └─ ← [Return] NetworkConfig({ entranceFee: 100000000000000000 [1e17], interval: 30, vrfCoordinator: 0x34A1D3fff3958843C43aD80F30b94c510645C316, gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, callBackGasLimit: 500000 [5e5], subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], link: 0x90193C961A926261B756D1E5bb255e67ff9498A1, account: 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 })
│ ├─ [0] VM::startBroadcast(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
│ │ └─ ← [Return]
│ ├─ [861712] → new Raffle@0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3
│ │ └─ ← [Return] 3958 bytes of code
│ ├─ [0] VM::stopBroadcast()
│ │ └─ ← [Return]
│ ├─ [5830223] → new AddConsumer@0x037eDa3aDB1198021A9b2e88C22B464fD38db3f3
│ │ └─ ← [Return] 29005 bytes of code
│ ├─ [73561] AddConsumer::addConsumer(Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3], VRFCoordinatorV2_5Mock: [0x34A1D3fff3958843C43aD80F30b94c510645C316], 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
│ │ ├─ [0] console::log("Adding consumer contract: ", Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3]) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] console::log("To vrfCoordinator: ", VRFCoordinatorV2_5Mock: [0x34A1D3fff3958843C43aD80F30b94c510645C316]) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] console::log("On ChainId: ", 31337 [3.133e4]) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] VM::startBroadcast(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
│ │ │ └─ ← [Return]
│ │ ├─ [67270] VRFCoordinatorV2_5Mock::addConsumer(7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3])
│ │ │ ├─ emit SubscriptionConsumerAdded(subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], consumer: Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3])
│ │ │ └─ ← [Stop]
│ │ ├─ [0] VM::stopBroadcast()
│ │ │ └─ ← [Return]
│ │ └─ ← [Stop]
│ └─ ← [Return] Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3], HelperConfig: [0x104fBc016F4bb334D775a19E8A6510109AC63E00]
├─ [0] VM::deal(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C], 10000000000000000000 [1e19])
│ └─ ← [Return]
├─ [2619] HelperConfig::getConfig()
│ └─ ← [Return] NetworkConfig({ entranceFee: 100000000000000000 [1e17], interval: 30, vrfCoordinator: 0x34A1D3fff3958843C43aD80F30b94c510645C316, gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, callBackGasLimit: 500000 [5e5], subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], link: 0x90193C961A926261B756D1E5bb255e67ff9498A1, account: 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 })
├─ [0] VM::startPrank(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
│ └─ ← [Return]
├─ [2818] LinkToken::mint(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], 100000000000000000000 [1e20])
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], amount: 100000000000000000000 [1e20])
│ └─ ← [Stop]
├─ [45313] VRFCoordinatorV2_5Mock::fundSubscription(7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], 100000000000000000000 [1e20])
│ ├─ emit SubscriptionFunded(subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], oldBalance: 0, newBalance: 100000000000000000000 [1e20])
│ └─ ← [Stop]
├─ [24546] LinkToken::approve(VRFCoordinatorV2_5Mock: [0x34A1D3fff3958843C43aD80F30b94c510645C316], 100000000000000000000 [1e20])
│ ├─ emit Approval(owner: DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], spender: VRFCoordinatorV2_5Mock: [0x34A1D3fff3958843C43aD80F30b94c510645C316], amount: 100000000000000000000 [1e20])
│ └─ ← [Return] true
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]
[369826] RaffleTest::testFulfillrandomwordsPicksAWinnerResetsAndSendsMoney()
├─ [0] VM::prank(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
│ └─ ← [Return]
├─ [47760] Raffle::enterRaffle{value: 100000000000000000}()
│ ├─ emit RaffleEntered(player: player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
│ └─ ← [Stop]
├─ [0] VM::warp(32)
│ └─ ← [Return]
├─ [0] VM::roll(2)
│ └─ ← [Return]
├─ [0] VM::deal(ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::prank(ECRecover: [0x0000000000000000000000000000000000000001])
│ └─ ← [Return]
├─ [23860] Raffle::enterRaffle{value: 100000000000000000}()
│ ├─ emit RaffleEntered(player: ECRecover: [0x0000000000000000000000000000000000000001])
│ └─ ← [Stop]
├─ [0] VM::deal(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::prank(SHA-256: [0x0000000000000000000000000000000000000002])
│ └─ ← [Return]
├─ [23860] Raffle::enterRaffle{value: 100000000000000000}()
│ ├─ emit RaffleEntered(player: SHA-256: [0x0000000000000000000000000000000000000002])
│ └─ ← [Stop]
├─ [0] VM::deal(RIPEMD-160: [0x0000000000000000000000000000000000000003], 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::prank(RIPEMD-160: [0x0000000000000000000000000000000000000003])
│ └─ ← [Return]
├─ [23860] Raffle::enterRaffle{value: 100000000000000000}()
│ ├─ emit RaffleEntered(player: RIPEMD-160: [0x0000000000000000000000000000000000000003])
│ └─ ← [Stop]
├─ [2325] Raffle::getLastTimeStamp() [staticcall]
│ └─ ← [Return] 1
├─ [0] VM::recordLogs()
│ └─ ← [Return]
├─ [144112] Raffle::performUpkeep(0x)
│ ├─ [115690] VRFCoordinatorV2_5Mock::requestRandomWords(RandomWordsRequest({ keyHash: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], requestConfirmations: 3, callbackGasLimit: 500000 [5e5], numWords: 1, extraArgs: 0x92fd13380000000000000000000000000000000000000000000000000000000000000000 }))
│ │ ├─ emit RandomWordsRequested(keyHash: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, requestId: 1, preSeed: 100, subId: 7570848181127581986339189052072122886913734678723205985508750752041200654908 [7.57e75], minimumRequestConfirmations: 3, callbackGasLimit: 500000 [5e5], numWords: 1, extraArgs: 0x92fd13380000000000000000000000000000000000000000000000000000000000000000, sender: Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3])
│ │ └─ ← [Return] 1
│ ├─ emit RequestedRaffleWinner(requestId: 1)
│ └─ ← [Stop]
├─ [0] VM::getRecordedLogs()
│ └─ ← [Return] [([0xeb0e3652e0f44f417695e6e90f2f42c99b65cd7169074c5a654b16b9748c3a4e, 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae, 0x10bcf2be64c261e434a6014656bea8260621c8940cfe977cd11416455f3a1a3c, 0x000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3], 0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000007a120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002492fd1338000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316), ([0xcd6e45c8998311cab7e9d4385596cac867e20a0587194b954fa3a731c93ce78b, 0x0000000000000000000000000000000000000000000000000000000000000001], 0x, 0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3)]
├─ [48080] VRFCoordinatorV2_5Mock::fulfillRandomWords(1, Raffle: [0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3])
│ ├─ [16741] Raffle::rawFulfillRandomWords(1, [78541660797044910968829902406342334108369226379826116161446442989268089806461 [7.854e76]])
│ │ ├─ [3000] ECRecover::fallback{value: 400000000000000000}()
│ │ │ └─ ← [Return]
│ │ ├─ emit WinnerPicked(winner: ECRecover: [0x0000000000000000000000000000000000000001])
│ │ └─ ← [Stop]
│ ├─ [0] console::log("prevBal: ", 100000000000000000000 [1e20]) [staticcall]
│ │ └─ ← [Stop]
│ ├─ [0] console::log("payment: ", 18040375169461072184580631934 [1.804e28]) [staticcall]
│ │ └─ ← [Stop]
│ └─ ← [Revert] InsufficientBalance()
└─ ← [Revert] InsufficientBalance()
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 31.66ms (2.22ms CPU time)
Ran 1 test suite in 920.08ms (31.66ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/unit/RaffleTest.t.sol:RaffleTest
[FAIL: InsufficientBalance()] testFulfillrandomwordsPicksAWinnerResetsAndSendsMoney() (gas: 369826)
Encountered a total of 1 failing tests, 0 tests succeeded Also, can anyone please explain to me why My whole repo can be found here: . The following lines is where the error /**
* @notice fulfillRandomWords fulfills the given request, sending the random words to the supplied
* @notice consumer.
*
* @dev This mock uses a simplified formula for calculating payment amount and gas usage, and does
* @dev not account for all edge cases handled in the real VRF coordinator. When making requests
* @dev against the real coordinator a small amount of additional LINK is required.
*
* @param _requestId the request to fulfill
* @param _consumer the VRF randomness consumer to send the result to
*/
function fulfillRandomWords(uint256 _requestId, address _consumer) external nonReentrant {
fulfillRandomWordsWithOverride(_requestId, _consumer, new uint256[](0));
}
/**
* @notice fulfillRandomWordsWithOverride allows the user to pass in their own random words.
*
* @param _requestId the request to fulfill
* @param _consumer the VRF randomness consumer to send the result to
* @param _words user-provided random words
*/
function fulfillRandomWordsWithOverride(uint256 _requestId, address _consumer, uint256[] memory _words) public {
uint256 startGas = gasleft();
if (s_requests[_requestId].subId == 0) {
revert InvalidRequest();
}
Request memory req = s_requests[_requestId];
if (_words.length == 0) {
_words = new uint256[](req.numWords);
for (uint256 i = 0; i < req.numWords; i++) {
_words[i] = uint256(keccak256(abi.encode(_requestId, i)));
}
} else if (_words.length != req.numWords) {
revert InvalidRandomWords();
}
VRFConsumerBaseV2Plus v;
bytes memory callReq = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, _requestId, _words);
s_config.reentrancyLock = true;
// solhint-disable-next-line avoid-low-level-calls, no-unused-vars
(bool success, ) = _consumer.call{gas: req.callbackGasLimit}(callReq);
s_config.reentrancyLock = false;
bool nativePayment = uint8(req.extraArgs[req.extraArgs.length - 1]) == 1;
uint256 rawPayment = i_base_fee + ((startGas - gasleft()) * i_gas_price);
if (!nativePayment) {
rawPayment = (1e18 * rawPayment) / uint256(i_wei_per_unit_link);
}
uint96 payment = uint96(rawPayment);
_chargePayment(payment, nativePayment, req.subId);
delete (s_requests[_requestId]);
emit RandomWordsFulfilled(_requestId, _requestId, req.subId, payment, nativePayment, success, false);
}
function _chargePayment(uint96 payment, bool nativePayment, uint256 subId) internal {
Subscription storage subcription = s_subscriptions[subId];
if (nativePayment) {
uint96 prevBal = subcription.nativeBalance;
if (prevBal < payment) {
revert InsufficientBalance();
}
subcription.nativeBalance = prevBal - payment;
s_withdrawableNative += payment;
} else {
uint96 prevBal = subcription.balance;
if (prevBal < payment) {
revert InsufficientBalance();
}
subcription.balance = prevBal - payment;
s_withdrawableTokens += payment;
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 18 replies
-
Try removing * 100
|
Beta Was this translation helpful? Give feedback.
-
Hey @SquilliamX the code looks correct to me maybe try running forge clean and re-test. This is my understanding of why Patrick likely determined that the "expected winner" is if (_words.length == 0) {
_words = new uint256[](req.numWords);
for (uint256 i = 0; i < req.numWords; i++) {
_words[i] = uint256(keccak256(abi.encode(_requestId, i)));
}
} Because the mock generates deterministic random numbers, the first random number (_randomWords[0]) will always hash to a predictable value. For example: _words[0] = uint256(keccak256(abi.encode(_requestId, 0))); When this number is used to calculate winnerIndex: winnerIndex = _randomWords[0] % s_players.length; If s_players.length is 4 (one initial player plus three additional entrants), the modulus operation often resolves to 0 due to the determinism of the mock and how _randomWords[0] is generated. As a result: s_players[0] == address(1) Thus, address(1) is chosen as the winner. Hope it makes sense. |
Beta Was this translation helpful? Give feedback.
-
Hello @SquilliamX, Please run your test with the |
Beta Was this translation helpful? Give feedback.
is this a typo?
int256 public MOCK_WEI_PER_UNIT_LINK = 4_15; // link to eth price in wei
If you use 4_15 (415) instead of 4e15, the calculated payment will be scaled down incorrectly by a factor of 10^13, leading to unrealistic results when determining the required LINK amount for transactions.