Protocol | Website | Contest Pot | nSLOC | Length | Start | End | |
---|---|---|---|---|---|---|---|
RabbitHole | Website | $36,500 USDC | 752 | 5 days | Jan 25, 2023 | Jan 30, 2023 |
Quests Protocol is a protocol to distribute token rewards for completing on-chain tasks.
We're releasing five new contracts that are in scope for the audit (more inherited contracts included below):
- Quest Factory: An upgradable factory that creates a new ERC-20 or ERC-1155 Quest. This follows the factory method & factory creation patterns. This also houses logic on aggregating Quests & minting receipts
- ERC-20 Quest: A Quest that rewards an on-chain action with an ERC-20 token. You must own an unclaimed receipt to be able to claim the reward.
- ERC-1155 Quest: A Quest that rewards an on-chain action with an ERC-1155 token. You must own an unclaimed receipt to be able to claim the reward.
- Receipt: An ERC-721 contract that is able to be claimed by a user once they have completed an on-chain task. This must be held (and unclaimed) to claim a reward.
- Ticket: An ERC-1155 contract that is used by the RabbitHole team for ERC-1155 Quests. AKA this is an ERC-1155 Reward.
- QuestFactory
- RabbitHoleReceipt
- Quest
- RabbitHoleTickets
- Erc20Quest
- Erc1155Quest
- ReceiptRenderer
- TicketRenderer
- IQuest
- IQuestFactory
Severity | Title | Count |
---|---|---|
Medium | Protocol fee can be withdrawn multiple times | [M-01] |
Once the Quest endTime
is passed, anyone can call the withdrawFee()
function multiple times. This means that the protocol fee can be sent to the protocolFeeRecipient
several times, thus draining the Quest rewardToken
. So the protocolFeeRecipient
can collect all the remaining funds and tokens that have not already be claimed. Therefore, a malicious actor can prevent any player to claim their rewardTokens
after the end of the Quest.
Erc20Quest.sol:
L102: function withdrawFee() public onlyAdminWithdrawAfterEnd {
IERC20(rewardToken).safeTransfer(protocolFeeRecipient, protocolFee());
}
// Withdraw nearly all the remaining tokens
while(IERC20(rewardToken).balanceOf(address(Erc20Quest)) > Erc20Quest.protocolFee()) {
Erc20Quest.withdrawFee();
}
withdrawFee()
should be callable only once.
bool feeWithdrawn;
// [...]
function withdrawFee() public onlyAdminWithdrawAfterEnd {
require(!feeWithdrawn, "Fees already withdrawn");
IERC20(rewardToken).safeTransfer(protocolFeeRecipient, protocolFee());
feeWithdrawn = true;
}