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: 214/283
Findings: 4
Award: $1.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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L539-L546 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175-L183
Staked fighers generated by FighterFarm can be transferred, even though it is not supposed to be transferrable while being staked.
The FighterFarm contract tracks the status of token IDs, ie, fighters being staked and disallows their transfer while being staked through the FighterFarm#_ableToTransfer hook, which is inserted in both transferFrom and safeTransferFrom functions. However, ERC721 contains an overloaded safeTransferFrom function with a different function signature (function safeTransferFrom(address from,address to,uint256 tokenId,bytes memory data)). Any user can transfer their staked fighter token by calling this overloaded function.
This is demonstrated by the following test PoC which can be added to the FighterFarm.t.sol test contract and can be executed in foundry :
function testStakingFighterExploit() public { _mintFromMergingPool(_ownerAddress); _fighterFarmContract.addStaker(_ownerAddress); _fighterFarmContract.updateFighterStaking(0, true); assertEq(_fighterFarmContract.fighterStaked(0), true); vm.prank(_ownerAddress); _fighterFarmContract.safeTransferFrom(_ownerAddress, address(1), 0, bytes("")); //if this works, fighter would be still marked as being staked assertEq(_fighterFarmContract.fighterStaked(0), true); //checking owner of tokenID 0 which got transferred assertEq(_fighterFarmContract.ownerOf(0),address(1)); }
The function would revert if the tokenID was not transferred. However, we are successful in transferring it.
Manual Review
Add the _ableToTransfer hook to all transfer functions
Other
#0 - c4-pre-sort
2024-02-23T04:34:30Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-23T04:34:40Z
raymondfam marked the issue as duplicate of #54
#2 - c4-pre-sort
2024-02-23T04:47:18Z
raymondfam marked the issue as duplicate of #739
#3 - c4-pre-sort
2024-02-23T04:53:01Z
raymondfam marked the issue as sufficient quality report
#4 - c4-judge
2024-03-11T02:40:28Z
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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L301 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC1155/ERC1155.sol#L134-L146
Non-transferrable game items can be transferred by using the safeBatchTransfer function in GameItems contract.
The GameItems contract facilitates the buying of GameItems within AIArena and places certain restrictions depending on the type of items- it could be transferrability, allowance per day, etc. It is an ERC1155-inherited contract which overrides the safeTransferFrom function and inserts a hook to check if the current tokenId (Game Item type) is transferrable or not. If it is not, the function call reverts. However, this can be easily bypassed by calling the ERC1155 function safeBatchTransferFrom, which is inherited by the contract, but not overrided to include the appropriate hook.
This is showcased by the following code PoC which can be added to GameItems.t.sol :
function testExploitNonTransferrable() public { _gameItemsContract.createGameItem("something", "https://ipfs.io/ipfs/", true, false, 10_000, 1 * 10 ** 18, 10); _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(1, 10); uint256[] memory a = new uint256[](1); uint256[] memory b = new uint256[](1); a[0] = 1; b[0] =10; _gameItemsContract.safeBatchTransferFrom(_ownerAddress, address(1), a, b, bytes("")); }
This function call does not revert and successfully conducts the transfer. Thus, a potential exploit.
Manual Review.
Override all transfer functions and add the appropriate hook.
Other
#0 - c4-pre-sort
2024-02-22T03:47:42Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:47:49Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:27:52Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:53:11Z
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/AAMintPass.sol#L109-L133 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L233-L262
The AAMintPass does not differentiate between AI Champion and Dendroid mintpasses and results in enabling users to mint any fighter type they want with a mint pass. FighterFarm#redeemMintPass cannot verify what kind of fighter type is associated with a mint pass.
The AAMintPass#claimMintPass function takes in a uint8[2] parameter numToMint which contains the number of AI Champion fighters to mint and the number of Dendroid mintpasses to mint in its 0th and 1st indices respectively. Now, after the function verifies that the signature provided by the user is indeed signed by the delegated address, it sums the total number of mint passes to be generated here. The contract then mints the total number of mint passes to the appropriate EOA account.
However, there is no distinction between the two fighter types while minting the pass. This means that any contract which accepts this pass will not be able to validate whether a pass is for an AI Champion fighter or a dendroid. In fact, we can see how FighterFarm#redeemMintPass takes in parameters from the user here. The redeemMintPass function takes in a uint8[] parameter fighterTypes
which contains the fighter type to be associated to each mint pass. This means that even if a user has been allocated only AI Champion mint passes, they can mint dendroid fighters.
The redeemMintPass allows the user to mint any type of fighter they want, and this is facilitated by the mint pass not carrying any sort of identification as to which kind of fighter type it is meant for. Since dendroids are supposed to be a more rarer variety, this can result in unintended consequences, such as the dendroids' rarity being diluted by malicious users.
Manual Review
There are many ways to mitigate this. The easiest one would be this :
Other
#0 - c4-pre-sort
2024-02-22T07:41:35Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T07:41:42Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:53:15Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-06T03:09:11Z
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
0.5612 USDC - $0.56
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L471 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L129-L134
Lack of functions for updation of FighterFarm#numElements variable will make FighterFarm contract unusable in later generations of fighters.
The number of elements for each generation is stored in a mapping(uint8 => uint8) numElements variable in the FighterFarm contract. The number of elements for the 0th generation is set to 3 in the contract's constructor. This is then later used in the FighterFarm#_createFighterBase function for calculating the element of the fighter.
However, on later generations, this will revert and the FighterFarm contract will be rendered unusable. This is because the contract does not have a function to update the number of elements for later generations. In other words, after incrementing a fighter's generation through the FighterFarm#incrementGeneration function, this operation in _createFighterBase will revert:
uint256 element = dna % numElements[generation[fighterType]];
This is because numElements[generation[fighterType]] will return 0 when generation > 0.
If needed, this coded PoC can be added to FighterFarm.t.sol to confirm the issue :
function testNumGenError() public { _mintFromMergingPool(_ownerAddress); _fundUserWith4kNeuronByTreasury(_ownerAddress); _fighterFarmContract.incrementGeneration(0); _neuronContract.addSpender(address(_fighterFarmContract)); vm.expectRevert(); _fighterFarmContract.reRoll(0, 0); }
Manual Review
Other
#0 - c4-pre-sort
2024-02-22T18:28:19Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T18:28:27Z
raymondfam marked the issue as duplicate of #45
#2 - HickupHH3
2024-03-07T06:57:25Z
doesn't explain why it reverts
#3 - c4-judge
2024-03-07T06:57:29Z
HickupHH3 marked the issue as partial-50