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: 32/173
Findings: 3
Award: $140.90
🌟 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
Function withdrawFee() can be called multilple times, due to the lack of a check to ensure that it can only be called once.
In cases where this function is accidently called multiple times, it causes the balance in the contract to decrease in value, which may impact the amount of rewards that users can claim or the amount of nonClaimableTokens that quest deployer can withdraw (in the case where function withdrawFee() is called before the claims are completed/ nonClaimableTokens are fully withdrawn).
It would be administratively cumbersome to have to then retrieve these excess funds from protocolFeeRecipient, and transfer it to the correct receipents.
Manual review
Include a check in the said function to ensure it can only be called once.
#0 - c4-judge
2023-02-06T07:58:49Z
kirk-baird marked the issue as duplicate of #23
#1 - c4-judge
2023-02-14T08:54:38Z
kirk-baird changed the severity to 3 (High Risk)
#2 - c4-judge
2023-02-14T08:56:37Z
kirk-baird marked the issue as satisfactory
🌟 Selected for report: carrotsmuggler
Also found by: AkshaySrivastav, ElKu, HollaDieWaldfee, Iurii3, KmanOfficial, adriro, bin2chen, evan, hansfriese, hl_, mert_eren, omis, peanuts
122.948 USDC - $122.95
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L85 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102-L104
In Erc20Quest.sol, protocol fee would be deducted twice if admin calls function withdrawFee() (line 102) before function withdrawRemainingTokens() (line 81). This will leave quest deployer withdrawing lesser amount of nonClaimableTokens than intended.
When function withdrawFee() is called, the protocolFee is transferred to the protocolFeeRecipient. The balance in the contract decreases accordingly:
When function withdrawRemainingTokens() is called subsequently, the balance of address(this) has already been reduced by protocolFee amount (previously transferred to the protocolFeeRecipient). However, the formula once again deducts the protocolFee. This results in the value of nonClaimableTokens to be lesser than it should be.
uint256 nonClaimableTokens = IERC20(rewardToken).balanceOf(address(this)) - protocolFee() - unclaimedTokens
Manual review
Include check to ensure that function withdrawFee() is called after function withdrawRemainingTokens().
#0 - c4-judge
2023-02-05T12:19:21Z
kirk-baird marked the issue as duplicate of #42
#1 - c4-judge
2023-02-06T08:27:31Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2023-02-06T08:27:43Z
kirk-baird marked the issue as duplicate of #61
#3 - c4-judge
2023-02-14T10:00:46Z
kirk-baird marked the issue as satisfactory
#4 - c4-judge
2023-02-20T09:32:40Z
kirk-baird changed the severity to 3 (High Risk)
#5 - c4-judge
2023-02-23T23:48:12Z
kirk-baird changed the severity to 2 (Med Risk)
🌟 Selected for report: CodingNameKiki
Also found by: 0x1f8b, 0x4non, 0x5rings, 0xAgro, 0xMirce, 0xRobocop, 0xSmartContract, 0xackermann, AkshaySrivastav, ArmedGoose, Aymen0909, BClabs, Breeje, Dewaxindo, Diana, HollaDieWaldfee, IceBear, IllIllI, Iurii3, Josiah, Krayt, PaludoX0, Phenomana, PrasadLak, RaymondFam, Rolezn, SaeedAlipoor01988, SaharDevep, SleepingBugs, adriro, arialblack14, bin2chen, brevis, btk, carlitox477, carrotsmuggler, catellatech, chaduke, chrisdior4, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, fellows, glcanvas, halden, hl_, horsefacts, jat, joestakey, kenta, libratus, lukris02, luxartvinsec, manikantanynala97, martin, matrix_0wl, nadin, nicobevi, oberon, peanuts, prestoncodes, rbserver, sakshamguruji, sashik_eth, sayan, seeu, thekmj, tnevler, trustindistrust, tsvetanovv, xAriextz, zadaru13, zaskoh
17.196 USDC - $17.20
bytes.concat()
instead of abi.encodePacked()
safeTransferOwnership
instead of transferOwnership
Contracts should be deployed with the same compiler version that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using for e.g. a new compiler version which is not thoroughly tested, hence introducing bugs that affect the contract system negatively. https://swcregistry.io/docs/SWC-103
bytes.concat()
instead of abi.encodePacked()
bytes.concat()
can be used instead of abi.encodePacked()
since version 0.8.4 for appending bytes.
Noted that the exact functions are not imported in various contracts. It is best practice to import exact functions rather than just the whole contracts.
For example:
As best practice, critical events should be indexed.
In ERC20Quest.sol, the variable name "nonClaimableTokens" in line 85 could be confused with the variable name in line 84 "unclaimedTokens".
Given "nonClaimableTokens" is in relation to tokens that cannot be claimed due to unsuccessful quest, it is recommended to rename it to something more distinct from "unclaimedTokens". For example, "FailedQuestTokens".
Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment.
Per solidity's documentation style guide, ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier.
Functions should be grouped according to their visibility and ordered:
constructor fallback function (if exists) external public internal private
For example (public comes after internal here where it should be the other way round):
Noted per the contest scoping details that the overall line coverage percentage provided by tests is 89%.
As best practice, it should be 100%.
safeTransferOwnership
instead of transferOwnership
transferOwnership
function is used in QuestFactory.sol
It is more secure to use a 2-stage ownership transfer i.e. safeTransferOwnership
Noted quest endTime in Quest.sol is not clearly indicated to users. This means that user can still do the quest after it ends, but they do not get any rewards. This may grieve and frustrate users, which in turn affects the reputation and reliability of RabbitHole.
In QuestFactory.sol, there is a missing check to ensure that endTime happens after startTime. It is best practice to incorporate this check into the contract to mitigate instances of human errors.
In QuestFactory.sol, zero address checks are missing. It is best practice to incorporate this check into the contract.
#0 - c4-judge
2023-02-05T12:05:14Z
kirk-baird marked the issue as grade-b
#1 - c4-sponsor
2023-02-07T22:46:06Z
jonathandiep marked the issue as sponsor confirmed