I was recently made aware of a hack involving a contract where you have a 10% chance to mint a rare NFT.
The hacker was able to see the decision on whether it was a rare or regular and cancel the mint before it happened. This is the code:
uint256 tokenId = totalSupply();
// Getting random number
uint256 rand = getRandom(tokenId);
// Store transaction datas
TransactionData memory transaction = TransactionData(
_minter,
block.timestamp,
_amount
);
transactions.push(transaction);
transactionsForAddress[_minter].push(transaction);
uint256 typeId = getRandomType(rand);
uint256 level = gen1
? typeId == 0 ? _regular : _rare
: 1;
// Store NFT Metadata
nfts.push(NFT(tokenId, typeId, level));
_safeMint(_minter, tokenId);
emit TokenMinted(_minter, tokenId);
}
How is this possible? Is it because the mint is done after the decision in another function?
If so does that mean each function is executed in separate blocks?
I am currently working on one and do not wish to make the same mistake
Edit: Asked for the random functions:
function getRandom(uint256 _seed) internal view returns (uint256) {
return
uint256(
keccak256(
abi.encodePacked(
blockhash(block.number + 4),
tx.origin,
blockhash(block.number + 2),
blockhash(block.number + 3),
blockhash(block.number + 1),
_seed,
block.timestamp
)
)
);
}
function getRandomType(uint256 _seed) internal pure returns (uint256) {
// Generate number between 0 and 9
if (uint256(uint256(keccak256(abi.encode(_seed, 5))) % 10) == 0) {
// 10% luck, 1 = RARE
return 1;
} else {
// 90% luck, 0 = NORMAL
return 0;
}
}
Thank you