Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Raffle winner and NFT rarity are correlated which can be exploited

Summary

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.

Vulnerability Details

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.

Impact

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.

Tools Used

Manual code inspection.

Recommendations

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.

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Rarity is 1/3 instead of what the docs say

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.