The index of the winning user and the rarity of the NFT are both derived from the same random number. This leads to an uneven distribution of NFTs depending on the location of the winner in the queue of raffle entrants. This can be used to gain an unfair advantage in obtaining rare or legendary NFTs.
The winner of the raffle is picked in fulfillRandomWords()
as a randomly drawn index of the entries:
index_of_winner: uint256 = random_words[0] % len(self.players)
The rarity of the NFT is picked in the same function a few lines further down:
rarity: uint256 = random_words[0] % 3
Both random drawings reference the same random number source random_words[0]
. This leads to a correlation between the index of the winner and the rarity of the NFT. This will have a significant effect on raffles with a small number of users.
For example, if there are three players, there are only three possible outcomes (with equal probability):
If random_words[0]%3
is 0: The winner will be user at index 0, and they will be awarded a COMMON NFT (rarity 0).
If random_words[0]%3
is 1: The winner will be user at index 1, and they will be awarded a RARE NFT (rarity 1).
If random_words[0]%3
is 2: The winner will be user at index 2, and they will be awarded a LEGENDARY NFT (rarity 2).
Sneaky user S
can exploit this to their advantage as follows: If there are currently two players in the raffle, S
can enter the raffle in third position and then immediately call request_raffle_winner()
to end the raffle. While the chance to win is still 33.3% for each player, if S
ends up winning, they are guaranteed a LEGENDARY NFT.
The order in which players entered the contest should not influence the rarity of the NFT when they win.
High: While protocol funds are not at risk from this incorrect implementation, the raffle mechanism (rarity of the winner NFT) can be gamed; therefore, its incorrect implementation is a severe disruption to protocol functionality.
Manual code inspection.
Request 2 random values from the Chainlink VRF provider by changing the constants MAX_ARRAY_SIZE
and NUM_WORDS
from 1
to 2
and use random_words[0]
for the index of the winner and random_words[1]
for the rarity calculation.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.