Platform: Code4rena
Start Date: 17/03/2023
Pot Size: $36,500 USDC
Total HM: 10
Participants: 98
Period: 3 days
Judge: leastwood
Total Solo HM: 5
Id: 223
League: ETH
Rank: 42/98
Findings: 1
Award: $39.87
🌟 Selected for report: 0
🚀 Solo Findings: 0
39.8657 USDC - $39.87
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L163 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L247 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L256-L261
Rather than a random one as designed
function buy(uint256 _amount) external { uint256 startingTrayId = _nextTokenId(); if (prelaunchMinted == type(uint256).max) { // Still in prelaunch phase if (msg.sender != owner) revert OnlyOwnerCanMintPreLaunch(); if (startingTrayId + _amount - 1 > PRE_LAUNCH_MINT_CAP) revert MintExceedsPreLaunchAmount(); } else { SafeTransferLib.safeTransferFrom(note, msg.sender, revenueAddress, _amount * trayPrice); } for (uint256 i; i < _amount; ++i) { TileData[TILES_PER_TRAY] memory trayTiledata; for (uint256 j; j < TILES_PER_TRAY; ++j) { lastHash = keccak256(abi.encode(lastHash)); trayTiledata[j] = _drawing(uint256(lastHash)); } tiles[startingTrayId + i] = trayTiledata; } _mint(msg.sender, _amount); // We do not use _safeMint on purpose here to disallow callbacks and save gas } ... function _drawing(uint256 _seed) private pure returns (TileData memory tileData) { uint256 res = _seed % SUM_ODDS; uint256 charRandValue = Utils.iteratePRNG(_seed); // Iterate PRNG to not have any biasedness / correlation between random numbers
Function buy call _drawing, which request for a random number using Utils.iteratePRNG(_seed)
_seed may be predictable since it is lastHash. If there aren't much volume minting Tray, one can simulate lastHash accurately
function iteratePRNG(uint256 _currState) internal pure returns (uint256 iteratedState) { unchecked { iteratedState = _currState * 15485863; iteratedState = (iteratedState * iteratedState * iteratedState) % 2038074743; } }
iteratePRNG does a very simple arithmetic operation. Obviously easy to predict.
One can also develop a smart contract to mint and revert if tiles he get doesn't contains what he want.
Manual review
#0 - c4-judge
2023-03-28T18:48:10Z
0xleastwood marked the issue as duplicate of #121
#1 - c4-judge
2023-04-11T19:54:19Z
0xleastwood marked the issue as satisfactory
#2 - c4-judge
2023-04-11T20:03:24Z
0xleastwood marked the issue as duplicate of #121
#3 - c4-judge
2023-04-12T00:55:01Z
0xleastwood marked the issue as duplicate of #130
#4 - c4-judge
2023-04-12T00:57:28Z
0xleastwood marked the issue as partial-50