1

i am writing a raffle game contract, I integrated Chainlink keepers to check how much time has passed and pick the winner to move on to next round.

    function checkUpkeep(
        bytes calldata 
    )
        external
        view
        override
        returns (bool upkeepNeeded, bytes memory)
    {
        upkeepNeeded = (!isPaused() && !awaitingRandomness && block.timestamp>=raffle[round.current()].endTime);
    }

    function performUpkeep(bytes calldata) external override {
       
        if (!isPaused() && !awaitingRandomness && block.timestamp>=raffle[round.current()].endTime) {
            pickWinner();
        }
    }

Then, to get a secure random value I integrated with VRF. When the pickWinner() function is called, It calls the requestRandomness function then just leaves the pickWinner() function.

function pickWinner() internal {
  
        if(raffle[round.current()].playerCount == 0){
            finishRound(address(0));
        } else if(raffle[round.current()].playerCount == 1){
            token.transfer(address(raffle[round.current()].players[0]), calculatePrize()); 
            token.transfer(address(treasuryAddress), getBalance());
            finishRound(address(raffle[round.current()].players[0]));
        } else{
               awaitingRandomness=true;
               uint256 requestId = requestRandomness(callbackGasLimit,requestConfirmations,numWords);

               randomnessIds[round.current()] = requestId;
               raffle[round.current()].status = GameStatus.PICKING_WINNER;
        }
        
    }

After this point, everything stops until fulfillRandomWords() function of VRFV2WrapperConsumerBase is called back. Inside that function, I get the randomWord and use it as index for array of addresses that I keep for the current raffle round and chooses the winner. Then, starts the next round.

function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {

        uint256 randomIndex = randomWords[0] % raffle[round.current()].players.length;
        address winner = raffle[round.current()].players[randomIndex];

        Game storage currentGameOfPlayer = players[winner][round.current()];
        currentGameOfPlayer.status = PlayerStatus.WIN;
        currentGameOfPlayer.earned = calculatePrize();
        currentGameOfPlayer.updatedAt = block.timestamp;
         
        raffle[round.current()].status = GameStatus.FINISHED;
        raffle[round.current()].winner = winner;
        token.transfer(winner, calculatePrize()); 
        token.transfer(address(treasuryAddress), getBalance());
        
        finishRound(winner);
    }

My problem is that fulfillRandomWords() function gets stuck because of gas limit. Also, even if it wasn't stuck, since all this automated logic is dependent on fulfillRandomWords() function being called, I am not sure if it is usable.

My question is:

-Is VRF service reliable enough to make this logic dependent on fulfillRandomWords() being called?

-Do you have any suggestions or examples for implementing this game? Maybe using another service or a different logic?

2 Answers 2

3

As a disclaimer, I am working at Gelato, which is Chainlink Keepers's competitor with Gelato Automate.

It is true that the callback gas limit can be very restrictive depending on your business logic.

In this case, I would decouple the fullFillRandomess() from the rest of the execution creating another Keeper that checks whether the random number has been delivered.

Something like this:

  • Automate/Keeper to launch VRF
  • VRF call back (update flag--> VRF available)
  • Automate/keeper to update winners, etc once VRF is available

I don't know how CL Keepers can create two Keepers in the same contract. Maybe someone else in the community can help us with that. Here you can find the Gelato Automate docs, where you can see that using the same contract as a resolver you can create n-keepers in the same contract: https://docs.gelato.network/developer-services/automate

And here is also an article on how to integrate Gelato with different VRF providers. https://medium.com/coinmonks/schedule-randomness-with-gelato-and-witnet-api3-chainlink-vrf-1ebc0aac37d

As in your use case, you have to decouple the Vrf fulfillment from the rest of the execution, you could also have a look into the part related to Witnet.

Hope it helps and you can build your raffle!

2
  • Hi donoso, thank you very much for your reply. I have also thought about using the keeper to make another check. I believe I can simply use "OR" logic in keeper functions with my current implementation and use IF conditions to make it work with CL Keeper. Gelato seems give a better control. I will look into it in more detail. However, I have another concern here: Would it create any vulnerabilities to NOT choose the winner in the fullFillRandomWords method? The winner would be public when the random word is returned and stored in contract but winner will be chosen when keeper funct is called. Apr 17, 2023 at 20:47
  • It depends on your custom logic, probably to avoid vulnerability, you could finalize the raffle by fullfillrandomess() or split the logic between the fullfillrandomness() and the keeper so the callback gas is lower.
    – donoso.eth
    Apr 18, 2023 at 7:23
1

If function fulfillmentRandomWords get stucked, it is because the gas used in the function is more than the callback gaslimit you set for VRF. Increasing the callback gaslimit can solve the problem. Please make sure the gas limit you set is less than the max callback gaslimit which can be found in variable Config smart contracts VRFCoordinator(2.5M in eth mainnet) in different networks.

But in some cases, when your business logic is too big to be handled in the function fulfillmentRandomWords, eg. gas used is more than the max callback gaslimit, it is a good practice to decouple the function fulfillmentRandomWords as suggested by @dodoso.eth.

  • automation 1 to send a VRF request.
  • VRF fulfills randomness and saves it in an array.
  • automation 2 to pick a winner based on VRF returned by VRF in the last step.

In your case, you can use Chainlink Automation to pick the winner based on the Randomness returned by VRF. Based on the workflow, Chainlink VRF only receives your request and fulfills the randomWords to your smart contract so that you will not have gaslimit problem. The winner is picked by Chainlink automation in the function performUpkeep rather than in the fulfillRandomWords.

It is also possible to create multiple Upkeeps for one smart contract. There is a parameter called performData in the function performUpkeep which can be used to flag calls from different upkeeps.

you can find an example and sample codes here.

Hope it helps!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.