F-2025-0014·missing-failure-handler

Missing VRF Failure Handling Leads to Permanent Round Lock and Fund Freezing

Fixedrafflelotteryvrf
TL;DR

Once vrfRequested is set to true, there is no way to recover if Chainlink VRF never delivers, leaving the round permanently stuck and funds locked.

Severity
MEDIUM
Impact
HIGH
Likelihood
LOW
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

Description

When a lottery round reaches maxTickets, the contract requests randomness from Chainlink VRF to select a winner. However, the implementation lacks any timeout mechanism or manual resolution path if the VRF callback never arrives.

solidity
function _requestRandomWinner(uint256 productId, uint256 roundId) private {
Round storage round = rounds[productId][roundId];
require(!round.vrfRequested, "VRF already requested");
uint256 requestId = vrfCoordinator.requestRandomWords(...);
vrfRequestToProduct[requestId] = productId;
vrfRequestToRound[requestId] = roundId;
round.vrfRequested = true;
round.vrfRequestId = requestId;
emit VRFRequested(requestId, productId, roundId);
}

Once round.vrfRequested is set to true, there is no way to:

  • Re-request randomness if VRF fails
  • Manually complete the round
  • Refund users if the round cannot complete
  • Start a new round for that product

Vulnerable Scenario:

  • A PREMIUM product round reaches 1000 tickets sold (maxTickets), triggering VRF request.
  • The Chainlink VRF coordinator experiences downtime, or the subscription runs out of LINK tokens.
  • The VRF callback never executes, leaving the round permanently stuck with round.vrfRequested = true and round.completed = false.
  • Line 327 prevents any re-request: require(!round.vrfRequested, "VRF already requested").
  • All funds from that round (1000 tickets × 20 USDC = 20,000 USDC) are locked in the contract.
  • Users cannot participate in new rounds for that product since currentRound[productId] never increments.
  • No admin function exists to resolve this stuck state.

This is not a theoretical concern. Chainlink VRF has experienced outages, and subscription management issues are common operational risks.

03Section · Impact

Impact

VRF callback failures result in permanent freezing of round funds and complete inability to continue lottery operations for the affected product. Users lose their ticket purchases with no recovery mechanism, and the product becomes permanently inoperable.

04Section · Recommendation

Recommendation

Implement a timeout-based permissionless fallback mechanism to handle VRF failures without relying on owner privileges:

  1. Track when VRF requests are made:
solidity
mapping(uint256 => mapping(uint256 => uint256)) public roundVRFRequestTime;
function _requestRandomWinner(uint256 productId, uint256 roundId) private {
// ... existing code ...
roundVRFRequestTime[productId][roundId] = block.timestamp;
}
  1. Add a permissionless emergency resolution function:
solidity
function resolveStuckRound(uint256 productId, uint256 roundId) external {
Round storage round = rounds[productId][roundId];
require(round.vrfRequested && !round.completed, "Round not stuck");
require(
block.timestamp > roundVRFRequestTime[productId][roundId] + 7 days,
"VRF timeout not reached"
);
// Use block-based fallback randomness
uint256 fallbackRandom = uint256(
keccak256(abi.encodePacked(
block.timestamp,
block.prevrandao,
block.number,
productId,
roundId
))
);
uint256 winningTicket = fallbackRandom % round.ticketsSold;
address winner = ticketOwner[productId][roundId][winningTicket];
round.vrfRandomWord = fallbackRandom;
round.winner = winner;
round.completed = true;
_distributeRound(productId, roundId, winner, fallbackRandom);
emit RoundCompleted(productId, roundId, winner, products[productId].jackpotUSD, winningTicket);
_startNewRound(productId);
}
05Section · Resolution

Resolution

Nexalo: Fixed.

Zealynx: Verified. Fixed.

Status
Fixed
F-2025-0014

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx