Platform: Code4rena
Start Date: 25/01/2023
Pot Size: $36,500 USDC
Total HM: 11
Participants: 173
Period: 5 days
Judge: kirk-baird
Total Solo HM: 1
Id: 208
League: ETH
Rank: 173/173
Findings: 1
Award: $0.75
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xRobocop, 0xmrhoodie, 0xngndev, AkshaySrivastav, ArmedGoose, Atarpara, Bauer, CodingNameKiki, ElKu, Garrett, HollaDieWaldfee, IllIllI, Iurii3, KIntern_NA, KmanOfficial, Lotus, M4TZ1P, MiniGlome, Ruhum, SovaSlava, bin2chen, bytes032, carrotsmuggler, cccz, chaduke, codeislight, cryptonue, doublesharp, evan, fs0c, glcanvas, gzeon, hansfriese, hihen, hl_, holme, horsefacts, ladboy233, lukris02, mahdikarimi, manikantanynala97, martin, mert_eren, mrpathfindr, omis, peakbolt, peanuts, prestoncodes, rbserver, rvierdiiev, sashik_eth, timongty, tnevler, trustindistrust, usmannk, wait, yixxas, zadaru13, zaskoh
0.7512 USDC - $0.75
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L96 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L76
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L96 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L76
The function withdrawFee(...) is called once a quest has ended (the final step in the diagram)
function withdrawFee() public onlyAdminWithdrawAfterEnd { IERC20(rewardToken).safeTransfer(protocolFeeRecipient, protocolFee()); }
modifier onlyAdminWithdrawAfterEnd() { if (block.timestamp < endTime) revert NoWithdrawDuringClaim(); _; }
I'm assuming, given the name, the modifier is supposed to be called only by the administrators after the quest has ended. However, the latter is not true. The function can be called by anyone, given the quest has ended.
Additionally, the function can be called indefinitely to drain the number of rewardTokens in ERC20Quest, because it uses the protocolFee() which is a view function that always produces the same value as an amount to be transferred.
Quest is ended, but a malicious user keeps calling withdrawFee(), essentially preventing anybody to claim their funds. Even if the protocol keeps funding back the quest, the user can keep spamming the withdrawFee function. Depending on the user's goal, he can drain protocolFeeRecipient any funds in the contract.
As a result, regular users will have difficulty claiming their tokens and getting their rewards.
Quest ends, but RH keeps spamming the withdrawFee function, essentially rugging the quest and stealing all the money.
Manual testing
First, ensure that the modifier is only callable by admins. Second, introduce a maximum period upon which users can claim tokens. After the period ends, forbid claiming. Once you forbid claiming, compute the result that is supposed to come from protocolFee().
Then, when withdrawFee() is called, check if the period has ended, set the result computed in the previous step to 0 and transfer the fee as intended.
#0 - c4-judge
2023-02-06T09:00:58Z
kirk-baird marked the issue as duplicate of #23
#1 - c4-judge
2023-02-14T08:56:17Z
kirk-baird marked the issue as satisfactory