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: 211/283
Findings: 1
Award: $2.06
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
Allows player at the start of each round to generate a risk free position by setting accumulatedPointsPerFighter
greater than 0 by winning a few battles.
The accumulatedPointsPerFighter
stores points accumulated by the fighter when the fighter wins a battle.
If we have a closer look at the the _addResultPoints
function, inside the condition where the fighter loses the battle, there's a if block
which checks if the accumulatedPointsPerFighter
is greater than 0.
File: src/RankedBattle.sol 479: if (accumulatedPointsPerFighter[tokenId][roundId] > 0) { 480: /// If the fighter has a positive point balance for this round, deduct points 481: points = stakingFactor[tokenId] * eloFactor; 482: if (points > accumulatedPointsPerFighter[tokenId][roundId]) { 483: points = accumulatedPointsPerFighter[tokenId][roundId]; 484: } 485: accumulatedPointsPerFighter[tokenId][roundId] -= points; 486: accumulatedPointsPerAddress[fighterOwner][roundId] -= points; 487: totalAccumulatedPoints[roundId] -= points; 488: if (points > 0) { 489: emit PointsChanged(tokenId, points, false); 490: } 491: } else {
This can be used as a way to gamify the points system, where a player would stake a very minimal amount of NRN and all he needs to do is gain a positive accumulatedPointsPerFighter
which can be achieved by playing a few battles.
Once the accumulatedPointsPerFighter
is greater than 0, the player would stake a very high amount of NRNs by using the RankedBattle::stakeNRN
function.
The next round for the player is essentially a risk free game, in case the player loses the next battle, that would result in a very minimal point loss as the points earned were on the basis of previous staked amount.
But, if he wins, his stakingFactor
would be too large and as soon as the battle was finished, the player can choose to immediately unstake his NRNs and leave, also unlocks the potential mis-use of this by using several accounts to play the game where the NRNs are transferred from one account to another as long as he can keep replicating the above position.
This also opens a way to have a significantly higher chance of getting an select for the NFT mint via merging pool as during winning condition the player can decide to use 100% of his mergingPortion
.
An example for this scenario:
accumulatedPointsPerFighter
to be greater than 0.The test below is written inside the file src/test/RankedBattle.t.sol
.
File: src/test/RankedBattle.t.sol function testUpdateBattleRecordPlayerFavourableBet() public { address playerA = vm.addr(3); address playerB = vm.addr(4); _mintFromMergingPool(playerA); _fundUserWith4kNeuronByTreasury(playerA); _mintFromMergingPool(playerB); _fundUserWith4kNeuronByTreasury(playerB); vm.prank(playerA); _rankedBattleContract.stakeNRN(1 * 10 ** 18, 0); vm.prank(playerB); _rankedBattleContract.stakeNRN(1 * 10 ** 18, 1); assertEq(_rankedBattleContract.amountStaked(0), 1 * 10 ** 18); // Player A staked 1 NRN assertEq(_rankedBattleContract.amountStaked(1), 1 * 10 ** 18); // Player B staked 1 NRN vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(0, 0, 0, 1500, true); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0) == 1500, true); // Player A accumulated points is 1500 by winning the battle vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(1, 0, 0, 1500, true); assertEq(_rankedBattleContract.accumulatedPointsPerFighter(1, 0) == 1500, true); // Player B accumulated points is 1500 by winning the battle // The favorable condition was met and the accumulated points for both players is 1500 (assuming stakingFactor as 1 and eloFactor as 1500) // Now the players can stake abysmally high amounts and still not lose any points, for simplicity we will stake 3999 NRN. vm.prank(playerA); _rankedBattleContract.stakeNRN(3_999 * 10 ** 18, 0); vm.prank(playerB); _rankedBattleContract.stakeNRN(3_999 * 10 ** 18, 1); vm.prank(address(_GAME_SERVER_ADDRESS)); // Losing Scenario _rankedBattleContract.updateBattleRecord(0, 0, 2, 1500, true); // Player A lost assertEq(_rankedBattleContract.accumulatedPointsPerFighter(0, 0) == 0, true); // Player A accumulated points is 0 and assertEq(_rankedBattleContract.amountStaked(0) == 4_000 * 10 ** 18, true); // Player A staked amount is 4000 NRN, only minimal amount of points (1500) were lost vm.prank(address(_GAME_SERVER_ADDRESS)); // Winning Scenario _rankedBattleContract.updateBattleRecord(1, 0, 0, 1500, true); // Player B won assertEq(_rankedBattleContract.accumulatedPointsPerFighter(1, 0) == 96000, true); // Player B accumulated points is now 96000 assertEq(_rankedBattleContract.amountStaked(1) == 4_000 * 10 ** 18, true); // Player B staked amount is 4000 NRN // After this battle, Both players can withdraw their Stakes and gamify the system even more by creating new accounts or rejoining from old accounts // This process can be repeated in every round, putting the system at risk of being gamed. }
Foundry
A possible solution would be to maintain a mapping of excessive points so that it can be used to slash NRNs during claims in future.
Other
#0 - c4-pre-sort
2024-02-22T17:27:53Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T17:28:01Z
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:08Z
HickupHH3 marked the issue as satisfactory