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: 199/283
Findings: 3
Award: $3.07
π Selected for report: 0
π Solo Findings: 0
π 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#L301
safeBatchTransferFrom
which was not overidden with the custom logicfunction testBatchSafeTransferFrom() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 1); uint256[] memory ids = new uint256[](1); uint256[] memory values = new uint256[](1); ids[0]=0; values[0]=1; _gameItemsContract.adjustTransferability(0,false); vm.expectRevert(); _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, ids, values, ""); }
Result: [FAIL. Reason: Call did not revert as expected] testBatchSafeTransferFrom() (gas: 221497)
Override the safeBatchTransferFrom
function and add the restriction of failing non transferrable items
function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data ) public override(ERC1155) { require(allGameItemAttributes[tokenId].transferable); super.safeBatchTransferFrom(from, to, ids, values, data); }
Token-Transfer
#0 - c4-pre-sort
2024-02-22T03:32:23Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:32:30Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:27:20Z
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:51:13Z
HickupHH3 marked the issue as satisfactory
π Selected for report: t0x1c
Also found by: 0rpse, 0xAadi, 0xBinChook, 0xCiphky, 0xDetermination, 14si2o_Flint, AC000123, Aamir, Abdessamed, Blank_Space, CodeWasp, DanielArmstrong, DarkTower, Draiakoo, Honour, Kalogerone, Krace, McToady, Merulez99, MidgarAudits, MrPotatoMagic, PedroZurdo, Silvermist, Tychai0s, VAD37, Velislav4o, VrONTg, WoolCentaur, YouCrossTheLineAlfie, ZanyBonzy, alexxander, aslanbek, btk, csanuragjain, d3e4, dimulski, djxploit, erosjohn, evmboi32, fnanni, forgebyola, forkforkdog, handsomegiraffe, immeas, israeladelaja, juancito, ktg, n0kto, neocrao, ni8mare, okolicodes, peanuts, petro_1912, shaflow2, shaka, swizz, ubermensch, ubl4nk, yotov721
2.0593 USDC - $2.06
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L416
Users can ensure no loss with high reward ratio, making the game unfair. This happens since even the smallest + points are enough to absorb a defeat with huge stake
function testNegligibleLoss() public { address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); // staking smallest possible value 1 wei of token _rankedBattleContract.stakeNRN(1, 0); assertEq(_rankedBattleContract.amountStaked(0), 1); // Game server mark this battle as won vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1500, true); // Points will be 1 (staking Factor) * 1500 (eloFactor) assertEq(_rankedBattleContract.accumulatedPointsPerFighter(tokenId, 0) == 1500, true); // Player stakes huge fund vm.prank(player); _rankedBattleContract.stakeNRN(3_000 * 10 ** 18, 0); // Battle is lost vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 2, 1500, true); // Bug: No fund loss, only the minimal points obtained are lost but fund are safe // Bug: If User directly started with this much fund, he would have lost fund assertEq(_rankedBattleContract.accumulatedPointsPerFighter(tokenId, 0) == 0, true); assertEq(_stakeAtRiskContract.getStakeAtRisk(tokenId) > 0, true); }
Outcome: [FAIL. Reason: Assertion failed.] testNegligibleLoss() (gas: 829347)
function testHighProfitNoRisk() public { address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); // staking smallest possible value 1 wei of token _rankedBattleContract.stakeNRN(1, 0); assertEq(_rankedBattleContract.amountStaked(0), 1); // Game server mark this battle as won vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1500, true); // Points will be 1 (staking Factor) * 1500 (eloFactor) assertEq(_rankedBattleContract.accumulatedPointsPerFighter(tokenId, 0) == 1500, true); // Player stakes huge fund vm.prank(player); _rankedBattleContract.stakeNRN(2_500 * 10 ** 18, 0); vm.prank(address(_GAME_SERVER_ADDRESS)); // Battle won _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1500, true); //1500(from old win) + sqrt(2500)*1500 = 76500 earned with no risk assertEq(_rankedBattleContract.accumulatedPointsPerFighter(tokenId, 0) == 76500, true); }
In case of User losing game, his points for current stake amount should be deduced. if existing positive accumulatedPointsPerFighter is < deduced current points then user curStakeAtRisk should be moved and user staked amount should be reduced
Other
#0 - c4-pre-sort
2024-02-22T15:28:28Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T15:28:37Z
raymondfam marked the issue as duplicate of #38
#2 - raymondfam
2024-02-22T15:29:18Z
Points would be zero if at risk penalty were to kick in.
#3 - c4-judge
2024-03-07T02:58:22Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-07T03:12:06Z
HickupHH3 marked the issue as satisfactory
#5 - c4-judge
2024-03-07T03:25:48Z
HickupHH3 marked the issue as partial-75
#6 - c4-judge
2024-03-07T03:32:32Z
HickupHH3 marked the issue as satisfactory
π Selected for report: t0x1c
Also found by: 0rpse, 0xAadi, 0xBinChook, 0xCiphky, 0xDetermination, 14si2o_Flint, AC000123, Aamir, Abdessamed, Blank_Space, CodeWasp, DanielArmstrong, DarkTower, Draiakoo, Honour, Kalogerone, Krace, McToady, Merulez99, MidgarAudits, MrPotatoMagic, PedroZurdo, Silvermist, Tychai0s, VAD37, Velislav4o, VrONTg, WoolCentaur, YouCrossTheLineAlfie, ZanyBonzy, alexxander, aslanbek, btk, csanuragjain, d3e4, dimulski, djxploit, erosjohn, evmboi32, fnanni, forgebyola, forkforkdog, handsomegiraffe, immeas, israeladelaja, juancito, ktg, n0kto, neocrao, ni8mare, okolicodes, peanuts, petro_1912, shaflow2, shaka, swizz, ubermensch, ubl4nk, yotov721
2.0593 USDC - $2.06
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L531
It seems that point earned for 1 wei and 1e18 NRN token amount would be same. However 1 wei amount of token causes 0 loss even on losing game which is not the case with 1e18 amount tokens This happens since stakingFactor_ is always rounded up to 1 when 0
uint256 stakingFactor_ = FixedPointMathLib.sqrt( (amountStaked[tokenId] + stakeAtRisk) / 10**18 ); // 1/10**18 = 0
if (stakingFactor_ == 0) { stakingFactor_ = 1; }
points = stakingFactor[tokenId] * eloFactor;
curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10**4; // 10*1/10**4 = 0
Below POC show same point for 1 and 1e18 amount token
function testSameGain() public { address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); // staking smallest possible value 1 wei of token _rankedBattleContract.stakeNRN(1, 0); assertEq(_rankedBattleContract.amountStaked(0), 1); // Game server is going to mark the battle as won vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1500, true); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0) == 1500, true); vm.prank(player); _rankedBattleContract.unstakeNRN(1, 0); _rankedBattleContract.setNewRound(); vm.prank(player); // staking smallest possible value 1 wei of token _rankedBattleContract.stakeNRN(1*10**18, 0); assertEq(_rankedBattleContract.amountStaked(0), 1*10**18); // Game server is going to mark the battle as won vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1500, true); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0) == 1500, true); }
A minimum staking amount requirement should be placed which will prevent such small deposits
Other
#0 - c4-pre-sort
2024-02-24T08:08:51Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T08:09:08Z
raymondfam marked the issue as duplicate of #38
#2 - c4-judge
2024-03-07T02:58:22Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-07T03:40:11Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0xAlix2, 0xCiphky, 0xDetermination, 0xG0P1, 0xMosh, 0xabhay, 14si2o_Flint, AlexCzm, Aymen0909, CodeWasp, DanielArmstrong, FloatingPragma, Giorgio, JCN, Jorgect, Kalogerone, KmanOfficial, Kow, KupiaSec, McToady, SpicyMeatball, VAD37, WoolCentaur, ZanyBonzy, alexxander, alexzoid, almurhasan, blutorque, csanuragjain, denzi_, dipp, djxploit, evmboi32, handsomegiraffe, haxatron, immeas, jesjupyter, ke1caM, klau5, lanrebayode77, lil_eth, merlin, merlinboii, nuthan2x, peanuts, shaflow2, shaka, sl1, solmaxis69, stakog, swizz, t0x1c, tallo, ubermensch, vnavascues, yotov721
1.0089 USDC - $1.01
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L342
If you lost a game which moved the stake to stakeAtRisk contract then you can recover those funds without risking any more funds. Even if you lose, you wont lose more funds but if you win you will start recovering back the funds
bool success = _neuronInstance.transfer(_stakeAtRiskAddress, curStakeAtRisk); if (success) { _stakeAtRiskInstance.updateAtRiskRecords(curStakeAtRisk, tokenId, fighterOwner); amountStaked[tokenId] -= curStakeAtRisk; }
if (curStakeAtRisk > 0) { _stakeAtRiskInstance.reclaimNRN(curStakeAtRisk, tokenId, fighterOwner); amountStaked[tokenId] += curStakeAtRisk; }
Below test fails since balance does not decrease on losing game
function testUseRiskFund() public { address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); // stake amount _rankedBattleContract.stakeNRN(2500*10**18, 0); // Game server is going to mark the battle as lost vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 2, 1, true); assertEq(_stakeAtRiskContract.getStakeAtRisk(0), 2500*10**15); vm.prank(player); // unstake amount _rankedBattleContract.unstakeNRN((2500*10**18)-(2500*10**15), 0); assertEq(_rankedBattleContract.amountStaked(0), 0); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0), 0); // Game server is going to mark the battle as lost vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 2, 1, true); // no loss for user // this fails vm.expectRevert(); assertEq(_stakeAtRiskContract.getStakeAtRisk(0), 2500*10**15); assertEq(_rankedBattleContract.amountStaked(0), 0); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0), 0); // Game server is going to mark the battle as won vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 0, 0, 1, true); // profit from win gets added assertEq(_stakeAtRiskContract.getStakeAtRisk(0), (2500*10**15)-(2500*10**12)); assertEq(_rankedBattleContract.amountStaked(0), 2500*10**12); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0), 0); }
Result: [FAIL. Reason: Call did not revert as expected] testUseRiskFund() (gas: 934506)
Revise the below line:
-- if (amountStaked[tokenId] + stakeAtRisk > 0) { -- _addResultPoints(battleResult, tokenId, eloFactor, mergingPortion, fighterOwner); ++ if (amountStaked[tokenId] > 0) { ++ _addResultPoints(battleResult, tokenId, eloFactor, mergingPortion, fighterOwner);
Other
#0 - c4-pre-sort
2024-02-24T08:18:44Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T08:19:02Z
raymondfam marked the issue as duplicate of #833
#2 - c4-judge
2024-03-13T11:32:25Z
HickupHH3 marked the issue as duplicate of #1641
#3 - c4-judge
2024-03-14T06:22:10Z
HickupHH3 marked the issue as satisfactory