Summary
The chances for win a given snek NFT in the raffle defined in the Snek NFT Stats are not observed.
Vulnerability Details
The 3 rarity scores are defined as follows:
COMMON_RARITY: public(constant(uint256)) = 70
RARE_RARITY: public(constant(uint256)) = 25
LEGEND_RARITY: public(constant(uint256)) = 5
This means that the chance the participant to win a NFT with COMMON_RARITY is 70%, with RARE_RARITY is 25% and with LEGEND_RARITY is 5%. But the fulfillRandomWords doesn't implement the rarity chances:
def fulfillRandomWords(request_id: uint256, random_words: uint256[MAX_ARRAY_SIZE]):
index_of_winner: uint256 = random_words[0] % len(self.players)
recent_winner: address = self.players[index_of_winner]
self.recent_winner = recent_winner
self.players = []
self.raffle_state = RaffleState.OPEN
self.last_timestamp = block.timestamp
@> rarity: uint256 = random_words[0] % 3
self.tokenIdToRarity[ERC721._total_supply()] = rarity
log WinnerPicked(recent_winner)
ERC721._mint(recent_winner, ERC721._total_supply())
send(recent_winner, self.balance)
The function calculates the rarity in the following way: random_words[0] % 3. As 0 is for the COMMON (Brown Snek), 1 is for RARE (Jungle Snek) and 2 is for LEGEND (Cosmic Snek). This means that the three types NFTs have an equal chance to be won (33.33%).
Impact
The participants in the raffle are misleading about the chance to win the COMMON, RARE and LEGEND NFT.
Tools Used
VS Code, Manual Review
Recommendations
Add the following changes to the fulfillRandomWords function:
def fulfillRandomWords(request_id: uint256, random_words: uint256[MAX_ARRAY_SIZE]):
index_of_winner: uint256 = random_words[0] % len(self.players)
recent_winner: address = self.players[index_of_winner]
self.recent_winner = recent_winner
self.players = []
self.raffle_state = RaffleState.OPEN
self.last_timestamp = block.timestamp
+ rarity: uint256 = random_words[0] % 100;
+ if (rarity < COMMON_RARITY) {
+ self.tokenIdToRarity[ERC721._total_supply()] = COMMON;
+ } else if (rarity < COMMON_RARITY + RARE_RARITY) {
+ self.tokenIdToRarity[ERC721._total_supply()] = RARE;
+ } else {
+ self.tokenIdToRarity[ERC721._total_supply()] = LEGEND;
+ }
- rarity: uint256 = random_words[0] % 3
- self.tokenIdToRarity[ERC721._total_supply()] = rarity
log WinnerPicked(recent_winner)
ERC721._mint(recent_winner, ERC721._total_supply())
send(recent_winner, self.balance)