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: 54/283
Findings: 5
Award: $122.75
π Selected for report: 0
π Solo Findings: 0
π Selected for report: klau5
Also found by: 0xAleko, 0xAlix2, 0xAsen, 0xCiphky, 0xKowalski, 0xlemon, 0xvj, 14si2o_Flint, Aamir, AlexCzm, Aymen0909, BARW, Blank_Space, DanielArmstrong, Davide, Draiakoo, Giorgio, McToady, MrPotatoMagic, PoeAudits, Ryonen, Silvermist, SpicyMeatball, Tychai0s, VAD37, Varun_05, alexxander, alexzoid, aslanbek, blutorque, btk, cats, d3e4, denzi_, evmboi32, fnanni, givn, grearlake, haxatron, jesjupyter, juancito, ke1caM, ktg, lanrebayode77, linmiaomiao, matejdb, merlinboii, n0kto, novamanbg, nuthan2x, petro_1912, pynschon, radin100, sashik_eth, shaka, sl1, soliditywala, solmaxis69, t0x1c, ubl4nk, vnavascues, xchen1130, yotov721, zhaojohnson
1.1225 USDC - $1.12
This issue allows users to bypass re-roll limits for fighters by exploiting the absence of checks for fighterType
in the re-roll function. This could lead to unfair advantages, imbalance in gameplay, and potential exploitation where certain fighters could be re-rolled more times than intended.
In FighterFarm.sol:370
, the reRoll
function lacks verification for fighterType
, meaning it does not properly enforce re-roll limitations across different types of fighters. As numRerolls[tokenId]
is checked against maxRerollsAllowed[fighterType]
, without validating if the fighterType
corresponds to the tokenId
's original type, users can potentially re-roll a fighter more times than allowed by simply specifying a different fighterType
with a higher re-roll limit. This oversight can lead to gameplay imbalance and exploitation.
function reRoll(uint8 tokenId, uint8 fighterType) public { // Potential to bypass re-roll limits due to lack of fighterType checks require(msg.sender == ownerOf(tokenId)); require(numRerolls[tokenId] < maxRerollsAllowed[fighterType]); ... }
Manual Review
Implement a validation mechanism within the reRoll function to ensure that the fighterType specified matches the fighterType of the tokenId being re-rolled. This could involve mapping each tokenId to its fighterType and checking this mapping within the function. Such a measure would prevent users from bypassing re-roll limits and maintain fair gameplay and balance.
Invalid Validation
Invalid Validation
#0 - c4-pre-sort
2024-02-22T01:37:59Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T01:38:39Z
raymondfam marked the issue as duplicate of #306
#2 - c4-judge
2024-03-05T04:33:08Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-19T09:05:07Z
HickupHH3 changed the severity to 3 (High Risk)
π Selected for report: klau5
Also found by: 0xAleko, 0xAlix2, 0xAsen, 0xCiphky, 0xKowalski, 0xlemon, 0xvj, 14si2o_Flint, Aamir, AlexCzm, Aymen0909, BARW, Blank_Space, DanielArmstrong, Davide, Draiakoo, Giorgio, McToady, MrPotatoMagic, PoeAudits, Ryonen, Silvermist, SpicyMeatball, Tychai0s, VAD37, Varun_05, alexxander, alexzoid, aslanbek, blutorque, btk, cats, d3e4, denzi_, evmboi32, fnanni, givn, grearlake, haxatron, jesjupyter, juancito, ke1caM, ktg, lanrebayode77, linmiaomiao, matejdb, merlinboii, n0kto, novamanbg, nuthan2x, petro_1912, pynschon, radin100, sashik_eth, shaka, sl1, soliditywala, solmaxis69, t0x1c, ubl4nk, vnavascues, xchen1130, yotov721, zhaojohnson
1.1225 USDC - $1.12
Judge has assessed an item in Issue #1214 as 3 risk. The relevant finding follows:
Reroll Function Vulnerability in FighterFarm Contract Due to Missing FighterType Check
#0 - c4-judge
2024-03-05T05:05:52Z
HickupHH3 marked the issue as duplicate of #306
#1 - c4-judge
2024-03-05T05:05:56Z
HickupHH3 marked the issue as partial-50
π Selected for report: t0x1c
Also found by: 0rpse, 0xAadi, 0xBinChook, 0xCiphky, 0xDetermination, 14si2o_Flint, AC000123, Aamir, Abdessamed, Blank_Space, CodeWasp, DanielArmstrong, DarkTower, Draiakoo, Honour, Kalogerone, Krace, McToady, Merulez99, MidgarAudits, MrPotatoMagic, PedroZurdo, Silvermist, Tychai0s, VAD37, Velislav4o, VrONTg, WoolCentaur, YouCrossTheLineAlfie, ZanyBonzy, alexxander, aslanbek, btk, csanuragjain, d3e4, dimulski, djxploit, erosjohn, evmboi32, fnanni, forgebyola, forkforkdog, handsomegiraffe, immeas, israeladelaja, juancito, ktg, n0kto, neocrao, ni8mare, okolicodes, peanuts, petro_1912, shaflow2, shaka, swizz, ubermensch, ubl4nk, yotov721
1.5445 USDC - $1.54
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L342 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L439
This vulnerability allows users to stake NRN tokens in a manner that fulfills the requirements for earning points without actually risking any stake. Users can exploit this by manipulating the stake calculation to always round down to zero, thus never transferring any stake to the StakeAtRisk contract, while still accruing points to increase their chances of earning new fighters.
In RankedBattle.sol:439, the method _addResultPoints can be exploited due to the way curStakeAtRisk is calculated. By staking an amount that makes curStakeAtRisk round down to 0 due to integer division, a user can avoid having any stake moved to the StakeAtRisk contract. This exploit is facilitated by the formula curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10 ** 4, where precise staking amounts can ensure curStakeAtRisk results in 0 after division. Consequently, the user can allocate all points to the mergingPool, increasing their chances of earning new fighters without any associated risk.
// RankedBattle.t.sol function testUserCanGetStakingBenefitsByStakingDust() public { address staker = vm.addr(3); uint256 bpsPerLoss = _rankedBattleContract.bpsLostPerLoss(); uint256 neuronToStake = 10 ** 4 / bpsPerLoss - 1; _mintFromMergingPool(staker); _fundUserWithNeuronByTreasury(staker, neuronToStake); vm.prank(staker); _rankedBattleContract.stakeNRN(neuronToStake, 0); vm.startPrank(_GAME_SERVER_ADDRESS); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); // staker lost his match, but StakeAtRisk took 0 Neuron uint256 initialStakeAtRisk = _stakeAtRiskContract.getStakeAtRisk(0); assertEq(initialStakeAtRisk, 0); // staker enjoys a nice streak of wins, setting all of his points toward the merginPool (100) _rankedBattleContract.updateBattleRecord(0, 100, 0, 1500, false); _rankedBattleContract.updateBattleRecord(0, 100, 0, 1500, false); uint256 pointsAfterWins = _mergingPoolContract.fighterPoints(0); assertGt(pointsAfterWins, 0); // Even if the user just keeps losing, no points nor stake will be at risk of being lost _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, false); uint256 updatedStakeAtRisk = _stakeAtRiskContract.getStakeAtRisk(0); assertEq(updatedStakeAtRisk, 0); uint256 pointsAfterLoses = _mergingPoolContract.fighterPoints(0); assertGt(pointsAfterLoses, 0); _rankedBattleContract.updateBattleRecord(0, 100, 0, 1500, false); // Win again assertGt(_mergingPoolContract.fighterPoints(0), pointsAfterWins); vm.stopPrank(); }
Manual Review
Implement a minimum stake requirement that ensures curStakeAtRisk cannot be rounded down to 0, potentially by setting a minimum threshold for bpsLostPerLoss. Consider using a more precise arithmetic library or techniques to prevent rounding errors that could be exploited.
Math
Math
#0 - c4-pre-sort
2024-02-22T16:57:29Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T16:57:38Z
raymondfam marked the issue as duplicate of #38
#2 - c4-judge
2024-03-07T02:49:49Z
HickupHH3 changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-03-07T02:58:22Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-07T03:21:34Z
HickupHH3 marked the issue as partial-75
π 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
This design flaw restricts the re-rolling feature to only the first 256 fighters due to the tokenId
parameter being defined as a uint8
instead of a uint256
. This severely limits the game's scalability and the players' ability to engage with the re-roll mechanic for fighters beyond the initial 256.
In FighterFarm.sol:370
, the reRoll
function is implemented with a tokenId
parameter of type uint8
. This choice of data type inadvertently restricts the function to operate only on the first 256 tokens because uint8
can only represent values from 0 to 255. As a result, any attempt to re-roll fighters with tokenId
values beyond 255 will fail due to this type limitation.
function reRoll(uint8 tokenId, uint8 fighterType) public { // Only the first 256 fighters can be re-rolled due to uint8 limitation require(msg.sender == ownerOf(tokenId)); ... }
Manual Review
To rectify this limitation, it is recommended to change the data type of the tokenId parameter in the reRoll function from uint8 to uint256. This change will align the function with the ERC-721 standard's expectation for token identifiers and ensure that all tokens, regardless of their number, can be re-rolled as intended.
Invalid Validation
Invalid Validation
#0 - c4-pre-sort
2024-02-22T01:37:06Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T01:37:13Z
raymondfam marked the issue as duplicate of #68
#2 - c4-judge
2024-03-05T01:58:07Z
HickupHH3 marked the issue as satisfactory
π Selected for report: haxatron
Also found by: 0xAlix2, 0xCiphky, 0xStriker, 0xaghas, 0xbranded, 0xlamide, 0xmystery, 0xvj, 14si2o_Flint, Aamir, AgileJune, Aymen0909, DanielArmstrong, DarkTower, Draiakoo, EagleSecurity, Giorgio, Krace, KupiaSec, MidgarAudits, MrPotatoMagic, PoeAudits, Ryonen, SpicyMeatball, Topmark, Tychai0s, VAD37, Varun_05, VrONTg, WoolCentaur, _eperezok, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, d3e4, devblixt, evmboi32, fnanni, immeas, jesjupyter, juancito, ke1caM, klau5, ktg, lil_eth, merlinboii, nuthan2x, peter, petro_1912, pkqs90, pynschon, radin100, sandy, sashik_eth, shaka, sl1, soliditywala, t0x1c, ubl4nk, visualbits, vnavascues
1.1225 USDC - $1.12
This issue leads to a panic revert whenever a fighter is rerolled or created with customAttributes[0] flag set to 100 while using a generation other than the first one, effectively halting the creation process due to a division by zero error. This can restrict the game's expansion to new generations of fighters, directly impacting the game's scalability and user experience.
In FighterFarm.sol:416
, the _createFighterBase
function calculates a fighter's element using the modulo operator with an uninitialized value in the numElements
mapping for generations beyond the first. Since numElements
for new generations is never set beyond the initial generation (index 0), any attempt to create a fighter or reroll an existing fighter with a subsequent generation will result in a division by zero error, causing the function to revert and fail to create a new fighter. This issue is critical as it prevents the successful creation of fighters for any generation beyond the first, thereby limiting the functionality and future expansion of the platform.
uint256 element = dna % numElements[generation[fighterType]]; // Division by zero error for generations > 0
// FighterFarm.t.sol function testMintRevertsOnNewGenerations() public { _mintFromMergingPool(_ownerAddress); // get 4k neuron from treasury _fundUserWithNeuronByTreasury(_ownerAddress, 10_000 * 10 ** 18); _fighterFarmContract.incrementGeneration(0); // Increase generation count vm.prank(address(_mergingPoolContract)); _fighterFarmContract.mintFromMergingPool( _ownerAddress, "_neuralNetHash", "original", [ /*customAttributes flag set*/ uint256(100), uint256(80)] ); // panic: division or modulo by 0 /* Notice this will also happen when trying to reroll fighters if (_fighterFarmContract.ownerOf(0) == _ownerAddress) { _neuronContract.addSpender(address(_fighterFarmContract)); _fighterFarmContract.reRoll(0, 0); // panic: division or modulo by 0 } **/ }
Manual Review
To resolve this issue, implement a mechanism to initialize numElements for each new generation within the contract. This can be achieved by either providing a function that allows the contract owner or another authorized entity to set numElements for new generations or by implementing a default value for all generations. Ensuring numElements is properly set before any fighters of a new generation are created will prevent the division by zero error and allow the game to scale to new generations seamlessly.
Invalid Validation
Invalid Validation
#0 - c4-pre-sort
2024-02-22T18:39:35Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T18:39:42Z
raymondfam marked the issue as duplicate of #45
#2 - c4-judge
2024-03-07T07:02:51Z
HickupHH3 marked the issue as satisfactory
π Selected for report: nuthan2x
Also found by: 0xE1, 0xblackskull, 0xgrbr, 0xvj, Greed, McToady, MidgarAudits, PetarTolev, Sabit, SovaSlava, SpicyMeatball, Timenov, Tychai0s, _eperezok, alexxander, btk, c0pp3rscr3w3r, favelanky, jesjupyter, josephdara, juancito, klau5, kutugu, lil_eth, merlinboii, pynschon, sandy, shaflow2, zaevlad
7.2869 USDC - $7.29
The absence of a mechanism to revoke the staker role in FighterFarm.sol
could lead to permanent authorization for addresses once they are granted the ability to stake fighters. This flaw limits the control over who can participate in staking, potentially leading to security risks or abuse of the system if malicious actors or compromised accounts cannot be removed from the staking pool.
In FighterFarm.sol:82
, the contract maintains a mapping (hasStakerRole
) to track addresses authorized to stake fighters. However, it lacks a function or mechanism to revoke this staking ability once granted. This oversight means that once an address is authorized, it remains permanently able to stake fighters, with no way to remove this privilege in response to changes in game dynamics, security concerns, or the address's behavior.
mapping(address => bool) public hasStakerRole; // No mechanism to revoke this role once granted
Manual Review
Implement a function that allows contract administrators or designated roles to revoke the staker role from addresses. This function should securely modify the hasStakerRole mapping to reflect the revocation of staking privileges.
Access Control
#0 - c4-pre-sort
2024-02-24T06:24:02Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T06:24:09Z
raymondfam marked the issue as duplicate of #20
#2 - c4-judge
2024-03-05T10:04:24Z
HickupHH3 marked the issue as partial-25