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: 4/173
Findings: 4
Award: $1,360.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xMAKEOUTHILL, 0xMirce, 7siech, AkshaySrivastav, AlexCzm, Awesome, Aymen0909, Cryptor, Deivitto, DimitarDimitrov, ElKu, Garrett, Jayus, Josiah, Kenshin, KrisApostolov, RaymondFam, SovaSlava, Timenov, UdarTeam, amaechieth, btk, c3phas, codeislight, fellows, frankudoags, gzeon, hansfriese, luxartvinsec, millersplanet, mookimgo, navinavu, oberon, paspe, pavankv, petersspetrov, pfapostol, prestoncodes, rbserver, sakshamguruji, shark, thekmj, trustindistrust, tsvetanovv, usmannk, vagrant, vanko1, xAriextz, yosuke
2.5852 USDC - $2.59
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L58-L61 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L47-L50
In RabbitHoleReceipt.sol
and RabbitHoleTicket.sol
, receipts and tickets can be minted by anyone.
The first line inside the modifier can be passed without reverting for any callers.
modifier onlyMinter() { msg.sender == minterAddress; _; }
Manual Review
We should modify like the below.
modifier onlyMinter() { require(msg.sender == minterAddress, 'Not minter'); _; }
#0 - c4-judge
2023-02-06T09:21:32Z
kirk-baird marked the issue as duplicate of #9
#1 - c4-judge
2023-02-14T08:37:34Z
kirk-baird marked the issue as satisfactory
🌟 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/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L81-L87 https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102-L104
withdrawFee
can be called several times, so attackers can use this to drain Erc20Quest's balance.
When the admin calls withdrawRemainingTokens
, protocolFee + unclaimedTokens
left in the Erc20Quest
contract. If unclaimedTokens >= protocolFee
, the attacker can call withdrawFee
to drain balance, and it can prevent user's claim because of the low balance.
Manual Review
withdrawFee
should be called only once.
#0 - c4-judge
2023-02-06T22:37:40Z
kirk-baird marked the issue as duplicate of #23
#1 - c4-judge
2023-02-14T08:54:57Z
kirk-baird changed the severity to 3 (High Risk)
#2 - c4-judge
2023-02-14T08:55:08Z
kirk-baird marked the issue as satisfactory
#3 - c4-judge
2023-02-14T08:56:50Z
kirk-baird marked issue #377 as primary and marked this issue as a duplicate of 377
#4 - c4-judge
2023-02-14T08:56:52Z
kirk-baird marked issue #375 as primary and marked this issue as a duplicate of 375
#5 - c4-judge
2023-02-20T09:29:59Z
kirk-baird marked the issue as not a duplicate
#6 - c4-judge
2023-02-20T09:30:11Z
kirk-baird marked the issue as duplicate of #605
🌟 Selected for report: glcanvas
Also found by: adriro, hansfriese, libratus
1234.14 USDC - $1,234.14
https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L228 https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L99
Users can't claim if they mint receipts after the admin changes receiptContract, and they can't get rewards.
In the implementation of QuestFactory.mintReceipt
, user will get factory's receipt. But when the user claim, quest's receipt is counted. So if the user mints receipt after the admin changes receipt contract, he will get a new receipt. But he needs old receipts for claiming rewards. So he can't get reward.
Manual Review
Get receipt contract from quest, and mint quest's receipt instead of factory's receipt.
#0 - c4-judge
2023-02-06T22:41:48Z
kirk-baird marked the issue as duplicate of #425
#1 - c4-judge
2023-02-15T21:45:11Z
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/QuestFactory.sol#L219-L229 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102-L104
After the end time, the protocol fee might be increased if a new receipt is minted and the fee would be locked inside the contract forever.
The main reason for this issue is that users can mint a new receipt after the end time.
With the below scenario, fees might be locked inside the contract.
Erc20Quest
contract was created with totalParticipants = 10
.QuestFactory.mintReceipt()
and claimed the rewards. We might assume the 10th user was an attacker.receiptRedeemers() = 9, redeemedTokens = 9
and the admin called Erc20Quest.withdrawRemainingTokens()
to withdraw remaining tokens. Then the contract will contain the fees of 9 redeemers only.QuestFactory.mintReceipt()
and Erc20Quest.receiptRedeemers()
will be 10 now.Erc20Quest.withdrawFee()
will try to withdraw the fees of 10 redeemers and it will revert because of insufficient balance.function protocolFee() public view returns (uint256) { return (receiptRedeemers() * rewardAmountInWeiOrTokenId * questFee) / 10_000; } /// @notice Sends the protocol fee to the protocolFeeRecipient /// @dev Only callable when the quest is ended function withdrawFee() public onlyAdminWithdrawAfterEnd { IERC20(rewardToken).safeTransfer(protocolFeeRecipient, protocolFee()); }
Manual Review
We should prevent to mint a new receipt after the end time in QuestFactory.mintReceipt()
.
For that, we can add a new element endTime
to the Quest
struct and check the time while minting a receipt.
struct Quest { mapping(address => bool) addressMinted; address questAddress; uint totalParticipants; uint numberMinted; uint endTime; //+++++++++++++++++ } function mintReceipt(string memory questId_, bytes32 hash_, bytes memory signature_) public { if (quests[questId_].numberMinted + 1 > quests[questId_].totalParticipants) revert OverMaxAllowedToMint(); if (quests[questId_].addressMinted[msg.sender] == true) revert AddressAlreadyMinted(); if (keccak256(abi.encodePacked(msg.sender, questId_)) != hash_) revert InvalidHash(); if (recoverSigner(hash_, signature_) != claimSignerAddress) revert AddressNotSigned(); if (block.timestamp >= quests[questId_].endTime) revert InvalidTime(); //+++++++++++++++++ quests[questId_].addressMinted[msg.sender] = true; quests[questId_].numberMinted++; emit ReceiptMinted(msg.sender, questId_); rabbitholeReceiptContract.mint(msg.sender, questId_); }
#0 - c4-judge
2023-02-06T09:22:33Z
kirk-baird marked the issue as duplicate of #61
#1 - c4-judge
2023-02-14T10:00:39Z
kirk-baird marked the issue as satisfactory
#2 - c4-judge
2023-02-20T09:32:24Z
kirk-baird changed the severity to 3 (High Risk)
#3 - c4-judge
2023-02-23T23:48:11Z
kirk-baird changed the severity to 2 (Med Risk)
🌟 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/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L81-L87 https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L102-L104
An attacker can run Erc20Quest.withdrawFee
before withdrawRemainingTokens
, and as a result, withdrawRemainingTokens
can revert.
In Erc20Quest.withdrawRemainingTokens
, the admin left protocolFee() + unclaimedTokens
in the contract. It means that it assumes withdrawFee()
will be called after withdrawRemainingTokens()
.
But in fact, anyone can call withdrawFee
after the quest's end time. So if an attacker calls withdrawFee
before the admin calls withdrawRemainingTokens
, withdrawRemainingTokens
will revert.
Manual Review
withdrawFee
should be called only once, and in the implementation of withdrawRemainingTokens
, if withdrawFee
is called before, no need to leave protocolFee()
.
#0 - c4-judge
2023-02-06T22:37:14Z
kirk-baird marked the issue as duplicate of #61
#1 - c4-judge
2023-02-14T10:00:33Z
kirk-baird marked the issue as satisfactory
#2 - c4-judge
2023-02-14T10:02:34Z
kirk-baird changed the severity to 3 (High Risk)
#3 - c4-judge
2023-02-23T23:48:11Z
kirk-baird changed the severity to 2 (Med Risk)