chainlink

Problem with fullfilRandomWords() when using Chainlink's VRFConsumerBaseV2 , Subscription method


My fulfillRewards function isn't either receiving the randomWords from vrf or there's a problem with filtering them, since my getAddressWithMostMints works so it is able to check the nftContract functions

This is my current Raffle code that uses Chainlink VRF (Verifiable Random Function) to select random winners from a collection of NFTs. The contract is built using Solidity and inherits from VRFConsumerBaseV2 and ConfirmedOwner. Here's an overview of its main features:

Interacts with an external NFT contract using the INFTContract interface, allowing it to query the owner of a token and the total supply of the NFTs.

Stores information about the request made to the Chainlink VRF Coordinator in the RequestStatus struct and maintains a mapping of request IDs to their corresponding status.

Allows the contract owner to make a request to the Chainlink VRF Coordinator to generate random numbers.

Determines the address with the most mints using the getAddressWithMostMints function.

The getRandomWinner function, callable only by the contract owner, selects random winners for the raffle in the following steps: a. The function first calls the requestRandomWords function, which sends a request to the Chainlink VRF Coordinator to generate random numbers. b. The actual random words generated by the Chainlink VRF Coordinator are processed in the fulfillRandomWords() function, which is called by the VRF Coordinator after the random numbers are generated. This function checks for duplicates and adds the selected winners to the winners array. c. The address with the most mints is determined by calling getAddressWithMostMints and added to the list of winners. d. The raffle is marked as ended by setting the raffleEnded flag to true.

Allows users to check the status of a Chainlink VRF request by calling the getRequestStatus() function, which returns the fulfillment status and the random words generated for a given request ID.

`\`// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

// Interface for the NFT contract
interface INFTContract {
function totalSupply() external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function balanceOf(address owner) external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
}

contract Raffle is VRFConsumerBaseV2, ConfirmedOwner {

    // Struct to store the status of a request made to the VRFCoordinator contract
    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords; // the random words generated for the request
    }
    
    // Struct to store the address of an owner and the number of NFT tokens they own
    struct AddressMintCount {
        address addr;
        uint256 count;
    }
    
    // Mapping of requestId to RequestStatus
    mapping(uint256 => RequestStatus) public s_requests;
    
    // VRFCoordinator contract instance
    VRFCoordinatorV2Interface COORDINATOR;
    
    // Subscription ID for making requests to the VRFCoordinator contract
    uint64 s_subscriptionId;
    
    // Array of past request IDs
    uint256[] public requestIds;
    
    // ID of the last request made
    uint256 public lastRequestId;
    
    // Key hash for generating random numbers
    bytes32 immutable keyHash;
    
    // Address of the Link token contract
    address public immutable linkToken;
    
    // NFT contract instance
    INFTContract public nftContract;
    
    // Minimum and maximum NFT token IDs for the raffle
    uint256 public minMints;
    uint256 public maxMints;
    
    // Flag indicating whether the raffle has ended or not
    bool public raffleEnded;
    
    // Array of winner addresses
    address[] public winners;
    
    // Fee required to make a request to the VRFCoordinator contract
    uint256 internal fee;
    
    // Gas limit for the callback function to process a request
    uint32 callbackGasLimit = 100000;
    
    // Number of confirmations required for a request to be processed
    uint16 requestConfirmations = 3;
    
    // Number of random words to be generated in a request
    uint32 numWords = 2;
    
    // Address of the VRF Coordinator contract used to generate random numbers
    address vrfCoordinator = 0xbd13f08b8352A3635218ab9418E340c60d6Eb418;
    
    // Address of the Link token contract
    address linkAddress = 0xfaFedb041c0DD4fA2Dc0d87a6B0979Ee6FA7af5F;
    
    // Address of the VRFWrapper contract used to interface with the VRF Coordinator contract
    address wrapperAddress = 0x38336BDaE79747a1d2c4e6C67BBF382244287ca6;
    
    // Event emitted when a request to the VRFCoordinator contract is sent
    event RequestSent(uint256 requestId, uint32 numWords);
    
    // Event emitted when a request to the VRFCoordinator contract is fulfilled
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);
    
    constructor(
        uint64 subscriptionId, 
        address _nftContractAddress
    )
        VRFConsumerBaseV2(vrfCoordinator)
        ConfirmedOwner(msg.sender)
    {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    
        keyHash = 0x121a143066e0f2f08b620784af77cccb35c6242460b4a8ee251b4b416abaebd4;
        linkToken = linkAddress;
    
        nftContract = INFTContract(_nftContractAddress);
    
        // Hardcode minMints and maxMints
        minMints = 1;
        maxMints = 30;
    
        raffleEnded = false;
    }
    
    // Function to check if the raffle is ready to be executed
    function isRaffleReady() public view returns (bool) {
        return nftContract.totalSupply() >= maxMints;
    }
    
    // Function to display all the winners
    function printWinners() public view returns (address[] memory){
        return winners;
    }
    
    // Function to select a random winner using Chainlink VRF
    function getRandomWinner() public onlyOwner returns (address[] memory) {
        uint256 requestId = requestRandomWords();
        
        address winner = getAddressWithMostMints();
        winners.push(winner);
        
        raffleEnded=true;
    
        return winners;
    }
    
    // Function to make a request to the VRFCoordinator contract to generate random words
    function requestRandomWords() public onlyOwner returns (uint256 requestId) {
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

function fulfillRandomWords(
uint256 \_requestId,
uint256\[\] memory \_randomWords
) internal override {
require(s_requests\[\_requestId\].exists, "request not found");
require(!s_requests\[\_requestId\].fulfilled, "request already fulfilled");

        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
    
        for (uint256 i = 0; i < _randomWords.length; i++) {
            uint256 result = (_randomWords[i] % maxMints) + minMints;
            address winner = nftContract.ownerOf(result);
    
            while (isWinnerDuplicate(winner)) {
                result = (_randomWords[i] % maxMints) + minMints;
                winner = nftContract.ownerOf(result);
            }
    
            winners.push(winner);
        }
    
        emit RequestFulfilled(_requestId, _randomWords);
    }
    
    
    // Function to check if a winner has already been selected
    function isWinnerDuplicate(address winner) private view returns (bool) {
        for (uint256 i = 0; i < winners.length; i++) {
            if (winners[i] == winner) {
                return true;
            }
        }
        return false;
    }
    
    // Function to determine the address with the most mints
    function getAddressWithMostMints() public view returns (address) {
        address addrWithMostMints = address(0);
        uint256 mostMints = 0;
    
        uint256 addrMintCountIndex = 0;
        AddressMintCount[200] memory addrMintCounts;
    
        for (uint256 tokenId = minMints; tokenId <= maxMints; tokenId++) {
            address currentOwner = nftContract.ownerOf(tokenId);
    
            bool found = false;
            for (uint256 i = 0; i < addrMintCountIndex; i++) {
                if (addrMintCounts[i].addr == currentOwner) {
                    addrMintCounts[i].count++;
                    found = true;
                    break;
                }
            }
    
            if (!found) {
                AddressMintCount memory newAddressMintCount = AddressMintCount({
                    addr: currentOwner,
                    count: 1
                });
                addrMintCounts[addrMintCountIndex] = newAddressMintCount;
                addrMintCountIndex++;
            }
        }
    
        for (uint256 i = 0; i < addrMintCountIndex; i++) {
            if (addrMintCounts[i].count > mostMints) {
                addrWithMostMints = addrMintCounts[i].addr;
                mostMints = addrMintCounts[i].count;
            }
        }
    
        return addrWithMostMints;
    }
    
    // Function to check the status of a Chainlink VRF request
    function getRequestStatus(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }

}``

Solution

  • Make sure you have the right values for the Chain you are trying to ask for random Words. I'd create set methods for all variables (callbackGasLimit, requestConfirmations, numWords, vrfCoordinator, linkAddress, keyHash) just in case.