fulfillRandomWords may revert, causing loss of funds and DoS in rewards distribution
fulfillRandomWords performs reward distribution that can revert on a single blacklisted recipient or insufficient balance, permanently locking the round per Chainlink VRF semantics.
Description
The function fulfillRandomWords is only callable by chainlink VRF to select a raffle winner when all tickets of a product have been bought. Because it can't be called manually, and because it handles distribution of funds, the function execution must never revert, or the contract will remain in a state where the product's round can't change, and funds will never be distributed.
Here are the possible reasons why the function may revert:
- The contract has less funds than the total to distribute (unlikely, as funds are drawn when users are paying for tickets)
- Any address to which the tokens are supposed to be sent is blacklisted (low likelyhood, this rarely happens)
This link refers to Chainlink's official documentation regarding reverts using VRF: https://docs.chain.link/vrf/v2-5/security#fulfillrandomwords-must-not-revert
Vulnerable Scenario:
The following steps help understand the issue:
- Users buy all tickets for the raffle
- The contract calls Chainlink VRF to select a winner
- One of the user who should receive stablecoins from the raffle is blacklisted
- When
NexumManagerreceives the VRF response, it immediately executes the rewards calculations and transfers - Because a receiver is blacklisted,
fulfillRandomWordsreverts fulfillRandomWordscannot be called with the same requestId again- The raffle product's round cannot increase, so we can't keep on using it for the raffles, and users can't receive rewards
Impact
- In the worst case scenario where the randomness can not be fulfilled, the raffle for this product will stop working and the rewards will remain stuck in the contract
NexumManager. - It is important that
fulfillRandomWordsand every functions it calls must never revert, as the contract will enter a state that cannot be fixed then: the distribution will not occur, resulting in loss of funds, and the raffle for this product will permanently be deactivated (DoS).
Recommendation
-
Ensure the function can not revert under any condition (remove all
requireinfulfillRandomWordsand called functions, and ensure external calls cannot revert). -
Consider implementing a pull-base functionnality for reward distributions, so users will pull the funds themselves. For example:
function _newRewards(address user, uint256 amount) private {pendingRewards[user] += amount;}function claimRewards(address user, uint256 amount) external {pendingRewards[user] -= amount;_safeTransfer(user, amount);}
Resolution
Nexalo: Fixed.
Zealynx: Verified. Fixed.

