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)