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: 55/173
Findings: 3
Award: $45.86
π Selected for report: 0
π Solo Findings: 0
π Selected for report: adriro
Also found by: 0xRobocop, 0xbepresent, Breeje, CodingNameKiki, HollaDieWaldfee, Kenshin, M4TZ1P, Ruhum, Tricko, badman, bin2chen, carrotsmuggler, cccz, csanuragjain, glcanvas, joestakey, lukris02, m9800, mert_eren, peakbolt, peanuts, prestoncodes, rvierdiiev, sashik_eth
21.6061 USDC - $21.61
A User who has not yet claimed his tickets , can do so even after quest has ended. This has 2 problems:
User might not be able to claim the tickets, as Owner would have already sweeped the balance using withdrawRemainingTokens
function
If User claims ticket post quest end time, Owner would get lesser tokens via withdrawRemainingTokens
function
even though User has minted ticket post endTime and ideally he should not be allowed to claim funds
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(); quests[questId_].addressMinted[msg.sender] = true; quests[questId_].numberMinted++; emit ReceiptMinted(msg.sender, questId_); rabbitholeReceiptContract.mint(msg.sender, questId_); }
a. If before Step 2 and post endTime, owner has already called withdrawRemainingTokens
function then all remaining balance would be sent to specified address. This becomes problem since now contract will not have funds for User A after User A mints the ticket
b. If after Step 3, owner calls withdrawRemainingTokens
function then owner will be refunded amount deducting User A claim amount as well (even though User A minted post endTime)
function withdrawRemainingTokens(address to_) public override onlyOwner { super.withdrawRemainingTokens(to_); uint unclaimedTokens = (receiptRedeemers() - redeemedTokens) * rewardAmountInWeiOrTokenId; uint256 nonClaimableTokens = IERC20(rewardToken).balanceOf(address(this)) - protocolFee() - unclaimedTokens; IERC20(rewardToken).safeTransfer(to_, nonClaimableTokens); }
Do not allow mintReceipt
function for a completed quest
#0 - c4-judge
2023-02-03T11:18:25Z
kirk-baird marked the issue as primary issue
#1 - c4-sponsor
2023-02-07T20:49:42Z
waynehoover marked the issue as sponsor acknowledged
#2 - c4-judge
2023-02-14T08:40:53Z
kirk-baird marked the issue as satisfactory
#3 - c4-judge
2023-02-14T08:48:27Z
kirk-baird marked issue #601 as primary and marked this issue as a duplicate of 601
π Selected for report: RaymondFam
Also found by: 0xMirce, AkshaySrivastav, AlexCzm, Aymen0909, BClabs, CodingNameKiki, ElKu, HollaDieWaldfee, Josiah, KIntern_NA, MiniGlome, StErMi, adriro, bin2chen, cccz, chaduke, csanuragjain, gzeon, hihen, holme, libratus, minhquanym, omis, peakbolt, peanuts, rbserver, rvierdiiev, timongty, ubermensch, usmannk, wait, zaskoh
7.046 USDC - $7.05
The ERC20 Quest ensures that unclaimed funds will be locked from Admin withdrawal even after endTime. This ensures User to claim there funds by redeeming ticket at any moment
The same check is missing in case of ERC1155 Quest. Admin will be able to withdraw token which user missed to claim within endTime which is wrong
function mint(address to_, uint256 id_, uint256 amount_, bytes memory data_) public onlyMinter { _mint(to_, id_, amount_, data_); }
Lets say X-10 users have claimed there ticket and got required funds using the claim
function
Now endTime has reached
Admin calls the withdrawRemainingTokens
function to withdraw the excess tokens
function withdrawRemainingTokens(address to_) public override onlyOwner { super.withdrawRemainingTokens(to_); IERC1155(rewardToken).safeTransferFrom( address(this), to_, rewardAmountInWeiOrTokenId, IERC1155(rewardToken).balanceOf(address(this), rewardAmountInWeiOrTokenId), '0x00' ); }
Ideally 10 users are still remaining from claiming their tickets, so Owner should be allowed to only withdraw balance-10 amount
But seems like this check is missing and Admin can withdraw full balance
IERC1155(rewardToken).safeTransferFrom( address(this), to_, rewardAmountInWeiOrTokenId, IERC1155(rewardToken).balanceOf(address(this), rewardAmountInWeiOrTokenId), '0x00' );
Calculate the total number of participant of the ERC1155 quest by the endTime, lets say this number is X Owner should now only be allowed to retrieve totalParticipants-X tokens
#0 - c4-judge
2023-02-03T08:27:21Z
kirk-baird marked the issue as primary issue
#1 - c4-sponsor
2023-02-07T20:47:43Z
waynehoover marked the issue as disagree with severity
#2 - waynehoover
2023-02-07T20:47:52Z
We donβt want the excess 1155 tokens, they should be locked in the Quest Contract so no users can ever get them
#3 - c4-judge
2023-02-10T05:03:11Z
kirk-baird changed the severity to QA (Quality Assurance)
#4 - c4-judge
2023-02-10T05:12:14Z
This previously downgraded issue has been upgraded by kirk-baird
#5 - c4-judge
2023-02-14T09:27:11Z
kirk-baird marked the issue as satisfactory
#6 - c4-judge
2023-02-14T09:27:21Z
kirk-baird marked issue #528 as primary and marked this issue as a duplicate of 528
#7 - c4-judge
2023-02-23T23:49:21Z
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
Impact: Changing the claimSignerAddress will invalidate all past user tickets.
Proof of Concept:
A
function mintReceipt(string memory questId_, bytes32 hash_, bytes memory signature_) public { ... if (keccak256(abi.encodePacked(msg.sender, questId_)) != hash_) revert InvalidHash(); if (recoverSigner(hash_, signature_) != claimSignerAddress) revert AddressNotSigned(); ... }
mintReceipt
will fail on existing issued signatures due to unmatching claimSignerAddressRecommended Mitigation Steps: Do not allow changing claimSignerAddress
Impact: The intialize function can currently be front run by attacker and set all initialization parameter. This will force product team to redeploy, wasting gas
Steps:
function initialize( address claimSignerAddress_, address rabbitholeReceiptContract_, address protocolFeeRecipient_ ) public initializer { __Ownable_init(); __AccessControl_init(); grantDefaultAdminAndCreateQuestRole(msg.sender); claimSignerAddress = claimSignerAddress_; rabbitholeReceiptContract = RabbitHoleReceipt(rabbitholeReceiptContract_); setProtocolFeeRecipient(protocolFeeRecipient_); setQuestFee(2_000); questIdCount = 1; }
Recommendation: Use a batch transaction to perform deployment and initialization together
Impact: It was observed that Fee recipient can be mistakenly set as 0 address. This will lead to all fees getting lost in zero address
Steps:
setProtocolFeeRecipient
functionRecommendation: Do not allow 0 address protocolFeeRecipient
#0 - c4-sponsor
2023-02-08T14:55:50Z
GarrettJMU marked the issue as sponsor acknowledged
#1 - c4-judge
2023-02-16T07:35:49Z
kirk-baird marked the issue as grade-b