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: 257/283
Findings: 1
Award: $0.23
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
When the roundId is too high and user is a first time reward claimer, the function call can potentially cause DoS due to out-of-gas.
In claimRewards
, the function is doing a loop for the last round msg.sender
has claimed reward, and for each round, another loop with length of round winner length is also applied. When the rounds start to build up and get high, this can potentially cause huge amount of gas usage. To an extreme point, it may even block users' reward claim because of out-of-gas or gas exceeds block gas limit.
uint256 winnersLength; uint32 claimIndex = 0; uint32 lowerBound = numRoundsClaimed[msg.sender]; for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { numRoundsClaimed[msg.sender] += 1; winnersLength = winnerAddresses[currentRound].length; for (uint32 j = 0; j < winnersLength; j++) { if (msg.sender == winnerAddresses[currentRound][j]) { _fighterFarmInstance.mintFromMergingPool( msg.sender, modelURIs[claimIndex], modelTypes[claimIndex], customAttributes[claimIndex] ); claimIndex += 1; } } }
By default, the winner count per round is set to 2 in the contract, and in the test contracts, they are updated to 3. As the round progress, it's more likely for the scenario to happen. Suppose it's current round 20, and Alice is aware that she has some rewards to claim in the merging pool contract, since this is her first time to claim rewards, to it would take around 20 * 2 = 40 total loops to complete this function. 40 loops itself can already cost huge amount of gas, and with time passes, the cost can only increase, unless each player claim rewards after each round has concluded.
Manual review
Considering adding a map to store winning info for each round, such as:
mapping(uint256 => map(address => bool)) public winnerAddresses
when checking if an address has won anything in the round:
if (winnerAddresses[currentRound][msg.sender]) { _fighterFarmInstance.mintFromMergingPool( msg.sender, modelURIs[claimIndex], modelTypes[claimIndex], customAttributes[claimIndex] ); claimIndex += 1; }
DoS
#0 - c4-pre-sort
2024-02-23T23:48:37Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T23:48:54Z
raymondfam marked the issue as duplicate of #1541
#2 - c4-judge
2024-03-11T13:00:33Z
HickupHH3 marked the issue as duplicate of #216
#3 - c4-judge
2024-03-11T13:08:46Z
HickupHH3 marked the issue as partial-50
#4 - c4-judge
2024-03-21T02:09:19Z
HickupHH3 marked the issue as satisfactory