-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathQuest.sol
151 lines (127 loc) · 5.62 KB
/
Quest.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IQuest} from './interfaces/IQuest.sol';
import {RabbitHoleReceipt} from './RabbitHoleReceipt.sol';
import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
/// @title Quest
/// @author RabbitHole.gg
/// @notice This contract is the base contract for all Quests. The Erc20Quest and Erc1155Quest contracts inherit from this contract.
contract Quest is Ownable, IQuest {
RabbitHoleReceipt public immutable rabbitHoleReceiptContract;
address public immutable rewardToken;
uint256 public immutable endTime;
uint256 public immutable startTime;
uint256 public immutable totalParticipants;
uint256 public immutable rewardAmountInWeiOrTokenId;
bool public hasStarted;
bool public isPaused;
string public questId;
uint256 public redeemedTokens;
mapping(uint256 => bool) private claimedList;
constructor(
address rewardTokenAddress_,
uint256 endTime_,
uint256 startTime_,
uint256 totalParticipants_,
uint256 rewardAmountInWeiOrTokenId_,
string memory questId_,
address receiptContractAddress_
) {
if (endTime_ <= block.timestamp) revert EndTimeInPast();
if (startTime_ <= block.timestamp) revert StartTimeInPast();
if (endTime_ <= startTime_) revert EndTimeLessThanOrEqualToStartTime();
endTime = endTime_;
startTime = startTime_;
rewardToken = rewardTokenAddress_;
totalParticipants = totalParticipants_;
rewardAmountInWeiOrTokenId = rewardAmountInWeiOrTokenId_;
questId = questId_;
rabbitHoleReceiptContract = RabbitHoleReceipt(receiptContractAddress_);
redeemedTokens = 0;
}
/// @notice Starts the Quest
/// @dev Only the owner of the Quest can call this function
function start() public virtual onlyOwner {
isPaused = false;
hasStarted = true;
}
/// @notice Pauses the Quest
/// @dev Only the owner of the Quest can call this function. Also requires that the Quest has started (not by date, but by calling the start function)
function pause() public onlyOwner onlyStarted {
isPaused = true;
}
/// @notice Unpauses the Quest
/// @dev Only the owner of the Quest can call this function. Also requires that the Quest has started (not by date, but by calling the start function)
function unPause() public onlyOwner onlyStarted {
isPaused = false;
}
/// @notice Marks token ids as claimed
/// @param tokenIds_ The token ids to mark as claimed
function _setClaimed(uint256[] memory tokenIds_) private {
for (uint i = 0; i < tokenIds_.length; i++) {
claimedList[tokenIds_[i]] = true;
}
}
/// @notice Prevents reward withdrawal until the Quest has ended
modifier onlyAdminWithdrawAfterEnd() {
if (block.timestamp < endTime) revert NoWithdrawDuringClaim();
_;
}
/// @notice Checks if the Quest has started at the function level
modifier onlyStarted() {
if (!hasStarted) revert NotStarted();
_;
}
/// @notice Checks if quest has started both at the function level and at the start time
modifier onlyQuestActive() {
if (!hasStarted) revert NotStarted();
if (block.timestamp < startTime) revert ClaimWindowNotStarted();
_;
}
/// @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);
}
/// @notice Calculate the amount of rewards
/// @dev This function must be implemented in the child contracts
function _calculateRewards(uint256 redeemableTokenCount_) internal virtual returns (uint256) {
revert MustImplementInChild();
}
/// @notice Transfer the rewards to the user
/// @dev This function must be implemented in the child contracts
/// @param amount_ The amount of rewards to transfer
function _transferRewards(uint256 amount_) internal virtual {
revert MustImplementInChild();
}
/// @notice Checks if a Receipt token id has been used to claim a reward
/// @param tokenId_ The token id to check
function isClaimed(uint256 tokenId_) public view returns (bool) {
return claimedList[tokenId_] == true;
}
/// @dev Returns the reward amount
function getRewardAmount() public view returns (uint256) {
return rewardAmountInWeiOrTokenId;
}
/// @dev Returns the reward token address
function getRewardToken() public view returns (address) {
return rewardToken;
}
/// @notice Allows the owner of the Quest to withdraw any remaining rewards after the Quest has ended
function withdrawRemainingTokens(address to_) public virtual onlyOwner onlyAdminWithdrawAfterEnd {}
}