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: 161/283
Findings: 6
Award: $13.09
π 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
Players can reroll more times than they are allowed to. By rerolling fighters can get different weight, element and physicalAttributes. First 2 impact the playing style, while the last contribute to nft rarity.
The game has a reRoll mechanism which allow fighters to get new random traits.
The number of reroll allowed per NFT is set by maxRerollsAllowed[fighterType]
.
Let's suppose the game owner wants to increase the champion (fighterType 0) generation:
addAttributeProbabilities
to add the probabilities for the new generation (attributeProbabilities
is used in dnaToIndex
to get attribute probability index);incrementGeneration
.A player wants to reroll a dendroid NFT (fighter type 1) but calls reRoll
with fighterType == 0 (champion). By selecting the other fighter type (than his tokenId type) he benefits the extra reroll champion benefit.
The dendroid nft gets new pseudo-random traits and physical attributes, even if dendroids have all physycal attributes hardcoded to 99.
Consider removing fighterType
function attribute from reroll
and use fighters[tokenId].dendroidBool
as index for fighter type :
- function reRoll(uint8 tokenId, uint8 fighterType) public { + function reRoll(uint8 tokenId) public { + uint8 fighterType = uint8(fighters[tokenId].dendroidBool); require(msg.sender == ownerOf(tokenId)); require(numRerolls[tokenId] < maxRerollsAllowed[fighterType]); ...
Invalid Validation
#0 - c4-pre-sort
2024-02-22T02:40:22Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T02:40:29Z
raymondfam marked the issue as duplicate of #306
#2 - c4-judge
2024-03-05T04:37:18Z
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: 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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L139-L158 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L329 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L499-L506
Players can get a dishonest advantage by allowing them to set any values for weight
and element
.
Game mechanics are broken by having heroes with unusual element/weight values.
One way users are rewarded is by letting them to mint new heroes.
After admin pickWinner
s, the winners can call MerginPool.claimRewards()
.
The issue rely on the fact that winners can set any values for customAttributes
, which are passed to mintFromMergingPool
and further used to set element
and weight
attributes.
function _createNewFighter( ... if (customAttributes[0] == 100) { (element, weight, newDna) = _createFighterBase(dna, fighterType); } else { element = customAttributes[0]; weight = customAttributes[1]; newDna = dna; } ... fighters.push( FighterOps.Fighter( weight, element, attrs,
I see two different solutions.
require
checks;customAttribute
to [uint256(100), uint256(100)] when calling _createNewFighter
in mintFromMergingPool
as in the other 2 places.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 + [uint256(100), uint256(100)] ); }
By doing so, element, weight and dna is computed by _createFighterBase
.
Invalid Validation
#0 - c4-pre-sort
2024-02-24T09:02:15Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T09:02:24Z
raymondfam marked the issue as duplicate of #226
#2 - c4-judge
2024-03-11T10:27:57Z
HickupHH3 marked the issue as satisfactory
π 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
If roundId grows big enough, users may be unable to claim NRN rewards due to block gas limit.
claimNRN()
function includes a loop that iterates over each round from the first round the user has not claimed (initially 0) up to the current roundId.
The issue here lies in the fact that the number of iterations is not user controlable.
If roundId
grows big enough and if a user has not claimed their rewards for a large number of rounds, iterating over all rounds will became very costly and can result in a gas cost that is over the block gas limit. In this case user can't claim his rewards anymore.
/// @notice Claims NRN tokens for the specified rounds. /// @dev Caller can only claim once per round. 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; } if (claimableNRN > 0) { amountClaimed[msg.sender] += claimableNRN; _neuronInstance.mint(msg.sender, claimableNRN); emit Claimed(msg.sender, claimableNRN); } }
Consider limiting the number of roundIds a user can claim rewards for at a time.
uint32 lowerBound = numRoundsClaimed[msg.sender]; - for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { + uint256 maxRoundId = lowerBound + 25 < roundId ? lowerBound + 25 : roundId; + for (uint32 currentRound = lowerBound; currentRound < maxRoundId; currentRound++) { ... }
DoS
#0 - c4-pre-sort
2024-02-25T02:27:50Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-25T02:28:00Z
raymondfam marked the issue as duplicate of #1541
#2 - c4-judge
2024-03-11T13:01:18Z
HickupHH3 marked the issue as duplicate of #216
#3 - c4-judge
2024-03-12T02:45:05Z
HickupHH3 marked the issue as partial-50
#4 - c4-judge
2024-03-21T02:57:24Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0rpse, 0xBinChook, 0xDetermination, 0xGreyWolf, 0xLogos, 0xWallSecurity, 0xaghas, 0xgrbr, 0xkaju, 0xlyov, AlexCzm, BARW, Blank_Space, BoRonGod, Daniel526, DanielArmstrong, Draiakoo, FloatingPragma, Giorgio, Greed, Jorgect, Matue, McToady, MidgarAudits, Nyxaris, PUSH0, PedroZurdo, Pelz, PoeAudits, Silvermist, SpicyMeatball, Tekken, Tricko, Tumelo_Crypto, VAD37, WoolCentaur, Zac, alexzoid, andywer, aslanbek, bgsmallerbear, cats, d3e4, desaperh, dimulski, dutra, erosjohn, evmboi32, favelanky, fnanni, forkforkdog, gesha17, givn, grearlake, haxatron, honey-k12, iamandreiski, immeas, juancito, kaveyjoe, ke1caM, kiqo, klau5, korok, lil_eth, lsaudit, n0kto, ni8mare, niser93, pa6kuda, peanuts, peter, shaka, sl1, soliditywala, solmaxis69, t0x1c, tallo, thank_you, tpiliposian, visualbits, vnavascues, web3pwn, yotov721
0.0352 USDC - $0.04
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L324 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L254 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L214
Using on-chain, known attributes as a source of pseudo-randomness opens the door for gamifying the system; players can get a dishonest advantage.
Players can see in advance the reroll heroes traits and can decide not to reRoll
. Protocol doesn't accumulate rerollCost.
_createNewFighter
and _createFighterBase
rely on week source of randomness from only on-chain attributes. Users can manipulate the DNA of their hero in different ways depending where _createNewFighter
is called from:
claimFighters
: players can wait other users mint fighters so fighters.length
get increased. Once a favorable DNA results from hashing the (abi.encode(msg.sender, fighters.length))
, player can call claimFighters
.... for (uint16 i = 0; i < totalToMint; i++) { _createNewFighter( msg.sender, uint256(keccak256(abi.encode(msg.sender, fighters.length))), modelHashes[i],
redeemMintPass
case it seems player can set any mintPassDnas
they desire.mintFromMergingPool
case it's similar to first case, but msg.sender
is MerginPool address instead.reRoll
is called, msg.sender, tokenId, numRerolls
are used. Players know in advance the reroll results and can decide not to call the function and pay for it. Protocol doesn't accumulate the reroll fees.DNA is used to create the base attributes for the fighter:
numElements[0] = 3; ... uint256 element = dna % numElements[generation[fighterType]];// @audit 3 possible values for generation 0; uint256 weight = dna % 31 + 65;// @audit [65, 95]
Use a safe way of getting random numbers (such as ChainLink VRF).
Other
#0 - c4-pre-sort
2024-02-24T01:48:49Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T01:49:05Z
raymondfam marked the issue as duplicate of #53
#2 - c4-judge
2024-03-06T03:49:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-06T03:51:13Z
HickupHH3 marked the issue as satisfactory
#4 - c4-judge
2024-03-15T02:10:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#5 - c4-judge
2024-03-22T04:21:19Z
HickupHH3 marked the issue as duplicate of #376
π Selected for report: klau5
Also found by: 0xAlix2, 0xCiphky, 0xDetermination, 0xG0P1, 0xMosh, 0xabhay, 14si2o_Flint, AlexCzm, Aymen0909, CodeWasp, DanielArmstrong, FloatingPragma, Giorgio, JCN, Jorgect, Kalogerone, KmanOfficial, Kow, KupiaSec, McToady, SpicyMeatball, VAD37, WoolCentaur, ZanyBonzy, alexxander, alexzoid, almurhasan, blutorque, csanuragjain, denzi_, dipp, djxploit, evmboi32, handsomegiraffe, haxatron, immeas, jesjupyter, ke1caM, klau5, lanrebayode77, lil_eth, merlin, merlinboii, nuthan2x, peanuts, shaflow2, shaka, sl1, solmaxis69, stakog, swizz, t0x1c, tallo, ubermensch, vnavascues, yotov721
1.0089 USDC - $1.01
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L543 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L254
Users can transfer fighters even if they are staked. Further they can participate in battles without the risk of loosing their NRN amount staked or loosing points.
First time when a player stake a NRN balance to one of his heroes, that hero is considered staked.
When player unstake entire balance from a hero token, that here is not staked anymore.
FighterFarm
implements _ableToTransfer()
that require a token to be unstaked before transferring to another address.
/// @notice Check if the transfer of a specific token is allowed. /// @dev Cannot receive another fighter if the user already has the maximum amount. /// @dev Additionally, users cannot trade fighters that are currently staked. /// @param tokenId The token ID of the fighter being transferred. /// @param to The address of the receiver. /// @return Bool whether the transfer is allowed or not. function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }
Players can circumvent this restriction in a few steps:
stakeNrn()
and stake 1_000 NRN; amountStaked[tokneId]
is increased, FighterFarm fighterStaked
is true;stakeAtRisk[roundId][tokenId]
is increased with 0.1% * amountStaked = 1 (for bpsLostPerLoss
== 10);hasUnstaked[tokenId][roundId]
is set to true;
FighterFarm fighterStaked
is set to false;bpsLostPerLoss
part from his stakeAtRisk
is reclaimed.
amountStaked[tokenId]
is set to 0.001 * 1e18 (0.1% of 1 NRN).fighterStaked
is false (can be transferred) and starting with the next round the new owner can call stakeNRN
without locking token transfer (updateFighterStaking
is not called).
This is the first part of the issue. Next you can read how a malicious player can benefit from this.
Before transferring the token, player stake a big amount of NRN.
Next he waits to win a battle so accumulatedPointsPerFighter
get increased.
The player transfer the fighter to another address he controls. the following will happen/ are true:
accumulatedPointsPerFighter[tokenId][roundId]
is set to a relatively big value;accumulatedPointsPerAddress[newFighterOwner][roundId]
is 0. the new newFighterOwner won no battle yes so he has no points.
Now, when a battele is won fighter accumulates points or reclaimNRN, like usual
but when a battle is lost, because accumulatedPointsPerFighter > accumulatedPointsPerAddress
, the tx will revert with an arithmeticError panic error.See the following coded PoC which can be added in RankedBattle.t.sol file:
function testWinPointsWithZeroRisk() public { address staker = makeAddr("staker"); address alice = makeAddr("alice"); _mintFromMergingPool(staker); _fundUserWith4kNeuronByTreasury(staker); // mint token 1 (not 0) to another player and increase totalAccumulatedPoints // this is required to `setNewRound()` _increaseTotalAccumulatedPoints(); // prepare token to circumvent `fighterStaked` transfer restriction _circumventTransferRestriction(staker); // staker stake some more to token 0 and 'waits' for a win // to increase `accumulatedPointsPerFighter` vm.prank(staker); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, 0); vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 0, 0, 1500, true); assertEq(_rankedBattleContract.amountStaked(0) >= 3_000 * 1e18, true); // token can be transferred freely vm.prank(staker); _fighterFarmContract.transferFrom(staker, alice, 0); assertEq(_fighterFarmContract.ownerOf(0), alice); //~ 1500 * sqrt(3_000) assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 1) == 1500 * 54, true, "fighter points < 54 * 1500"); assertEq(_rankedBattleContract.accumulatedPointsPerAddress(alice, 1) == 0, true, " alice points != 0"); // this should not revert vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x11)); //arithmeticError vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, true); // a won battle doesn't revert vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 0, 0, 1500, true); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 1) > 1500 * 54, true); } // helper functions function _increaseTotalAccumulatedPoints() private { uint256 tokenId = 1; address hellen = makeAddr("hellen"); _mintFromMergingPool(hellen); _fundUserWith11kNeuronByTreasury(hellen); vm.prank(hellen); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, tokenId); assertEq(_rankedBattleContract.amountStaked(tokenId), 3_000 * 10 ** 18); vm.prank(address(_GAME_SERVER_ADDRESS)); // 0 == win _rankedBattleContract.updateBattleRecord(tokenId, 50, 0, 1500, true); assertEq(_rankedBattleContract.totalAccumulatedPoints(0) > 0, true); } function _circumventTransferRestriction(address staker) private { // staker loose one battle vm.prank(staker); _rankedBattleContract.stakeNRN(1_000 * 10 ** 18, 0); vm.prank(address(_GAME_SERVER_ADDRESS)); // battleResult 2 == loose // 1 == ties // 0 == win _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, true); // staker unstake all his NRN uint256 amountStakedLeft = _rankedBattleContract.amountStaked(0); vm.prank(staker); _rankedBattleContract.unstakeNRN(amountStakedLeft, 0); assertEq(_rankedBattleContract.amountStaked(0), 0, "amountStacked after unstake"); vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 0, 0, 1500, true); _rankedBattleContract.setNewRound(); // token 0 has amountStaked > 0 but hasUnstaked for the new roundId is 'false' -> Not ok assertEq(_rankedBattleContract.amountStaked(0), 0.001 * 1e18, "amountStacked after unstake and new round"); assertEq(_fighterFarmContract.fighterStaked(0), false, "token 0 is staked"); }
Malicious user can stake more NRN to ensure his stakingFactor
is increased to mantain the following true: pointsToLooseThisBattle > accumulatedPointsPerAddress[fighterOwner][roundId]
Or he can always transfer the fighter to new addresses with
accumulatedPointsPerAddress[fighterOwner][roundId]
== 0.
There are 2 things that lead to this problem
stakeAtRisk
balance was transferred andaccumulatedPointsPerFighter[owner]{roundId]
> 0;To give the owner flexibility to transfer his NFT when he wishes, consider implementing a new function which will set the nft into a transferable state.
This function should:
When the token is transferred :
accumulatedPointsPerFighter[tokenId][roundId]
from accumulatedPointsPerAddress
and totalAccumulatedPoints
;accumulatedPointsPerFighter[tokenId][roundId] = 0
Invalid Validation
#0 - c4-pre-sort
2024-02-23T05:24:54Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:25:07Z
raymondfam marked the issue as duplicate of #739
#2 - c4-pre-sort
2024-02-23T05:30:36Z
raymondfam marked the issue as not a duplicate
#3 - c4-pre-sort
2024-02-23T05:30:43Z
raymondfam marked the issue as duplicate of #51
#4 - c4-pre-sort
2024-02-23T05:31:32Z
raymondfam marked the issue as not a duplicate
#5 - c4-pre-sort
2024-02-23T05:31:42Z
raymondfam marked the issue as duplicate of #739
#6 - c4-pre-sort
2024-02-26T02:14:26Z
raymondfam marked the issue as not a duplicate
#7 - c4-pre-sort
2024-02-26T02:14:43Z
raymondfam marked the issue as duplicate of #833
#8 - c4-judge
2024-03-11T02:47:57Z
HickupHH3 marked the issue as satisfactory
#9 - c4-judge
2024-03-11T02:48:40Z
HickupHH3 removed the grade
#10 - c4-judge
2024-03-13T10:12:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#11 - c4-judge
2024-03-13T11:32:36Z
HickupHH3 marked the issue as duplicate of #1641
#12 - c4-judge
2024-03-14T06:25:01Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0xAlix2, 0xCiphky, 0xDetermination, 0xG0P1, 0xMosh, 0xabhay, 14si2o_Flint, AlexCzm, Aymen0909, CodeWasp, DanielArmstrong, FloatingPragma, Giorgio, JCN, Jorgect, Kalogerone, KmanOfficial, Kow, KupiaSec, McToady, SpicyMeatball, VAD37, WoolCentaur, ZanyBonzy, alexxander, alexzoid, almurhasan, blutorque, csanuragjain, denzi_, dipp, djxploit, evmboi32, handsomegiraffe, haxatron, immeas, jesjupyter, ke1caM, klau5, lanrebayode77, lil_eth, merlin, merlinboii, nuthan2x, peanuts, shaflow2, shaka, sl1, solmaxis69, stakog, swizz, t0x1c, tallo, ubermensch, vnavascues, yotov721
1.0089 USDC - $1.01
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/VoltageManager.sol#L107-L119
Any player can initiate an unlimited number of battles without buying batteries.
For each battle a VOLTAGE_COST
is deducted from the address who initiated the battle. If the owner voltage is too low to start a new battle, players can buy and use the so called battery
items to replenish their energy back to 100%.
Malicious user can start a huge number of battles with almost no cost (1 NRN wei/ round) by transferrin the hero NFT to new addresses.
stakeNRN
:stakeAtRisk
balance (considering bpsLostPerLoss = 10).unstakeNRN
and unstake entire balance (999 wei left):amountStaked
is 0;1 day
.stakeAtRisk
is 1 wei, the following check from updateBattleRecord
passesuint256 stakeAtRisk = _stakeAtRiskInstance.getStakeAtRisk(tokenId); if (amountStaked[tokenId] + stakeAtRisk > 0) { _addResultPoints(battleResult, tokenId, eloFactor, mergingPortion, fighterOwner); }
I see many ways to fix this issue, but each solution has its drawbacks.
_addResultPoints
only if amountStaked + atRisk
is bigger than a predefined threshold. But smaller players (in term of NRN balance) will be disadvantaged.ownerVoltage
< 100, disable token transfer; or
b. if getStakeAtRisk(tokenId)
> 0, disable transferInvalid Validation
#0 - c4-pre-sort
2024-02-23T02:28:06Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-23T02:28:21Z
raymondfam marked the issue as duplicate of #43
#2 - c4-judge
2024-03-07T04:24:01Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-07T04:24:43Z
HickupHH3 marked the issue as not a duplicate
#4 - c4-judge
2024-03-07T04:24:53Z
HickupHH3 removed the grade
#5 - c4-judge
2024-03-19T04:04:10Z
HickupHH3 marked the issue as unsatisfactory: Insufficient quality
#6 - c4-judge
2024-03-19T04:05:52Z
HickupHH3 removed the grade
#7 - c4-judge
2024-03-19T04:06:01Z
HickupHH3 marked the issue as duplicate of #137
#8 - c4-judge
2024-03-19T04:06:08Z
HickupHH3 marked the issue as satisfactory
π Selected for report: givn
Also found by: 0x11singh99, 0xAkira, 0xBinChook, 0xDetermination, 0xMosh, 0xStriker, 0xmystery, 14si2o_Flint, 7ashraf, Aamir, AlexCzm, BARW, Bauchibred, BenasVol, BigVeezus, Blank_Space, Bube, DarkTower, DeFiHackLabs, EagleSecurity, KmanOfficial, Krace, McToady, MrPotatoMagic, PetarTolev, Rolezn, SHA_256, SpicyMeatball, Tekken, Timenov, ZanyBonzy, agadzhalov, alexzoid, boredpukar, btk, cartlex_, dimulski, forkforkdog, haxatron, immeas, jesjupyter, juancito, kartik_giri_47538, klau5, lsaudit, merlinboii, nuthan2x, offside0011, oualidpro, peter, radev_sw, rekxor, rspadi, shaflow2, shaka, swizz, vnavascues, yotov721, yovchev_yoan
8.8123 USDC - $8.81
attributeProbabilities
are set twice in AiArenaHelper constructor;Remove addAttributeProbabilities()
function call from AiArenaHelper
constructor.
constructor(uint8[][] memory probabilities) { _ownerAddress = msg.sender; // Initialize the probabilities for each attribute addAttributeProbabilities(0, probabilities);// @audit-qa remove this function call uint256 attributesLength = attributes.length; for (uint8 i = 0; i < attributesLength; i++) { attributeProbabilities[0][attributes[i]] = probabilities[i];// because you are setting attr prob here attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i]; } }
MINTER_ROLE
can mint back any burned NRN.Neuron contract expose externally the burn
function, allowing any NRN holder to destroy tokens they hold. The maximum total supply should be decreased by the amount of burned tokens.
Additionally, in mint
function use <=
instead <
to better reflect what MAX_SUPPLY
means.
amountStaked
balance == 0.To allow transferring a previously staked fighter, a player must call unstakeNRN
even if amountStaked
for that fighter is 0.
Consider improving the player UX and read the stakedBalance
directly.
Currently magic numbers 0, 1, and 2 are used as battle results. Consider creating and using an enum instead. Moreover avoid using value 0 as a valid battle results:
enum battleResult { invalidResult, won, tie, lost } https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L440 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L505-L513 ### L-05 Allows only voltage spender to consume energy Currently any addresses can call `spendVoltage` to reduce its energy. Even if unlikely, this can open the door for abuses. Consider keeping only selected addresses to spend voltage ```solidity function spendVoltage(address spender, uint8 voltageSpent) public { - require(spender == msg.sender || allowedVoltageSpenders[msg.sender]); + require(allowedVoltageSpenders[msg.sender]); ...
GameItems contract implements a few restrictions related to the amount of items can be minted daily per address. Players can get an advantage by minting items with daily allowance. Moreover, there is an transferable
item options.
Consider to allow only fighter owners to mint items which can't be transferred or which have an daily allowance.
iconsTypes
array length check is missing but all other arrays length is checked.
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L233-L248
iconsTypes
from RedeemMintPass
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L236
generation
from createPhysicalAttributes
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/AiArenaHelper.sol#L85
#0 - raymondfam
2024-02-26T02:56:14Z
With the exception of L-03 being a false positive in the absence of adequate elaboration, the report possesses an adequate amount of generic L and NC.
#1 - c4-pre-sort
2024-02-26T02:56:22Z
raymondfam marked the issue as sufficient quality report
#2 - HickupHH3
2024-03-15T13:57:53Z
L-01: L/R L-02: R L-03: NC (more elaboration required) L-04: R L-06: NC L-07: R NC-01: R/NC #1551: R
#3 - c4-judge
2024-03-15T14:00:39Z
HickupHH3 marked the issue as grade-b