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: 245/283
Findings: 3
Award: $0.33
π 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#L539 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175
Users can bypass _ableToTransfer
restriction within FighterFarm
, due to not overwritten ERC721 method
Before every transfer, there are certain checks which are supposed to be made - that the receiver does not exceed the MAX_FIGHTERS_ALLOWED and that the ERC721 transferred is not currently staked.
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); } }
In order to enforce these restrictions, the transferFrom(address,address,uint256)
and safeTransferFrom(address,address,uint256)
ERC721 methods are overwritten.
function transferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _transfer(from, to, tokenId); }
function safeTransferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _safeTransfer(from, to, tokenId, ""); }
However, the inherited ERC721 OZ contract also has a third method for transferring tokens, which is not overwritten - safeTransferFrom(address,address,uint256,bytes)
Because the method is not overwritten, any user can use it and bypass all restrictions, allowing them to send staked tokens and bypass max NFT limit
function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved"); _safeTransfer(from, to, tokenId, data); }
Manual review
overwrite the safeTransferFrom(address,address,uint256,bytes)
method
ERC721
#0 - c4-pre-sort
2024-02-23T05:09:01Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:09:11Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:44:32Z
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
Users can transfer game items marked as non-transferable
The GameItems
contract inherits ERC1155. Since some items, can be marked as non-transferable, the safeTransferFrom
method is overwritten, so that it first performs a check whether the item has transferable
set to false.
function safeTransferFrom( address from, address to, uint256 tokenId, uint256 amount, bytes memory data ) public override(ERC1155) { require(allGameItemAttributes[tokenId].transferable); super.safeTransferFrom(from, to, tokenId, amount, data); }
However,ERC1155 also has another method which allows transferring assets - safeBatchTransferFrom
. This method is not overwritten and does not perform the necessary transferable
check, allowing any user to send non-transferable assets freely.
Manual review
Overwrite the safeBatchTransferFrom
Add to GameItems.t.sol
function testAdjustTransferabilityFromOwner() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 2); //paying 2 $NRN for 2 batteries _gameItemsContract.adjustTransferability(0, false); (,, bool transferable,,,) = _gameItemsContract.allGameItemAttributes(0); assertEq(transferable, false); uint256[] memory ids = new uint256[](1); uint256[] memory amounts = new uint256[](1); amounts[0] = 2; address randomAddr = address(2); assertEq(2, _gameItemsContract.balanceOf(_ownerAddress, 0)); assertEq(0, _gameItemsContract.balanceOf(randomAddr, 0)); _gameItemsContract.safeBatchTransferFrom(_ownerAddress, randomAddr, ids, amounts, ""); assertEq(0, _gameItemsContract.balanceOf(_ownerAddress, 0)); assertEq(2, _gameItemsContract.balanceOf(randomAddr, 0)); (,, transferable,,,) = _gameItemsContract.allGameItemAttributes(0); assertEq(transferable, false); }
Error
#0 - c4-pre-sort
2024-02-22T04:01:02Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T04:01:12Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:28:18Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:54:24Z
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/RankedBattle.sol#L294
Users won't be able to claim their rewards.
In order to claim their rewards, users have to call claimNRN
. What it does is it loops over all rounds (since 0) and calculates the user's rewards. The problem is that it even goes through the rounds when the user has not had a stake whatsoever.
This could be problematic if a user deposits after a lot of rounds have gone by (e.g. there's been 10,000 rounds prior). In order to claim any reward, the function would have to loop through all 10,000 rounds, leading to OOG issues.
function claimNRN() external { require(numRoundsClaimed[msg.sender] < roundId, "Already claimed NRNs for this period"); uint256 claimableNRN = 0; uint256 nrnDistribution; uint32 lowerBound = numRoundsClaimed[msg.sender]; for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { nrnDistribution = getNrnDistribution(currentRound); claimableNRN += ( accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution ) / totalAccumulatedPoints[currentRound]; numRoundsClaimed[msg.sender] += 1; } if (claimableNRN > 0) { amountClaimed[msg.sender] += claimableNRN; _neuronInstance.mint(msg.sender, claimableNRN); emit Claimed(msg.sender, claimableNRN); } }
Manual review
When a user stakes for the first time, set numRoundsClaimed[user]
value to the current round
Other
#0 - c4-pre-sort
2024-02-25T02:25:47Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-25T02:25:56Z
raymondfam marked the issue as duplicate of #1541
#2 - c4-judge
2024-03-11T13:01:02Z
HickupHH3 marked the issue as duplicate of #216
#3 - c4-judge
2024-03-12T02:44:21Z
HickupHH3 marked the issue as partial-50
#4 - c4-judge
2024-03-21T02:11:48Z
HickupHH3 marked the issue as satisfactory