Platform: Code4rena
Start Date: 09/02/2024
Pot Size: $60,500 USDC
Total HM: 17
Participants: 283
Period: 12 days
Judge:
Id: 328
League: ETH
Rank: 64/283
Findings: 2
Award: $111.91
π Selected for report: 0
π Solo Findings: 0
π Selected for report: abhishek_thaku_r
Also found by: 0xAlix2, 0xDetermination, 0xShitgem, Draiakoo, Fulum, Greed, MrPotatoMagic, PoeAudits, Tychai0s, ahmedaghadi, alexzoid, dimulski, fnanni, givn, iamandreiski, immeas, kartik_giri_47538, kiqo, klau5, korok, ktg, maxim371, offside0011, pontifex, sashik_eth, stakog, swizz, yotov721
111.676 USDC - $111.68
Users can't re-roll fighters with tokenId
more than type(uint8).max but should be able. It is a medium issue because an important part of the payable functionality of the protocol does not work properly.
Users should be able to re-roll their fighters at least maxRerollsAllowed[fighterType]
times via the FighterFarm.reRoll
for the rerollCost
amount of NRN
tokens:
/// @notice Rolls a new fighter with random traits. /// @param tokenId ID of the fighter being re-rolled. /// @param fighterType The fighter type. function reRoll(uint8 tokenId, uint8 fighterType) public { require(msg.sender == ownerOf(tokenId)); require(numRerolls[tokenId] < maxRerollsAllowed[fighterType]); require(_neuronInstance.balanceOf(msg.sender) >= rerollCost, "Not enough NRN for reroll");
But the max tokenId
which the reRoll
function can accept is type(uint8).max. So fighters with tokenId
more than type(uint8).max can't be re-rolled.
Manual review
Consider using type uint256
instead of uint8
:
- function reRoll(uint8 tokenId, uint8 fighterType) public { + function reRoll(uint256 tokenId, uint8 fighterType) public {
Error
#0 - c4-pre-sort
2024-02-22T02:47:06Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T02:47:12Z
raymondfam marked the issue as duplicate of #68
#2 - c4-judge
2024-03-05T02:01:29Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-05T02:04:51Z
HickupHH3 changed the severity to 3 (High Risk)
π Selected for report: ahmedaghadi
Also found by: 0x13, 0xAleko, 0xDetermination, 0xKowalski, 0xPluto, 0xRiO, 0xvj, AlexCzm, Avci, BARW, BigVeezus, Cryptor, DeFiHackLabs, Draiakoo, Fitro, Giorgio, GoSlang, Greed, Honour, Kalogerone, KmanOfficial, Krace, McToady, MidgarAudits, MrPotatoMagic, Nyxaris, ReadyPlayer2, Ryonen, SovaSlava, SpicyMeatball, VAD37, _eperezok, alexzoid, almurhasan, btk, cu5t0mpeo, deadrxsezzz, djxploit, dvrkzy, emrekocak, erosjohn, evmboi32, fnanni, grearlake, inzinko, jesjupyter, jesusrod15, josephdara, ke1caM, klau5, ktg, ladboy233, merlinboii, nuthan2x, peanuts, pipidu83, pontifex, radev_sw, sl1, sobieski, soliditywala, t0x1c, taner2344, vnavascues, y4y, yovchev_yoan, zaevlad
0.2347 USDC - $0.23
In case the amount of unclaimed rewards for the user exceeds MAX_FIGHTERS_ALLOWED
the user's rewards will be blocked permanently. The user won't be able receives rewards at all.
Users are allowed to claim butch rewards for multiple rounds using the MergingPool.claimRewards
function but they can't specify the number of round or reward to be claimed:
function claimRewards( string[] calldata modelURIs, string[] calldata modelTypes, uint256[2][] calldata customAttributes ) external { uint256 winnersLength; uint32 claimIndex = 0; uint32 lowerBound = numRoundsClaimed[msg.sender]; for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { numRoundsClaimed[msg.sender] += 1; winnersLength = winnerAddresses[currentRound].length; for (uint32 j = 0; j < winnersLength; j++) { if (msg.sender == winnerAddresses[currentRound][j]) { _fighterFarmInstance.mintFromMergingPool(
There is also no function for claiming rewards for a specified round at the contract.
In case the amount of rewards for the user exceeds the MAX_FIGHTERS_ALLOWED
the user won't be able to claim rewards at all because of revert in the FighterFarm._createNewFighter
function:
function mintFromMergingPool( address to, string calldata modelHash, string calldata modelType, uint256[2] calldata customAttributes ) public { require(msg.sender == _mergingPoolAddress); _createNewFighter(
function _createNewFighter( address to, uint256 dna, string memory modelHash, string memory modelType, uint8 fighterType, uint8 iconsType, uint256[2] memory customAttributes ) private { require(balanceOf(to) < MAX_FIGHTERS_ALLOWED);
Even if the user transfers all his fighters to another address it will still be impossible to claim rewards.
It is also inpossible to change the winnerAddresses
mapping for previous rounds because the MergingPool.pickWinner
function allows the admin to pick the winners only for the current round:
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L111-L118
Manual review
Consider checking the acceptable amount of rewards which user can receive and bound the for loop with the amount:
function claimRewards( string[] calldata modelURIs, string[] calldata modelTypes, uint256[2][] calldata customAttributes ) external { uint256 winnersLength; uint32 claimIndex = 0; + uint256 claimable = _fighterFarmInstance.MAX_FIGHTERS_ALLOWED - _fighterFarmInstance.balanceOf(msg.sender); uint32 lowerBound = numRoundsClaimed[msg.sender]; - for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { + for (uint32 currentRound = lowerBound; currentRound < roundId && claimIndex < claimable; currentRound++) { numRoundsClaimed[msg.sender] += 1; winnersLength = winnerAddresses[currentRound].length; for (uint32 j = 0; j < winnersLength; j++) { if (msg.sender == winnerAddresses[currentRound][j]) { _fighterFarmInstance.mintFromMergingPool(
Loop
#0 - c4-pre-sort
2024-02-22T09:32:09Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T09:32:19Z
raymondfam marked the issue as duplicate of #216
#2 - c4-judge
2024-03-11T12:53:35Z
HickupHH3 marked the issue as satisfactory