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: 23/283
Findings: 11
Award: $253.93
π Selected for report: 0
π Solo Findings: 0
π Selected for report: CodeWasp
Also found by: 0x13, 0xAlix2, 0xAsen, 0xCiphky, 0xE1, 0xLogos, 0xaghas, 0xlemon, 0xlyov, 0xvj, ADM, Aamir, BARW, Bauchibred, DMoore, DanielArmstrong, Draiakoo, Fulum, GhK3Ndf, Josh4324, Kalogerone, KmanOfficial, Krace, KupiaSec, Limbooo, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Tendency, _eperezok, adam-idarrha, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, c0pp3rscr3w3r, cartlex_, cats, d3e4, deadrxsezzz, denzi_, devblixt, dimulski, erosjohn, evmboi32, fnanni, grearlake, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, juancito, klau5, korok, ktg, ladboy233, matejdb, merlinboii, novamanbg, nuthan2x, oualidpro, peanuts, petro_1912, pkqs90, pynschon, radev_sw, rouhsamad, sashik_eth, shaka, sobieski, soliditywala, stackachu, tallo, thank_you, ubl4nk, vnavascues, web3pwn, xchen1130, zhaojohnson
0.1044 USDC - $0.10
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L322 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L16
Any fighter (an ERC-721) can be transferred even though the intention of the FighterFarm
contract says otherwise (the FighterFarm.fighterStaked
mapping value is true
for the given token ID).
The most significant vulnerabilities involve the potential loss of data integrity concerning the token and its owner (e.g. points stats, stake at risk) across multiple contracts (e.g. RankedBattle
, StakeAtRisk
, FighterFarm
). This increases the risk of a DoS attack when the game server is updating a battle record (via the updatedBattleRecord
function) for the token previosuly transferred.
The FighterFarm
contract lacks measures in certain transfer-related functions to enforce the non-transferability status of fighters, allowing transfers even when explicitly disallowed by the contract state (i.e. the fighter is staked):
safeTransferFrom(address,address,uint256,bytes)
(no overriden by FighterFarm
, inherited from ERC721
): does not implement the _ableToTransfer
check.safeTransferFrom(address,address,uint256)
(overriden by FighterFarm
): does implement the _ableToTransfer
check.transferFrom(address,address,uint256)
(overriden by FighterFarm
): does implement the _ableToTransfer
check.Therefore, fighters can always be transferred by calling the FighterFarm.safeTransferFrom(address,address,uint256,bytes)
function.
Add the following tests in RankedBattle.t.sol
:
function testSafeTransferFromDoesNotCheckIfFighterIsStaked() public { // Arrange address staker = vm.addr(3); _mintFromMergingPool(staker); _fundUserWith4kNeuronByTreasury(staker); assertEq(_neuronContract.balanceOf(staker) >= 4_000 * 10 ** 18, true); assertEq(_fighterFarmContract.ownerOf(0), staker); assertEq(_neuronContract.hasRole(keccak256("STAKER_ROLE"), address(_rankedBattleContract)), true); vm.prank(staker); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, 0); assertEq(_rankedBattleContract.amountStaked(0), 3_000 * 10 ** 18); assertEq(_fighterFarmContract.fighterStaked(0), true); address toAddress = msg.sender; // Act - `transferFrom` checks whether fighter is staked vm.prank(staker); vm.expectRevert(); _fighterFarmContract.transferFrom(staker, toAddress, 0); // Act - `safeTransferFrom(address,address,uint256)` checks whether fighter is staked vm.prank(staker); vm.expectRevert(); _fighterFarmContract.safeTransferFrom(staker, toAddress, 0); // Act - `safeTransferFrom(address,address,uint256,bytes)` does not check whether fighter is staked vm.prank(staker); _fighterFarmContract.safeTransferFrom(staker, toAddress, 0, ""); // Assert assertEq(_fighterFarmContract.ownerOf(0), toAddress); }
Forge tests and manually reviewed.
Override the FighterFarm.safeTransferFrom(address,address,uint256,bytes)
function making sure the token is transferable.
DoS
#0 - c4-pre-sort
2024-02-23T06:01:28Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T06:01:37Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:58:06Z
HickupHH3 marked the issue as satisfactory
π Selected for report: Aamir
Also found by: 0rpse, 0x11singh99, 0x13, 0xAlix2, 0xAsen, 0xBinChook, 0xCiphky, 0xE1, 0xKowalski, 0xLogos, 0xWallSecurity, 0xaghas, 0xbranded, 0xlemon, 0xlyov, 0xpoor4ever, 0xprinc, 0xvj, ADM, Aymen0909, BARW, Bauchibred, Breeje, CodeWasp, DMoore, DeFiHackLabs, Draiakoo, Fulum, GhK3Ndf, Greed, Jorgect, Josh4324, Kalogerone, KmanOfficial, Krace, Limbooo, McToady, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Ryonen, SovaSlava, SpicyMeatball, Tendency, Timenov, ZanyBonzy, _eperezok, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, cats, csanuragjain, deadrxsezzz, denzi_, devblixt, dimulski, djxploit, erosjohn, evmboi32, fnanni, grearlake, haxatron, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, josephdara, juancito, kiqo, klau5, korok, krikolkk, ktg, kutugu, ladboy233, lil_eth, m4ttm, matejdb, merlinboii, n0kto, ni8mare, novamanbg, nuthan2x, oualidpro, pa6kuda, peter, petro_1912, pkqs90, pynschon, sandy, sashik_eth, shaflow2, shaka, sobieski, soliditywala, solmaxis69, stackachu, tallo, thank_you, tpiliposian, ubl4nk, visualbits, vnavascues, web3pwn, xchen1130, zhaojohnson
0.0037 USDC - $0.00
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L291 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L10
Any game item (an ERC-1155) can be transferred even though the intention of its configuration says otherwise (the GameItemAttributes.transferable
member is false
).
The GameItems
contract lacks measures in certain transfer-related functions to enforce the non-transferability status of items, allowing transfers even when explicitly disallowed by the game attribute configuration:
safeTransferFrom(address,address,uint256,uint256,bytes)
(overriden by GameItems
): does implement the GameItemAttributes.transferable
check.safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
(not overriden by GameItems
, inherited from ERC1155
): doesn't implement the GameItemAttributes.transferable
check.Therefore, game items can always be transferred by calling the GameItems.safeBatchTransferFrom
function.
/// @notice Safely transfers an NFT from one address to another. /// @dev Added a check to see if the game item is transferable. function safeTransferFrom( address from, address to, uint256 tokenId, uint256 amount, bytes memory data ) public override(ERC1155) { // @audit-issue this check is not present in `safeBatchTransferFrom` require(allGameItemAttributes[tokenId].transferable); super.safeTransferFrom(from, to, tokenId, amount, data); }
Add the following tests in GameItems.t.sol
:
function testSafeBatchTransferFromIsAvailable() public { // Arrange _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 2); //paying 2 $NRN for 2 batteries _gameItemsContract.adjustTransferability(0, false); (,, bool transferable,,,) = _gameItemsContract.allGameItemAttributes(0); assertEq(transferable, false); assertEq(_gameItemsContract.balanceOf(address(this), 0), 2); assertEq(_gameItemsContract.balanceOf(msg.sender, 0), 0); // Act - safeTransferFrom checks `transferable` vm.expectRevert(); _gameItemsContract.safeTransferFrom(address(this), msg.sender, 0, 1, ""); // Act - safeBatchTransferFrom does not check `transferable` uint256[] memory tokenIds = new uint256[](1); tokenIds[0] = 0; uint256[] memory amounts = new uint256[](1); amounts[0] = 1; _gameItemsContract.safeBatchTransferFrom(address(this), msg.sender, tokenIds, amounts, ""); // Assert assertEq(_gameItemsContract.balanceOf(address(this), 0), 1); assertEq(_gameItemsContract.balanceOf(msg.sender, 0), 1); }
Forge tests and manually reviewed.
Override the GameItems.safeBatchTransferFrom
function making sure each token is transferable.
Invalid Validation
#0 - c4-pre-sort
2024-02-22T04:45:37Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T04:45:46Z
raymondfam marked the issue as duplicate of #15
#2 - c4-judge
2024-03-05T03:21:27Z
HickupHH3 marked the issue as not a duplicate
#3 - c4-judge
2024-03-05T03:22:39Z
HickupHH3 marked the issue as duplicate of #575
#4 - c4-judge
2024-03-05T04:47:38Z
HickupHH3 changed the severity to 3 (High Risk)
#5 - c4-judge
2024-03-18T01:13:08Z
HickupHH3 marked the issue as satisfactory
π Selected for report: Abdessamed
Also found by: 0rpse, 0xAlix2, 0xAsen, 0xCiphky, 0xlemon, 0xmystery, 0xvj, ADM, Aamir, Archime, BARW, DarkTower, Draiakoo, FloatingPragma, JCN, McToady, MrPotatoMagic, OMEN, PetarTolev, Ryonen, SpicyMeatball, Tendency, VAD37, Velislav4o, VrONTg, Zac, adamn000, ahmedaghadi, alexxander, alexzoid, bhilare_, btk, cats, d3e4, denzi_, devblixt, dimulski, evmboi32, fnanni, givn, haxatron, immeas, jesjupyter, juancito, ke1caM, klau5, korok, krikolkk, matejdb, n0kto, niser93, peter, pkqs90, radin100, shaka, sl1, soliditywala, stackachu, stakog, t0x1c, vnavascues, yotov721, zhaojohnson
1.2667 USDC - $1.27
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L233
A FighterFarm.redeemMintPass
caller can forge the following fighter NFT properties of their Mint Pass by arbitrarily set the arguments:
fighterTypes
parameter).iconsTypes
parameter).mintPassDnas
parameter).modelHashes
parameter).modelTypes
parameter).Therefore, it is not only possible to redeem as many Dendroid fighters as Mint Passes but also create Champion with cherry picked physical attributes probabilities and other combinations of data not expected by the system.
According to the sponsor and its documentation some fighter traits are more rare than others, for instance fighters of type Dendroid (i.e. fighterType
is 1
) are more rare than fighter of type Champion (i.e. fighterType
is 0
) and have particular physical attributes. In fact all Dendroid physical attributes are hard coded to 99
whilst some of the Champion ones are calculated from its DNA and others depend on an icon type.
The lack of validation on the FighterFarm.redeemMintPass
arguments against the caller Mint Pass undermines the consideration of rarity for specific fighter traits.
This already implemented test in RankedBattle.t.sol
is a good example:
function testRedeemMintPass() public { uint8[2] memory numToMint = [1, 0]; bytes memory signature = abi.encodePacked( hex"20d5c3e5c6b1457ee95bb5ba0cbf35d70789bad27d94902c67ec738d18f665d84e316edf9b23c154054c7824bba508230449ee98970d7c8b25cc07f3918369481c" ); string[] memory _tokenURIs = new string[](1); _tokenURIs[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; // first i have to mint an nft from the mintpass contract assertEq(_mintPassContract.mintingPaused(), false); _mintPassContract.claimMintPass(numToMint, signature, _tokenURIs); assertEq(_mintPassContract.balanceOf(_ownerAddress), 1); assertEq(_mintPassContract.ownerOf(1), _ownerAddress); // once owning one i can then redeem it for a fighter uint256[] memory _mintpassIdsToBurn = new uint256[](1); string[] memory _mintPassDNAs = new string[](1); uint8[] memory _fighterTypes = new uint8[](1); uint8[] memory _iconsTypes = new uint8[](1); string[] memory _neuralNetHashes = new string[](1); string[] memory _modelTypes = new string[](1); _mintpassIdsToBurn[0] = 1; // @audit-issue not only each value can be arbitrarily set but also combinations of them not expected by the system _mintPassDNAs[0] = "dna"; // @audit-info an arbitrarily value _fighterTypes[0] = 0; // @audit-info an arbitrarily value _neuralNetHashes[0] = "neuralnethash"; // @audit-info an arbitrarily value _modelTypes[0] = "original"; // @audit-info an arbitrarily value _iconsTypes[0] = 1; // @audit-info an arbitrarily value // approve the fighterfarm contract to burn the mintpass _mintPassContract.approve(address(_fighterFarmContract), 1); _fighterFarmContract.redeemMintPass( _mintpassIdsToBurn, _fighterTypes, _iconsTypes, _mintPassDNAs, _neuralNetHashes, _modelTypes ); // check balance to see if we successfully redeemed the mintpass for a fighter assertEq(_fighterFarmContract.balanceOf(_ownerAddress), 1); // check balance to see if the mintpass was burned assertEq(_mintPassContract.balanceOf(_ownerAddress), 0); }
Manually reviewed.
A suggested initial step for mitigation is to establish a mapping system associating a claimed Mint Pass with its immutable fighter data, This mapping can be stored in a designated contract such as AAMintPass
or FighterFarm
. Then, modify the FighterFarm.redeemMintPass
function to retrieve this data from the mapping instead of relying on the parameters passed by the caller.
Invalid Validation
#0 - c4-pre-sort
2024-02-22T08:22:55Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T08:23:02Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:54:13Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-05T10:56:27Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-06T03:36:33Z
HickupHH3 marked the issue as satisfactory
π 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
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L233 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L472 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L509
The system permits the creation or updating of a fighter type (e.g. Champion) with the new DNA and attributes of another type (e.g. Dendroid). The impact of the issue on the AI Arena web server is currently unknown; however, it appears to deviate from the intended behavior of the sytem.
This is a summary of the fighters according to the sponsor and its documentation:
Champion features:
fighterType
: expected to be 0
.weight
: from 65
to 95
.Dendroid features:
fighterType
: expected to be 1
.weight
: from 65
to 95
.uint256(1)
.99
for all attributes.The FighterFarm.reRoll
function doesn't validate the fighterType
argument against the token attributes, which allows setting Dendroid data (e.g. DNA, new DNA, element, weight, generation) into a Champion and the other way around.
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"); _neuronInstance.approveSpender(msg.sender, rerollCost); bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, rerollCost); if (success) { numRerolls[tokenId] += 1; uint256 dna = uint256(keccak256(abi.encode(msg.sender, tokenId, numRerolls[tokenId]))); (uint256 element, uint256 weight, uint256 newDna) = _createFighterBase(dna, fighterType); fighters[tokenId].element = element; fighters[tokenId].weight = weight; fighters[tokenId].physicalAttributes = _aiArenaHelperInstance.createPhysicalAttributes( newDna, generation[fighterType], fighters[tokenId].iconsType, fighters[tokenId].dendroidBool // @audit-issue `fighterType` should be checked against `dendroidBool` ); _tokenURIs[tokenId] = ""; } }
The FighterFarm.redeemMintPass
function allows to set unsupported fighter types (e.g. 2
, 3
) in fighterTypes
, which have unexpected consequences in the FighterFarm._createNewFighter
function due to using different fighter type values across the comparisons:
uint256 newDna = fighterType == 0 ? dna : uint256(fighterType); // @audit-issue comparison against `0`
bool dendroidBool = fighterType == 1; // @audit-issue comparison against `1` FighterOps.FighterPhysicalAttributes memory attrs = _aiArenaHelperInstance.createPhysicalAttributes( newDna, generation[fighterType], iconsType, dendroidBool );
The MergingPool.claimRewards
function passes arbitrarily customAttributes
into the FighterFarm.mintFromMergingPool
function. However, the latter has 0
hard coded as fighterType
argument of createNewFighter
.
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, // @audit-issue `fighterType` is hard coded as Champion 0, customAttributes // @audit-issue `customAttributes` may contain non-Champion values ); }
The lack of validation on the FighterFarm.redeemMintPass
arguments against the caller Mint Pass undermines the consideration of rarity for specific fighter traits.
NA
Manually reviewed.
First, make sure that the FighterFarm.reRoll
function validates the fighterType
argument against the token dendroidBool
attribute. Also, make sure that the FighterFarm._createNewFighter
either does not accept unsupported fighterType
values or be consistent with the values used on comparisons across the logic (e.g. always use 0
)
Invalid Validation
#0 - c4-pre-sort
2024-02-25T05:18:02Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-25T05:18:17Z
raymondfam marked the issue as duplicate of #306
#2 - c4-judge
2024-03-05T04:43:02Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-19T09:04:23Z
HickupHH3 changed the severity to 3 (High Risk)
π 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
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L85 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L110 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L470
No new fighters (both Champion and Dendroid fighter types) can be created after generation 0 due to a "Division or modulo by 0" revert on the FighterFarm._createFighterBase
function. This private function is externally called by:
FighterFarm.reRoll
.FighterFarm.claimFighters
.FighterFarm.redeemMintPass
.MergingPool.claimRewards
.In the FighterFarm
contract:
numElements
mapping maps a generation to a number of elements:/// @notice Mapping of number elements by generation. mapping(uint8 => uint8) public numElements;
numElements
:/// @notice Sets the owner address, the delegated address. /// @param ownerAddress Address of contract deployer. /// @param delegatedAddress Address of delegated signer for messages. /// @param treasuryAddress_ Community treasury address. constructor(address ownerAddress, address delegatedAddress, address treasuryAddress_) ERC721("AI Arena Fighter", "FTR") { _ownerAddress = ownerAddress; _delegatedAddress = delegatedAddress; treasuryAddress = treasuryAddress_; numElements[0] = 3; // @audit here }
The contract has no numElements
setter neither an assignment expression that sets numElements
on future generations.
The _createFighterBase
function calculates element
of the given fighter type by computing the the modulo operation mod(dna, x)
, being x
the numElements
of the current generation for the given fighter type:
uint256 element = dna % numElements[generation[fighterType]];
0
.Add the following tests in FighterFarm.t.sol
:
function testClaimFightersRevertForChampionWithGenerationGtZero() public { // Arrange uint8[2] memory numToMint = [1, 0]; // NB: fighter type is Champion bytes memory claimSignature = abi.encodePacked( hex"407c44926b6805cf9755a88022102a9cb21cde80a210bc3ad1db2880f6ea16fa4e1363e7817d5d87e4e64ba29d59aedfb64524620e2180f41ff82ca9edf942d01c" ); string[] memory claimModelHashes = new string[](1); claimModelHashes[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; string[] memory claimModelTypes = new string[](1); claimModelTypes[0] = "original"; // NB: increment Champion generation from 0 to 1 _fighterFarmContract.incrementGeneration(0); assertEq(_fighterFarmContract.generation(0), 1); // Act vm.expectRevert(); _fighterFarmContract.claimFighters(numToMint, claimSignature, claimModelHashes, claimModelTypes); // Assert assertEq(_fighterFarmContract.balanceOf(_ownerAddress), 0); assertEq(_fighterFarmContract.totalSupply(), 0); } function testClaimFightersRevertForDendroidWithGenerationGtZero() public { // Arrange uint8[2] memory numToMint = [0, 1]; // NB: fighter type is Dendroid bytes memory claimSignature = abi.encodePacked( hex"407c44926b6805cf9755a88022102a9cb21cde80a210bc3ad1db2880f6ea16fa4e1363e7817d5d87e4e64ba29d59aedfb64524620e2180f41ff82ca9edf942d01c" ); string[] memory claimModelHashes = new string[](1); claimModelHashes[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; string[] memory claimModelTypes = new string[](1); claimModelTypes[0] = "original"; // NB: increment Dendroid generation from 0 to 1 _fighterFarmContract.incrementGeneration(1); assertEq(_fighterFarmContract.generation(1), 1); // Act vm.expectRevert(); _fighterFarmContract.claimFighters(numToMint, claimSignature, claimModelHashes, claimModelTypes); // Assert assertEq(_fighterFarmContract.balanceOf(_ownerAddress), 0); assertEq(_fighterFarmContract.totalSupply(), 0); }
Forge tests and manually reviewed.
Add a numElements
setter (with the access controlled) and make sure to properly set it for the upcoming generations. Also consider to do not allow increment generation (via the incrementGeneration
function) if numElements
for the upcoming generation have not been set yet.
DoS
#0 - c4-pre-sort
2024-02-22T19:12:10Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T19:12:18Z
raymondfam marked the issue as duplicate of #45
#2 - c4-judge
2024-03-08T03:20:16Z
HickupHH3 marked the issue as satisfactory
π 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#L142
The impact of this issue in the AI Arena web server is unknown, however it is expected that all fighters weight (from Fighter.weight
) sits between 65
and 95
(both included).
According to the sponsor a fighter weight must be between 65
and 95
(both included). The MergingPool.claimRewards
function does not validate the values in customAttributes
and neither does it the FighterFarm.mintFromMergingPool
function. Therefore, any caller claiming its rewards can create a fighter with an out of range weight.
Add the following test in MergingPool.t.sol
is a good example:
function testClaimRewardsSetsWeightOutOfRange() public { // Arrange _mintFromMergingPool(_ownerAddress); _mintFromMergingPool(_DELEGATED_ADDRESS); assertEq(_fighterFarmContract.ownerOf(0), _ownerAddress); assertEq(_fighterFarmContract.ownerOf(1), _DELEGATED_ADDRESS); uint256[] memory _winners = new uint256[](2); _winners[0] = 0; _winners[1] = 1; // winners of roundId 0 are picked _mergingPoolContract.pickWinner(_winners); assertEq(_mergingPoolContract.isSelectionComplete(0), true); assertEq(_mergingPoolContract.winnerAddresses(0, 0) == _ownerAddress, true); // winner matches ownerOf tokenId assertEq(_mergingPoolContract.winnerAddresses(0, 1) == _DELEGATED_ADDRESS, true); string[] memory _modelURIs = new string[](2); _modelURIs[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; _modelURIs[1] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; string[] memory _modelTypes = new string[](2); _modelTypes[0] = "original"; _modelTypes[1] = "original"; uint256[2][] memory _customAttributes = new uint256[2][](2); uint256 weightBelowRange = 64; uint256 weightAboveRange = 96; _customAttributes[0][0] = uint256(1); _customAttributes[0][1] = uint256(weightBelowRange); // @audit-info out of range _customAttributes[1][0] = uint256(1); _customAttributes[1][1] = uint256(weightAboveRange); // @audit-info out of range // winners of roundId 1 are picked _mergingPoolContract.pickWinner(_winners); // winner claims rewards for previous roundIds // Act _mergingPoolContract.claimRewards(_modelURIs, _modelTypes, _customAttributes); // Assert // @audit-info check whether weight is below the (65,95) range ( uint256 weight2, , , , , , , , ) = _fighterFarmContract.fighters(2); assertEq(weight2, weightBelowRange); // @audit-info check whether weight is above the (65,95) range ( uint256 weight3, , , , , , , , ) = _fighterFarmContract.fighters(3); assertEq(weight3, weightAboveRange); }
Manually reviewed.
Properly validate the customAttributes
values in the FighterFarm._createNewFighter
function. For instance:
uint256 weight = customAttributes[1]; require(weight >= MIN_FIGHTER_WEIGHT); require(weight <= MAX_FIGHTER_WEIGHT);
Invalid Validation
#0 - c4-pre-sort
2024-02-24T09:12:05Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T09:12:14Z
raymondfam marked the issue as duplicate of #226
#2 - c4-judge
2024-03-11T10:31:15Z
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
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L139 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
Winners may never successfully claim their rewards due to running out of gass when calling the MergingPool.claimRewards
function.
The inefficiency of the MergingPool.claimRewards
arises from its double-loop structure. It begins by iterating from numRoundsClaimed[msg.sender]
to currentRound < roundId
and then proceeds to loop through the list of winners (i.e. winnerAddresses[currentRound][j]
) until it finds the corresponding address. This approach becomes notably inefficient when an address has accumulated wins across multiple rounds but has not claimed rewards previously. The function's design results in unnecessary iterations, leading to increased gas consumption and diminished performance for users with a history of unclaimed wins.
Just being the first winner address and claiming the previous round ID reward costs ~840k gas units.
NA
Forge tests for gas estimation and manually reviewed.
A recommended first step in mitigating this issue is to adjust the MergingPool.claimRewards
function to facilitate claiming rewards for individual rounds. Additionally, it is advisable to explore the use of an iterable mapping for efficiently checking if an address has won in a specific round, especially considering the anticipated number of addresses in winnerAddresses
per round. The adoption of an iterable mapping not only enhances efficiency but also helps prevent the presence of duplicated addresses.
DoS
#0 - c4-pre-sort
2024-02-24T00:09:32Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T00:09:40Z
raymondfam marked the issue as duplicate of #1541
#2 - c4-judge
2024-03-11T13:01:43Z
HickupHH3 marked the issue as duplicate of #216
#3 - c4-judge
2024-03-12T02:37:20Z
HickupHH3 marked the issue as partial-50
#4 - c4-judge
2024-03-21T03:01:54Z
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.0088 USDC - $0.01
Judge has assessed an item in Issue #2018 as 2 risk. The relevant finding follows:
NC-03 FighterFarm calls to _createNewFighter and _createFighterBase provide weak sources of randomness for dna param
#0 - HickupHH3
2024-03-21T03:42:49Z
Fighters dna is supposed to be random and unique.
To generate fighters dna functions above use Solidity keccak256 to calculate a hash based on different parameters. The issue is that these parameters are all deterministic and predictable, and as a result dna lacks the inherent unpredictability associated with true randomness.
#1 - c4-judge
2024-03-21T03:43:25Z
HickupHH3 marked the issue as duplicate of #1017
#2 - c4-judge
2024-03-21T03:43:30Z
HickupHH3 marked the issue as partial-25
#3 - HickupHH3
2024-03-21T03:43:43Z
partial credit for lacking elaboration
#4 - c4-judge
2024-03-22T04:23:17Z
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
0.5044 USDC - $0.50
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L322
Transfering unstaked fighters has the potential to compromise data integrity related to the token and its owner (e.g. points stats, stake at risk) across multiple contracts (e.g. RankedBattle
, StakeAtRisk
, FighterFarm
). This increases the risk of a DoS attack when the game server is updating a battle record (via the updatedBattleRecord
function) for the token previosuly transferred.
Any fighter (an ERC-721) can be transferred after as long as it doesn't have any NRN amount staked on it. However, in the RankedBattle.updatedBattleRecord
there are statements and assignment statements that use fighter owner data without distinguishing the current and previous owner. This creates an elevated risk of DoS scenarios when the game server updates a battle record due to unexpected circumstances, such as:
if (curStakeAtRisk > 0) { // @audit-issue unexpected revert due to `fighterOwner` had 0 amount lost _stakeAtRiskInstance.reclaimNRN(curStakeAtRisk, tokenId, fighterOwner); amountStaked[tokenId] += curStakeAtRisk; }
accumulatedPointsPerFighter[tokenId][roundId] -= points; // @audit-issue unexpected revert due to `fighterOwner` had 0 points accumulatedPointsPerAddress[fighterOwner][roundId] -= points; totalAccumulatedPoints[roundId] -= points;
RankedBattle.updatedBattleRecord
checks (i.e. require()
). The new owner voltage could be 0 and expected to be replenished ahead of time (both achievable thanks to the VoltageManager.spendVoltage
function lack of checks on voltageSpent
):// @audit-issue the last two expressions could be falsy for the new fighter owner (involves multiple transfers) require( !initiatorBool || _voltageManagerInstance.ownerVoltageReplenishTime(fighterOwner) <= block.timestamp || _voltageManagerInstance.ownerVoltage(fighterOwner) >= VOLTAGE_COST );
This proof of concept showcases the Scenario 1:
Add the following tests in RankedBattle.t.sol
:
function testUpdateBattleRecordDoSDueToLossUnstakeTransferWin() public { // Arrange address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, 0); assertEq(_rankedBattleContract.amountStaked(0), 3_000 * 10 ** 18); uint256 roundId = _rankedBattleContract.roundId(); // Act - Update a loss battle record that leaves 3 NRN as current stake at risk vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 50, 2, 1500, true); // Act - Unstake all the NRN on the token uint256 amountStakedAfterLoss = _rankedBattleContract.amountStaked(0); vm.prank(player); _rankedBattleContract.unstakeNRN(amountStakedAfterLoss, tokenId); // Act - Transfer fighter from player to new address vm.prank(player); _fighterFarmContract.transferFrom(player, address(777), tokenId); // Act - Update a win battle record vm.prank(address(_GAME_SERVER_ADDRESS)); vm.expectRevert(); _rankedBattleContract.updateBattleRecord(0, 50, 0, 1500, true); }
Forge tests.
This issue is not trivial to address. A recommended starting point for mitigation involves assuming that:
DoS
#0 - c4-pre-sort
2024-02-24T05:15:02Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T05:15:10Z
raymondfam marked the issue as duplicate of #1641
#2 - c4-judge
2024-03-12T03:34:04Z
HickupHH3 changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-03-12T04:01:25Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-12T04:03:31Z
HickupHH3 changed the severity to 2 (Med Risk)
#5 - c4-judge
2024-03-13T10:41:11Z
HickupHH3 marked the issue as partial-50
π Selected for report: Timenov
Also found by: 0x11singh99, 0xblackskull, CodeWasp, MidgarAudits, MrPotatoMagic, Rolezn, Sabit, SovaSlava, andywer, btk, josephdara, lil_eth, merlinboii, sobieski, vnavascues
238.8948 USDC - $238.89
Judge has assessed an item in Issue #2018 as 2 risk. The relevant finding follows:
L-03 GameItems allowedBurningAddresses cannot be unset Instances
GameItems.sol Description
Function setAllowedBurningAddresses can only grant and never revoke this permission to given addresses since there is a hardcoded true.
Recommended Mitigation Steps
Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders, add a second parameter bool allowed to setAllowedBurningAddresses, to indicate whether the given address should or should not be allowed to burn game items.
L-04 FighterFarm hasStakerRole cannot be unset Instances
FighterFarm.sol Description
Function addStaker can only grant and never revoke this permission to given addresses since there is a hardcoded true.
Recommended Mitigation Steps
Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders, add a second parameter bool allowed to addStaker, to indicate whether the given address should or should not be allowed to stake.
#0 - c4-judge
2024-03-21T03:45:27Z
HickupHH3 marked the issue as duplicate of #47
#1 - c4-judge
2024-03-21T03:46:16Z
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
Instances
Description
Function transferOwnership
transfers the ownership of the smart contract to a new address in a single transaction. This is risky as errors in specifying the new address can result in unintended consequences.
Recommended Mitigation Steps
Use two-step transactions to set the new owner address, e.g. by integrating OpenZeppelin Ownable2Step.
Instances
Description
Contracts that manage administrator permissions always set the _ownerAddress
as administrator in the constructor
. However, transferOwnership
neither revokes the permission from the previous owner, nor sets the new owner as administrator.
All these contracts include a function adjustAdminAccess
for managing administrator permissions, but decoupling this from when ownership is transferred may lead to errors.
Recommended Mitigation Steps
Update isAdmin
in transferOwnership
, e.g. by setting the current (soon to be previous) owner address to false
and the new owner to true
.
allowedBurningAddresses
cannot be unsetInstances
Description
Function setAllowedBurningAddresses
can only grant and never revoke this permission to given addresses since there is a hardcoded true
.
Recommended Mitigation Steps
Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders
, add a second parameter bool allowed
to setAllowedBurningAddresses
, to indicate whether the given address should or should not be allowed to burn game items.
hasStakerRole
cannot be unsetInstances
Description
Function addStaker
can only grant and never revoke this permission to given addresses since there is a hardcoded true
.
Recommended Mitigation Steps
Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders
, add a second parameter bool allowed
to addStaker
, to indicate whether the given address should or should not be allowed to stake.
redeemMintPass
can revert due to iconsTypes
access out-of-boundsInstances
Description
Function redeemMintPass
requires that all input parameters of type array have the same length except for iconsTypes
. After that, the function traverses all the arrays in a single for
loop.
Since the function missed validating the length of iconsTypes
, it could revert unexpectedly when attempting to access an index out-of-bounds.
Recommended Mitigation Steps
Update the require
L243 so that it also checks iconsTypes.length
.
updateModel
always increments stats, even when receiving same model dataInstances
Description
Function updateModel
always increments numTrained[tokenId]
and totalNumTrained
(+1). It skips validating whether modelHash
and modelType
params are different from data currently in storage. This could be used by players to their advantage.
Recommended Mitigation Steps
Validate modelHash
and modelType
: compare given params against storage and only increment numTrained[tokenId]
and totalNumTrained
if there are differences.
spendVoltage
not protected against underflowInstances
Description
Function spendVoltage
skips validating whether ownerVoltage[spender]
is greater or equal than voltageSpent
. If voltageSpent
is greater, the subtraction operation L110 will cause a revert due to an underflow error.
Recommended Mitigation Steps
Validate that ownerVoltage[spender]
is greater or equal than voltageSpent
before performing the subtraction.
setupAirdrop
can interfere with existing approvalsInstances
Description
The Neuron contract might already have certain approvals set for specific addresses. Initiating an airdrop directly from the Neuron contract could potentially override or interfere with these existing approvals, leading to unexpected behavior.
Recommended Mitigation Steps
Move the airdrop logic onto a separate contract.
MAX_SUPPLY
can never be mintedInstances
Description
Function mint
includes a condition requiring totalSupply() + amount
to be lesser than MAX_SUPPLY
, which prevents from minting all the NRN supply.
Recommended Mitigation Steps
Use <=
operator instead of <
so that all NRN supply can be minted.
unstakeNRN
performs state changes for amount
0
Instances
Description
Function unstakeNRN
skips validating whether the amount
of NRN tokens to unstake is not 0
. However, still performs a number of state updates, setting e.g.:
stakingFactor[tokenId]
_calculatedStakingFactor[tokenId][roundId] = true
hasUnstaked[tokenId][roundId] = true
May also emit events, like Unstaked
.
Recommended Mitigation Steps
Validate that the amount
of NRN tokens to unstake is not 0
.
Instances
Description
Both functions can revert. This is because totalAccumulatedPoints
for the given round id can be 0
, resulting in a division by zero scenario.
Recommended Mitigation Steps
Validate that totalAccumulatedPoints[roundId]
is not 0
before performing the division.
updateBattleRecord
performs state changes for any battleResult
Instances
Description
According to _updateRecord
, battleResult
can only adopt 3 values: 0, 1, 2.
Function updateBattleRecord
skips validating battleResult
, which results in a number of state updates (points, voltage and totalBattles
) that may not be correct.
Recommended Mitigation Steps
Validate that battleResult
is within range.
attributes
are different from docsDescription
Contract attributes
are "head", "eyes", "mouth", "body", "hands", "feet".
Documentation attributes (see The Skin) are "hair", "eyes", "mouth", "body", "hands", "feet".
Notice HEAD vs HAIR.
Recommended Mitigation Steps
Review and update fighter attributes accordingly.
constructor
could call addAttributeDivisor
Description (and Mitigation)
Contract constructor
could call addAttributeDivisor
external
function on the same contract, instead of updating attributeToDnaDivisor
mapping. In which case, the function should be made public
.
_createNewFighter
and _createFighterBase
provide weak sources of randomness for dna
paramInstances
Description
Fighters dna
is supposed to be random and unique.
To generate fighters dna
functions above use Solidity keccak256
to calculate a hash based on different parameters. The issue is that these parameters are all deterministic and predictable, and as a result dna
lacks the inherent unpredictability associated with true randomness.
Recommended Mitigation Steps
Improve dna
generation and use a better source of randomness, e.g. ChainLink VRF.
updateModel
can update model data anytimeDescription
Function updateModel
can be called to update the fighters modelHash
and modelType
anytime. This could be used by players to their advantage.
Recommended Mitigation Steps
Change updateModel
so that modelHash
and modelType
can only be updated under certain conditions, e.g. after a timelock, after a number of wins, etc.
bool
in struct
GameItemAttributes
could be isTransferable
Description (and Mitigation)
Use isTransferable
, instead of transferable
.
_mint
hardcoded dataDescription (and Mitigation)
Review hardcoded string random
in _mint
call.
instantiate...Contract
have a misleading nameInstances
Description (and Mitigation)
Functions above do not create a new contract/instance, they only create a reference. Consider updating function names to e.g. setNeuronContract
.
setStakeAtRiskAddress
performs redundant stepDescription (and Mitigation)
It is redundant to store a reference to the StakeAtRisk Contract (L195) and the address (L194). Consider removing the address.
Instances
Description (and Mitigation)
Functions above validate msg.sender == _rankedBattleAddress
, but provide mixed feedback. Consider reverting with the same message when checking for a particular condition.
useVoltageBattery
uses hardcoded tokenId
Description
Function useVoltageBattery
uses a hardcoded 0
when referring to battery
tokenId
:
require(_gameItemsContractInstance.balanceOf(msg.sender, 0) > 0); _gameItemsContractInstance.burn(msg.sender, 0, 1);
This will work for as long as the deployment script keeps creating battery
as the first game item.
Recommended Mitigation Steps
Update the implementation so that it does not rely on hardcoded information.
#0 - raymondfam
2024-02-26T02:53:30Z
With the exception of L-07 being a false positive given pre-check already in place, the report possesses an adequate amount of generic L and NC.
#1 - c4-pre-sort
2024-02-26T02:53:34Z
raymondfam marked the issue as sufficient quality report
#2 - HickupHH3
2024-03-11T04:03:34Z
#2004: L #2001: L #1989: L
#3 - c4-judge
2024-03-14T10:37:23Z
HickupHH3 marked the issue as grade-b
#4 - vnavascues
2024-03-20T21:31:05Z
With the exception of L-07 being a false positive given pre-check already in place, the report possesses an adequate amount of generic L and NC.
I'd like to ask why L-07 is a false positive. I don't see the pre-check of the voltageSpent
parameter in the code. As far as I understand a call with spendVoltage = 101
will make the function panic 100% calls.
function spendVoltage(address spender, uint8 voltageSpent) public { require(spender == msg.sender || allowedVoltageSpenders[msg.sender]); if (ownerVoltageReplenishTime[spender] <= block.timestamp) { _replenishVoltage(spender); } ownerVoltage[spender] -= voltageSpent; emit VoltageRemaining(spender, ownerVoltage[spender]); }
#5 - vnavascues
2024-03-20T22:01:15Z
Issue NC-03 FighterFarm calls to _createNewFighter and _createFighterBase provide weak sources of randomness for dna param should qualify for M-08 Users can get benefited from DNA pseudorandomly calculation.
Although no PoC was provided, all instances were reported and the proposed solution is the same. The reason it was reported as NC is because the proposed solution involves major changes not only on the contracts design, but also workflow and business model. Thank you for your time and consideration!
#6 - vnavascues
2024-03-20T22:12:35Z
Issue L-03 GameItems allowedBurningAddresses cannot be unset should qualify for M-16 Burner role can not be revoked.
The instance reported and the proposed solution are the same.
Furthermore, for consistency, L-04 FighterFarm hasStakerRole cannot be unset should be a Medium too.