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: 142/283
Findings: 3
Award: $15.74
π Selected for report: 0
π Solo Findings: 0
π Selected for report: ktg
Also found by: 0xCiphky, 0xDetermination, 0xRiO, 0xWallSecurity, 0xlemon, 0xvj, AlexCzm, BARW, Blank_Space, Draiakoo, FloatingPragma, Giorgio, Matue, McToady, MrPotatoMagic, Silvermist, SpicyMeatball, Tendency, Topmark, Tumelo_Crypto, _eperezok, agadzhalov, ahmedaghadi, alexxander, aslanbek, cats, d3e4, denzi_, dutra, evmboi32, fnanni, givn, handsomegiraffe, haxatron, immeas, juancito, ke1caM, kiqo, klau5, krikolkk, niser93, peter, petro_1912, pkqs90, rspadi, sl1, stakog, visualbits, vnavascues, yotov721
1.876 USDC - $1.88
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L139-L167 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L313-L331 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L484-L531
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( msg.sender, modelURIs[claimIndex], modelTypes[claimIndex], customAttributes[claimIndex] ); claimIndex += 1; } } } if (claimIndex > 0) { emit Claimed(msg.sender, claimIndex); } }
_fighterFarmInstance.mintFromMergingPool
which then passes the inputs to _createNewFighter
function of RankedBattle
contract as follows.function mintFromMergingPool( address to, string calldata modelHash, string calldata modelType, uint256[2] calldata customAttributes ) public { require(msg.sender == _mergingPoolAddress); _createNewFighter( to, uint256(keccak256(abi.encode(msg.sender, fighters.length))), modelHash, modelType, 0, 0, customAttributes ); }
_createNewFighter
function includes a check to determine whether the call is for a custom Fighter or a regular one, based on the first element in customAttributes
. However, this check solely relies on the value 100, which can be accidentally done by users resulting in obtaining a regular Fighter for an inflated price.if (customAttributes[0] == 100) { (element, weight, newDna) = _createFighterBase(dna, fighterType); } else { element = customAttributes[0]; weight = customAttributes[1]; newDna = dna; }
Manual Analysis
Other
#0 - c4-pre-sort
2024-02-24T08:45:23Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T08:45:30Z
raymondfam marked the issue as primary issue
#2 - raymondfam
2024-02-24T08:46:04Z
Intended for argument input at uers' discretion. It's type 1 fighter after all. Low QA
#3 - c4-judge
2024-03-11T10:18:20Z
HickupHH3 marked the issue as satisfactory
#4 - HickupHH3
2024-03-11T10:22:16Z
Main issue is that the custom fighter's element
and weight
can be arbitrarily high when there are boundaries
uint256 element = dna % numElements[generation[fighterType]]; uint256 weight = dna % 31 + 65;
if (customAttributes[0] == 100) { (element, weight, newDna) = _createFighterBase(dna, fighterType); } else { element = customAttributes[0]; weight = customAttributes[1]; newDna = dna; }
#5 - c4-judge
2024-03-11T10:32:26Z
HickupHH3 marked issue #932 as primary and marked this issue as a duplicate of 932
π 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
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L139-L167 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L294-L311
claimRewards
function significantly impacts the experience of new users, as increasing high gas costs during reward claiming.roundId = 0
and will call the claim function after n rounds
function testDosOfClaimRewards() public { _mintFromMergingPool(_ownerAddress); _mintFromMergingPool(_DELEGATED_ADDRESS); _mintFromMergingPool(_DELEGATED_ADDRESS); uint256[] memory _winners = new uint256[](2); _winners[0] = 0; _winners[1] = 1; // winners of roundId 0 are picked _mergingPoolContract.pickWinner(_winners); for (uint i = /* update number of rounds here */; i != 0; ){ uint256[] memory _winnerOfNext100Rounds = new uint256[](2); _winnerOfNext100Rounds[0] = 1; _winnerOfNext100Rounds[1] = 2; _mergingPoolContract.pickWinner(_winnerOfNext100Rounds); unchecked { --i; } } string[] memory _modelURIs = new string[](1); _modelURIs[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; string[] memory _modelTypes = new string[](1); _modelTypes[0] = "original"; uint256[2][] memory _customAttributes = new uint256[2][](1); _customAttributes[0][0] = uint256(1); _customAttributes[0][1] = uint256(80); // winner of roundId 0 claims rewards _mergingPoolContract.claimRewards(_modelURIs, _modelTypes, _customAttributes); }
MergingPool.t.sol
with the command below will show the gas report of the following testforge test --mt testDosOfClaimRewards --gas-report
-> after 100 rounds | src/MergingPool.sol:MergingPool contract | | | | | | |------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 912202 | 4340 | | | | | | Function Name | min | avg | median | max | # calls | | claimRewards | 624524 | 624_524| 624524 | 624524 | 1 | -> after 200 rounds | src/MergingPool.sol:MergingPool contract | | | | | | |------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 912202 | 4340 | | | | | | Function Name | min | avg | median | max | # calls | | claimRewards | 838124 | 838_124| 838124 | 838124 | 1 | -> after 13853 rounds -> At this point the function is unusable and, both the new users after this and the winner of roundId = 0 will be unable to claim reward | src/MergingPool.sol:MergingPool contract | | | | | | |------------------------------------------|-----------------|----------|----------|----------|---------| | Deployment Cost | Deployment Size | | | | | | 912202 | 4340 | | | | | | Function Name | min | avg | median | max | # calls | | claimRewards | 30000932 |30_000_932| 30000932 | 30000932 | 1 |
Manual Review
getUnclaimedRewards
to give the count of unclaimed reward of the user and the claim function can just verify if the input lengths is equal to the number of reward user can claim if that is equal function can proceed with creating new fighter acording to inputs user has given.DoS
#0 - c4-pre-sort
2024-02-23T23:40:34Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T23:40:49Z
raymondfam marked the issue as duplicate of #1541
#2 - c4-judge
2024-03-11T13:00:20Z
HickupHH3 marked the issue as duplicate of #216
#3 - c4-judge
2024-03-11T13:08:05Z
HickupHH3 changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-03-11T13:08:58Z
HickupHH3 marked the issue as partial-50
#5 - c4-judge
2024-03-21T02:06:10Z
HickupHH3 marked the issue as satisfactory
π Selected for report: 0xDetermination
Also found by: 0x11singh99, 0xAnah, 0xRiO, JcFichtner, K42, MatricksDeCoder, McToady, PetarTolev, Raihan, SAQ, SM3_SS, SY_S, Timenov, ahmedaghadi, al88nsk, dharma09, donkicha, emrekocak, favelanky, hunter_w3b, judeabara, kiqo, lrivo, lsaudit, merlinboii, mikesans, offside0011, oualidpro, peter, rekxor, shamsulhaq123, unique, yashgoel72, yovchev_yoan, ziyou-
13.6293 USDC - $13.63
The attributes.length
variable is used throughout the contract and can be initialized as constant. This constant declaration oversight can lead to increased gas costs during contract deployment and function executions. Also array attributes is not changing throught the contract
The cause of the issue is the absence of a constant declaration for the attributes.length
variable. As a result, each reference to attributes.length
incurs gas costs for storage reads, impacting contract efficiency.
uint8 constant public attributesLength = 6;
The redeemMintPass
function contains redundant require statements to validate the lengths of input arrays, leading to inefficient gas usage.
The cause of the issue is the redundant require statements used to validate the lengths of input arrays (mintpassIdsToBurn
, mintPassDnas
, fighterTypes
, modelHashes
, and modelTypes
). These redundant require statements contribute to gas inefficiency.
// Validate lengths of input arrays uint256 length = mintpassIdsToBurn.length; require( length == mintPassDnas.length && length == fighterTypes.length && length == modelHashes.length && length == modelTypes.length, "Array lengths do not match" ); for (uint16 i = 0; i < length; i++) { require(msg.sender == _mintpassInstance.ownerOf(mintpassIdsToBurn[i]), "Sender is not the owner of the mint pass"); _mintpassInstance.burn(mintpassIdsToBurn[i]); _createNewFighter( msg.sender, uint256(keccak256(abi.encode(mintPassDnas[i]))), modelHashes[i], modelTypes[i], fighterTypes[i], iconsTypes[i], [uint256(100), uint256(100)] ); }
!=
instead of <
or >
in loops/conditions will gave gasi>0
and 206 when using i!=0
with increasing iteration this difference can quickly increase resulting in high gas cost for calling functions.function checkingGasOfLoop() public { for (uint256 i =1;i!=0;){ unchecked{ --i; } } }
if (points > 0) {}
Total 22 instance throughout the scope https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L48 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L73 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L99 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L136 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L148 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L178 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L211 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L249 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L124 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L149 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L152 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L176 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L178 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L207 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L131 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L299 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L390 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L469 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L488 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L164 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L245 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L306
numRoundsClaimed[msg.sender]
mapping in RankedBattle::claimNRN function can be made gas efficent.The claimNRN
function contains inefficiencies in updating the numRoundsClaimed
mapping, leading to unnecessary gas consumption.
function claimNRN() external { require(numRoundsClaimed[msg.sender] < roundId, "Already claimed NRNs for this period"); uint256 claimableNRN = 0; uint256 nrnDistribution; uint32 lowerBound = numRoundsClaimed[msg.sender]; for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { nrnDistribution = getNrnDistribution(currentRound); claimableNRN += ( accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution ) / totalAccumulatedPoints[currentRound]; - numRoundsClaimed[msg.sender] += 1; } // Update numRoundsClaimed mapping outside the loop + numRoundsClaimed[msg.sender] = uint32(roundId-1); if (claimableNRN > 0) { amountClaimed[msg.sender] += claimableNRN; _neuronInstance.mint(msg.sender, claimableNRN); emit Claimed(msg.sender, claimableNRN); } }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L299-L305 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L149-L163
#0 - raymondfam
2024-02-25T22:39:05Z
4G
#1 - c4-pre-sort
2024-02-25T22:39:10Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-19T07:43:15Z
HickupHH3 marked the issue as grade-b