RabbitHole Quest Protocol contest - hansfriese's results

A protocol to distribute token rewards for completing on-chain tasks.

General Information

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

RabbitHole

Findings Distribution

Researcher Performance

Rank: 4/173

Findings: 4

Award: $1,360.43

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

In RabbitHoleReceipt.sol and RabbitHoleTicket.sol, receipts and tickets can be minted by anyone.

Proof of Concept

The first line inside the modifier can be passed without reverting for any callers.

    modifier onlyMinter() {
        msg.sender == minterAddress;
        _;
    }

Tools Used

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

Lines of code

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

Vulnerability details

Impact

withdrawFee can be called several times, so attackers can use this to drain Erc20Quest's balance.

Proof of Concept

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.

Tools Used

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

Findings Information

🌟 Selected for report: glcanvas

Also found by: adriro, hansfriese, libratus

Labels

bug
2 (Med Risk)
satisfactory
duplicate-425

Awards

1234.14 USDC - $1,234.14

External Links

Lines of code

https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L228 https://github.com/rabbitholegg/quest-protocol/tree/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L99

Vulnerability details

Impact

Users can't claim if they mint receipts after the admin changes receiptContract, and they can't get rewards.

Proof of Concept

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.

Tools Used

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

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-122

Awards

122.948 USDC - $122.95

External Links

Lines of code

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

Vulnerability details

Impact

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.

Proof of Concept

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.

  1. Erc20Quest contract was created with totalParticipants = 10.
  2. Before the end time, 10 signatures have been published properly but only 9 users minted the receipt using QuestFactory.mintReceipt() and claimed the rewards. We might assume the 10th user was an attacker.
  3. After the end time, 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.
  4. After that, the 10th user minted the receipt using QuestFactory.mintReceipt() and Erc20Quest.receiptRedeemers() will be 10 now.
  5. But 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());
    }

Tools Used

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)

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-122

Awards

122.948 USDC - $122.95

External Links

Lines of code

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

Vulnerability details

Impact

An attacker can run Erc20Quest.withdrawFee before withdrawRemainingTokens, and as a result, withdrawRemainingTokens can revert.

Proof of Concept

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.

Tools Used

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)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter