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: 262/283
Findings: 2
Award: $0.10
π 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/FighterFarm.sol#L355 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L338
The fighter nft is transferable by default but it cannot be transferred when a user has staked it, this staking occurs when a user create a stake in order to play a game. The transferFrom and saferFrom has the _ableToTransfer(uint256 tokenId, address to), this functions checks three conditions before it can transfer the fighter to another user, the conditions are owner of the nft, maximum number of fighter nft held by the user and if the fighter is currently staked.
The problem arise from the fact that the function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) was not taken into consideration, the FighterFarm.sol inherits the ERC721 openzepplin contract, the mentioned safeTransferFrom is different from the function safeTransferFrom( address from, address to,uint256 tokenId), the first has 4 parameters while the second has 3 parameters. The safeFrom with 3 parameters and the transferFrom has the _ableToTransfer(uint256 tokenId, address to) check but the safeTransferFrom with 4 parameters do not have this check which allows anyone one to transfer their fighter nft to others even when the fighter nft is staked.
function testTransferringFighter1() public { // create two users address user1 = vm.addr(5); address user2 = vm.addr(6); // fund user1 with neuron tokens _mintFromMergingPool(user1); // Add staker role to user1 _fighterFarmContract.addStaker(user1); // update user1 fighter staking status vm.prank(user1); _fighterFarmContract.updateFighterStaking(0, true); assertEq(_fighterFarmContract.fighterStaked(0), true); // Send fighter nft to user2 vm.prank(user1); _fighterFarmContract.safeTransferFrom(user1, user2, 0, ""); // Check if user2 has the fighter nft assertEq(_fighterFarmContract.ownerOf(0) == user2, true); }
Manual Review
Add the _ableToTransfer(uint256 tokenId, address to) to safeTransferFrom(address from,address to, uint256 tokenId, bytes memory data) Reviewing all safeTransferFrom paths to ensure consistency
Other
#0 - c4-pre-sort
2024-02-23T05:05:05Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:05:17Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:09:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-11T02:41:43Z
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
When creating a game item, an admin can set the transferability of the game item to true or false, game items that are set to true can be transfered from one user to another and game items set to false cannot be transfered. According to the design this should be fine as the safeTransferFrom has a check to know if the game item is transferable or not.
require(allGameItemAttributes[tokenId].transferable)
The problem arise from the fact that ERC1155 also has a safeBatchTransferFrom which the GameItems.sol contract inherited and it was not overwritten to have a require statement to check if the game is transferable.
The main impacts of this issue are:
Users can freely transfer game items meant to be locked Breaches game design intentions and incentives Harms fairness and integrity of the game economy
function testTransferNonTransferableGameItems() public { // create two users address user1 = vm.addr(5); address user2 = vm.addr(6); // fund user1 with neuron tokens _fundUserWith4kNeuronByTreasury(user1); // create a new game item and set the transferability to false _gameItemsContract.createGameItem( "Gloves", "https://ipfs.io/ipfs/gloves", false, false, 10_000, 1 * 10 ** 18, 10 ); //Mint the new game item to user1 vm.prank(user1); _gameItemsContract.mint(1, 1); // create a new array with one item, the item is an array containing the token id, it will also be used as an array containing the number of items. uint256[] memory arr1 = new uint256[](1); arr1[0] = 1; // User 1 sends the game item to users vm.prank(user1); _gameItemsContract.safeBatchTransferFrom(user1, user2, arr1, arr1, ""); // checks that user2 has the game item sent by user1 assertEq(_gameItemsContract.balanceOf(user2, 1), 1); }
Manual Review
To address this, I recommend:
Adding the require(allGameItemAttributes[tokenId].transferable) to safeBatchTransferFrom() Reviewing all safeTransferFrom paths to ensure consistency Adding unit tests for non-transferable item restrictions
Fixing this oversight will properly enforce game item transferability rules as intended.
Other
#0 - c4-pre-sort
2024-02-22T03:58:00Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:58:08Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:28:13Z
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:53:46Z
HickupHH3 marked the issue as satisfactory