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: 230/283
Findings: 3
Award: $1.22
π 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
Users can call safeTransferFrom
and pass data in order to skip the _ableToTransfer
function. This will allow:
The user can call safeTransferFrom
passing data which the FighterFarm.sol
does not override, skipping the _ableToTransfer
function.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {FighterOps} from "../src/FighterOps.sol"; import {Test, console} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {FighterFarm} from "../src/FighterFarm.sol"; import {Neuron} from "../src/Neuron.sol"; import {AAMintPass} from "../src/AAMintPass.sol"; import {MergingPool} from "../src/MergingPool.sol"; import {RankedBattle} from "../src/RankedBattle.sol"; import {VoltageManager} from "../src/VoltageManager.sol"; import {GameItems} from "../src/GameItems.sol"; import {AiArenaHelper} from "../src/AiArenaHelper.sol"; // import {Utilities} from "./utils/Utilities.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /// @notice Unit test for FighterFarm Contract. contract FighterFarmTest is Test { // Utilities internal _utils; // address payable[] internal _users; /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ uint8[][] internal _probabilities; address internal constant _DELEGATED_ADDRESS = 0x22F4441ad6DbD602dFdE5Cd8A38F6CAdE68860b0; address internal _ownerAddress; address internal _treasuryAddress; address internal _neuronContributorAddress; /*////////////////////////////////////////////////////////////// CONTRACT INSTANCES //////////////////////////////////////////////////////////////*/ FighterFarm internal _fighterFarmContract; AAMintPass internal _mintPassContract; MergingPool internal _mergingPoolContract; RankedBattle internal _rankedBattleContract; VoltageManager internal _voltageManagerContract; GameItems internal _gameItemsContract; AiArenaHelper internal _helperContract; Neuron internal _neuronContract; function getProb() public { _probabilities.push([25, 25, 13, 13, 9, 9]); _probabilities.push([25, 25, 13, 13, 9, 1]); _probabilities.push([25, 25, 13, 13, 9, 10]); _probabilities.push([25, 25, 13, 13, 9, 23]); _probabilities.push([25, 25, 13, 13, 9, 1]); _probabilities.push([25, 25, 13, 13, 9, 3]); } /*////////////////////////////////////////////////////////////// SETUP //////////////////////////////////////////////////////////////*/ function setUp() public { // _utils = new Utilities(); // _users = _utils.createUsers(5); _ownerAddress = address(this); _treasuryAddress = vm.addr(1); _neuronContributorAddress = vm.addr(2); getProb(); _fighterFarmContract = new FighterFarm(_ownerAddress, _DELEGATED_ADDRESS, _treasuryAddress); _helperContract = new AiArenaHelper(_probabilities); _mintPassContract = new AAMintPass(_ownerAddress, _DELEGATED_ADDRESS); _mintPassContract.setFighterFarmAddress(address(_fighterFarmContract)); _mintPassContract.setPaused(false); _gameItemsContract = new GameItems(_ownerAddress, _treasuryAddress); _voltageManagerContract = new VoltageManager(_ownerAddress, address(_gameItemsContract)); _neuronContract = new Neuron(_ownerAddress, _treasuryAddress, _neuronContributorAddress); _rankedBattleContract = new RankedBattle( _ownerAddress, address(_fighterFarmContract), _DELEGATED_ADDRESS, address(_voltageManagerContract) ); _rankedBattleContract.instantiateNeuronContract(address(_neuronContract)); _mergingPoolContract = new MergingPool(_ownerAddress, address(_rankedBattleContract), address(_fighterFarmContract)); _fighterFarmContract.setMergingPoolAddress(address(_mergingPoolContract)); _fighterFarmContract.instantiateAIArenaHelperContract(address(_helperContract)); _fighterFarmContract.instantiateMintpassContract(address(_mintPassContract)); _fighterFarmContract.instantiateNeuronContract(address(_neuronContract)); _fighterFarmContract.setMergingPoolAddress(address(_mergingPoolContract)); } /*////////////////////////////////////////////////////////////// Test //////////////////////////////////////////////////////////////*/ /// @notice Test transferring a fighter while staked. 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,"");//passes } /*////////////////////////////////////////////////////////////// HELPERS //////////////////////////////////////////////////////////////*/ /// @notice Helper function to mint an fighter nft to an address. function _mintFromMergingPool(address to) internal { vm.prank(address(_mergingPoolContract)); _fighterFarmContract.mintFromMergingPool(to, "_neuralNetHash", "original", [uint256(1), uint256(80)]); } /// @notice Helper function to fund an account with 4k $NRN tokens. function _fundUserWith4kNeuronByTreasury(address user) internal { vm.prank(_treasuryAddress); _neuronContract.transfer(user, 4_000 * 10 ** 18); assertEq(4_000 * 10 ** 18 == _neuronContract.balanceOf(user), true); } function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { // Handle the token transfer here return this.onERC721Received.selector; } }
Manual Review
Override the safeTransferFrom
with data parameter too.
ERC721
#0 - c4-pre-sort
2024-02-23T04:18:36Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-23T04:18:44Z
raymondfam marked the issue as duplicate of #54
#2 - c4-pre-sort
2024-02-23T04:47:05Z
raymondfam marked the issue as duplicate of #739
#3 - c4-pre-sort
2024-02-23T04:50:01Z
raymondfam marked the issue as sufficient quality report
#4 - c4-judge
2024-03-11T02:35:09Z
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
In GameItems.sol
only safeTransferFrom
is overwritten, leaving users able to transfer untransferable items using safeBatchTransferFrom
. This breaks a key invariant of the system so I consider it High.
Users are able to transfer any item using safeBatchTransferFrom()
by the erc1155.
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console, stdError} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {GameItems} from "../src/GameItems.sol"; import {Neuron} from "../src/Neuron.sol"; import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; contract GameItemsTest is Test { address internal constant _DELEGATED_ADDRESS = 0x22F4441ad6DbD602dFdE5Cd8A38F6CAdE68860b0; address internal _ownerAddress; address internal _treasuryAddress; address internal _neuronContributorAddress; GameItems internal _gameItemsContract; Neuron internal _neuronContract; function setUp() public { _ownerAddress = address(this); _treasuryAddress = vm.addr(1); _neuronContributorAddress = vm.addr(2); _gameItemsContract = new GameItems(_ownerAddress, _treasuryAddress); _neuronContract = new Neuron(_ownerAddress, _treasuryAddress, _neuronContributorAddress); _neuronContract.addSpender(address(_gameItemsContract)); _gameItemsContract.instantiateNeuronContract(address(_neuronContract)); _gameItemsContract.createGameItem("Battery", "https://ipfs.io/ipfs/", true, true, 10_000, 1 * 10 ** 18, 10); } function testTransferLockedItmes() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 2); //paying 2 $NRN for 2 batteries _gameItemsContract.adjustTransferability(0, false);//locking the items (,, bool transferable,,,) = _gameItemsContract.allGameItemAttributes(0); assertEq(transferable, false); vm.expectRevert(); _gameItemsContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, 2, ""); uint256[] memory ids = new uint256[](1); uint256[] memory amounts = new uint256[](1); ids[0] = 0; amounts[0] = 2; _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, ids, amounts, ""); } }
Manual review
Override the safeBatchTransferFrom
.
Token-Transfer
#0 - c4-pre-sort
2024-02-22T03:34:43Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:34:51Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:27:24Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:51:19Z
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
In the reRoll
function users are able to lie about the fighter type that they pass as the parameter. Putting the wrong value of the fighter type will change result in unexpected changes of the fighters element, Physical attributes, Generation and Weight, though it will not the change the fighter type itself. Users will also be able to use the maxRerollsAllowed
of the wrong fighter type. I consider this issue of Medium severity.
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L370 ReRoll makes no checks if the fighterType passed by the user is the same as the one of the token.
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L370
Creates a fighter base, using the fighterType
.
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L383 Creates physical attributes based on fighter type. However the dendroidBool remains the same.
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {FighterOps} from "../src/FighterOps.sol"; import {Test, console} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {FighterFarm} from "../src/FighterFarm.sol"; import {Neuron} from "../src/Neuron.sol"; import {AAMintPass} from "../src/AAMintPass.sol"; import {MergingPool} from "../src/MergingPool.sol"; import {RankedBattle} from "../src/RankedBattle.sol"; import {VoltageManager} from "../src/VoltageManager.sol"; import {GameItems} from "../src/GameItems.sol"; import {AiArenaHelper} from "../src/AiArenaHelper.sol"; // import {Utilities} from "./utils/Utilities.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /// @notice Unit test for FighterFarm Contract. contract FighterFarmTest1 is Test { // Utilities internal _utils; // address payable[] internal _users; /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ uint8[][] internal _probabilities; address internal constant _DELEGATED_ADDRESS = 0x22F4441ad6DbD602dFdE5Cd8A38F6CAdE68860b0; address internal _ownerAddress; address internal _treasuryAddress; address internal _neuronContributorAddress; /*////////////////////////////////////////////////////////////// CONTRACT INSTANCES //////////////////////////////////////////////////////////////*/ FighterFarm internal _fighterFarmContract; AAMintPass internal _mintPassContract; MergingPool internal _mergingPoolContract; RankedBattle internal _rankedBattleContract; VoltageManager internal _voltageManagerContract; GameItems internal _gameItemsContract; AiArenaHelper internal _helperContract; Neuron internal _neuronContract; function setUp() public { // _utils = new Utilities(); // _users = _utils.createUsers(5); _ownerAddress = address(this); _treasuryAddress = vm.addr(1); _neuronContributorAddress = vm.addr(2); getProb(); _fighterFarmContract = new FighterFarm(_ownerAddress, _DELEGATED_ADDRESS, _treasuryAddress); _helperContract = new AiArenaHelper(_probabilities); _mintPassContract = new AAMintPass(_ownerAddress, _DELEGATED_ADDRESS); _mintPassContract.setFighterFarmAddress(address(_fighterFarmContract)); _mintPassContract.setPaused(false); _gameItemsContract = new GameItems(_ownerAddress, _treasuryAddress); _voltageManagerContract = new VoltageManager(_ownerAddress, address(_gameItemsContract)); _neuronContract = new Neuron(_ownerAddress, _treasuryAddress, _neuronContributorAddress); _rankedBattleContract = new RankedBattle( _ownerAddress, address(_fighterFarmContract), _DELEGATED_ADDRESS, address(_voltageManagerContract) ); _rankedBattleContract.instantiateNeuronContract(address(_neuronContract)); _mergingPoolContract = new MergingPool(_ownerAddress, address(_rankedBattleContract), address(_fighterFarmContract)); _fighterFarmContract.setMergingPoolAddress(address(_mergingPoolContract)); _fighterFarmContract.instantiateAIArenaHelperContract(address(_helperContract)); _fighterFarmContract.instantiateMintpassContract(address(_mintPassContract)); _fighterFarmContract.instantiateNeuronContract(address(_neuronContract)); _fighterFarmContract.setMergingPoolAddress(address(_mergingPoolContract)); } function testReroll() public { _mintFromMergingPool(_ownerAddress); // get 4k neuron from treasury _fundUserWith4kNeuronByTreasury(_ownerAddress); // after successfully minting a fighter, update the model if (_fighterFarmContract.ownerOf(0) == _ownerAddress) { uint256 neuronBalanceBeforeReRoll = _neuronContract.balanceOf(_ownerAddress); uint8 tokenId = 0; uint8 fighterType = 0; _neuronContract.addSpender(address(_fighterFarmContract)); _fighterFarmContract.reRoll(tokenId, 1); //@audit passing the wrong fighterType function does not revert assertEq(_fighterFarmContract.numRerolls(0), 1); assertEq(neuronBalanceBeforeReRoll > _neuronContract.balanceOf(_ownerAddress), true); } } function _mintFromMergingPool(address to) internal { vm.prank(address(_mergingPoolContract)); _fighterFarmContract.mintFromMergingPool(to, "_neuralNetHash", "original", [uint256(1), uint256(80)]); } /// @notice Helper function to fund an account with 4k $NRN tokens. function _fundUserWith4kNeuronByTreasury(address user) internal { vm.prank(_treasuryAddress); _neuronContract.transfer(user, 4_000 * 10 ** 18); assertEq(4_000 * 10 ** 18 == _neuronContract.balanceOf(user), true); } }
Manual review
The fighterType should be directly taken from the tokenId, not specified by the user.
Invalid Validation
#0 - c4-pre-sort
2024-02-21T23:54:54Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-21T23:55:03Z
raymondfam marked the issue as duplicate of #17
#2 - c4-pre-sort
2024-02-22T00:33:34Z
raymondfam marked the issue as sufficient quality report
#3 - c4-pre-sort
2024-02-22T00:33:39Z
raymondfam marked the issue as not a duplicate
#4 - c4-pre-sort
2024-02-22T00:33:45Z
raymondfam marked the issue as duplicate of #305
#5 - c4-pre-sort
2024-02-22T01:04:52Z
raymondfam marked the issue as duplicate of #306
#6 - c4-judge
2024-03-05T04:31:00Z
HickupHH3 marked the issue as satisfactory
#7 - c4-judge
2024-03-19T09:05:07Z
HickupHH3 changed the severity to 3 (High Risk)