51863 sc low lack of winning ticket removal in handlewinnerselection leads to unfair prize distribution and economic exploitation
Submitted on Aug 6th 2025 at 10:13:56 UTC by @vivekd for Attackathon | Plume Network
Report ID: #51863
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Raffle.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The handleWinnerSelection function fails to remove or mark winning tickets after selection, allowing the same ticket number to win multiple times in multi-winner raffles.
This flaw allows a user with a single ticket entry to potentially win all available prizes for a given raffle, breaking the fairness and economic model of the raffle system where prizes should be distributed among different participants.
Vulnerability Details
The vulnerability exists in the winner selection mechanism which doesn't prevent previously-won tickets from winning again:
function handleWinnerSelection(uint256 requestId, uint256[] memory rng) external onlyRole(SUPRA_ROLE) {
uint256 prizeId = pendingVRFRequests[requestId];
// ... validation ...
// Calculate winning ticket - same range every time
uint256 winningTicketIndex = (rng[0] % totalTickets[prizeId]) + 1;
// Binary search to find winner
Range[] storage ranges = prizeRanges[prizeId];
address winnerAddress;
// ... binary search logic ...
// Store winner - but ticket remains in pool
prizeWinners[prizeId].push(Winner({
winnerAddress: winnerAddress,
winningTicketIndex: winningTicketIndex,
drawnAt: block.timestamp,
claimed: false
}));
// Increment counters but don't modify ticket pool
winnersDrawn[prizeId]++;
userWinCount[prizeId][winnerAddress]++;
}The critical issues are:
No Ticket Removal:
totalTickets[prizeId]remains unchanged after each drawNo Ticket Marking: No mechanism tracks which tickets have already won
Static Pool:
prizeRanges[prizeId]array remains unmodified between drawsSame Probability Space: Each draw uses identical ticket range (1 to
totalTickets)
This design flaw means the same ticket index can be selected repeatedly across multiple winner draws.
Impact Details
A user with just one ticket entry could win all prizes:
Example:
10 Nintendo Switch raffle (quantity = 10)
User buys 1 ticket (#500)
If RNG repeatedly selects 500, the user wins all 10 units
Breakdown of Trust:
Users expect fair distribution among participants
If one ticket wins multiple times, credibility is destroyed
This is unfair prize distribution and economic exploitation of the raffle system
References
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Raffle.sol#L237-L284
Proof of Concept
First Winner Draw
1. Admin calls requestWinner(1)
2. VRF returns random number: 7,234,567
3. handleWinnerSelection calculates:
winningTicket = 7234567 % 1000 + 1 = 568
4. Binary search finds: Bob owns ticket 568
5. Result: Bob wins 1st PS5
6. State: winnersDrawn[1] = 1
Important: totalTickets[1] still = 1000
Ticket 568 still owned by BobThird Winner Draw
1. Admin calls requestWinner(1)
2. VRF returns random number: 4,789,567
3. handleWinnerSelection calculates:
winningTicket = 4789567 % 1000 + 1 = 568
4. Binary search finds: Bob owns ticket 568 (SAME TICKET AGAIN!)
5. Result: Bob wins 3rd PS5
6. State: winnersDrawn[1] = 3, prize becomes inactiveFinal Result:
prizeWinners[1] = [
Winner(Bob, 568, timestamp1, false),
Winner(Bob, 568, timestamp2, false),
Winner(Bob, 568, timestamp3, false)
]
Bob's single ticket #568 won all 3 PlayStation 5s!Verification of the Issue:
totalTickets[prizeId]never decreases after winsprizeRanges[prizeId]is never modified after winsNo mapping exists to track used tickets
The same
winningTicketIndexcan appear multiple times inprizeWinners
Was this helpful?