Skip to content

Commit 099c19a

Browse files
Krishang NadgaudaKrishang Nadgauda
authored andcommitted
start abstracting out Drop from SignatureDrop
1 parent b0d9dcb commit 099c19a

File tree

5 files changed

+385
-0
lines changed

5 files changed

+385
-0
lines changed

contracts/feature/Drop.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./interface/IClaimCondition.sol";
5+
6+
contract Drop is IClaimCondition {
7+
8+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./interface/IDropSinglePhase.sol";
5+
import "../lib/MerkleProof.sol";
6+
import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol";
7+
8+
abstract contract DropSinglePhase is IDropSinglePhase {
9+
10+
using BitMapsUpgradeable for BitMapsUpgradeable.BitMap;
11+
12+
/// @dev The active conditions for claiming lazy minted tokens.
13+
ClaimCondition public claimCondition;
14+
15+
mapping(bytes32 => mapping(address => uint256)) private lastClaimTimestamp;
16+
mapping(bytes32 => BitMapsUpgradeable.BitMap) private usedAllowlistSpot;
17+
18+
/// @dev The ID for the active claim condition.
19+
bytes32 private conditionId;
20+
21+
function _msgSender() internal virtual returns (address) {
22+
return msg.sender;
23+
}
24+
25+
function _beforeClaim(
26+
address _receiver,
27+
uint256 _quantity,
28+
address _currency,
29+
uint256 _pricePerToken,
30+
AllowlistProof calldata _allowlistProof,
31+
bytes memory _data
32+
) internal virtual;
33+
34+
function _afterClaim(
35+
address _receiver,
36+
uint256 _quantity,
37+
address _currency,
38+
uint256 _pricePerToken,
39+
AllowlistProof calldata _allowlistProof,
40+
bytes memory _data
41+
) internal virtual;
42+
43+
/// @dev Lets an account claim NFTs.
44+
function claim(
45+
address _receiver,
46+
uint256 _quantity,
47+
address _currency,
48+
uint256 _pricePerToken,
49+
AllowlistProof calldata _allowlistProof,
50+
bytes memory _data
51+
) external payable virtual {
52+
53+
_beforeClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
54+
55+
bytes32 activeConditionId = conditionId;
56+
/**
57+
* We make allowlist checks (i.e. verifyClaimMerkleProof) before verifying the claim's general
58+
* validity (i.e. verifyClaim) because we give precedence to the check of allow list quantity
59+
* restriction over the check of the general claim condition's quantityLimitPerTransaction
60+
* restriction.
61+
*/
62+
63+
// Verify inclusion in allowlist.
64+
(bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof(
65+
_msgSender(),
66+
_quantity,
67+
_allowlistProof
68+
);
69+
70+
// Verify claim validity. If not valid, revert.
71+
bool toVerifyMaxQuantityPerTransaction = _allowlistProof.maxQuantityInAllowlist == 0;
72+
73+
verifyClaim(
74+
_msgSender(),
75+
_quantity,
76+
_currency,
77+
_pricePerToken,
78+
toVerifyMaxQuantityPerTransaction
79+
);
80+
81+
if (validMerkleProof && _allowlistProof.maxQuantityInAllowlist > 0) {
82+
/**
83+
* Mark the claimer's use of their position in the allowlist. A spot in an allowlist
84+
* can be used only once.
85+
*/
86+
usedAllowlistSpot[activeConditionId].set(merkleProofIndex);
87+
}
88+
89+
// Update contract state.
90+
claimCondition.supplyClaimed += _quantity;
91+
lastClaimTimestamp[activeConditionId][_msgSender()] = block.timestamp;
92+
93+
// If there's a price, collect price.
94+
collectPrice(_quantity, _currency, _pricePerToken);
95+
96+
// Mint the relevant NFTs to claimer.
97+
uint256 startTokenId = transferClaimedTokens(_receiver, _quantity);
98+
99+
emit TokensClaimed(claimCondition, _msgSender(), _receiver, _quantity, startTokenId);
100+
101+
_afterClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
102+
}
103+
104+
/// @dev Collects and distributes the primary sale value of NFTs being claimed.
105+
function collectPrice(
106+
uint256 _quantityToClaim,
107+
address _currency,
108+
uint256 _pricePerToken
109+
) internal virtual;
110+
111+
/// @dev Transfers the NFTs being claimed.
112+
function transferClaimedTokens(
113+
address _to,
114+
uint256 _quantityBeingClaimed
115+
) internal virtual returns (uint256 startTokenId);
116+
117+
function setClaimCondition(ClaimCondition calldata _condition, bool _resetClaimEligibility)
118+
external
119+
{
120+
if (_resetClaimEligibility) {
121+
conditionId = keccak256(abi.encodePacked(msg.sender, block.number));
122+
}
123+
124+
ClaimCondition memory currentConditoin = claimCondition;
125+
126+
claimCondition = ClaimCondition({
127+
startTimestamp: block.timestamp,
128+
maxClaimableSupply: _condition.maxClaimableSupply,
129+
supplyClaimed: _resetClaimEligibility ? currentConditoin.supplyClaimed : _condition.supplyClaimed,
130+
quantityLimitPerTransaction: _condition.supplyClaimed,
131+
waitTimeInSecondsBetweenClaims: _condition.waitTimeInSecondsBetweenClaims,
132+
merkleRoot: _condition.merkleRoot,
133+
pricePerToken: _condition.pricePerToken,
134+
currency: _condition.currency
135+
});
136+
137+
emit ClaimConditionUpdated(_condition, _resetClaimEligibility);
138+
}
139+
140+
/// @dev Checks a request to claim NFTs against the active claim condition's criteria.
141+
function verifyClaim(
142+
address _claimer,
143+
uint256 _quantity,
144+
address _currency,
145+
uint256 _pricePerToken,
146+
bool verifyMaxQuantityPerTransaction
147+
) public view {
148+
ClaimCondition memory currentClaimPhase = claimCondition;
149+
150+
require(
151+
_currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken,
152+
"invalid currency or price."
153+
);
154+
155+
// If we're checking for an allowlist quantity restriction, ignore the general quantity restriction.
156+
require(
157+
_quantity > 0 &&
158+
(!verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction),
159+
"invalid quantity."
160+
);
161+
require(
162+
currentClaimPhase.supplyClaimed + _quantity <= currentClaimPhase.maxClaimableSupply,
163+
"exceed max claimable supply."
164+
);
165+
// require(nextTokenIdToClaim + _quantity <= nextTokenIdToMint, "not enough minted tokens.");
166+
// require(maxTotalSupply == 0 || nextTokenIdToClaim + _quantity <= maxTotalSupply, "exceed max total supply.");
167+
// require(
168+
// maxWalletClaimCount == 0 || walletClaimCount[_claimer] + _quantity <= maxWalletClaimCount,
169+
// "exceed claim limit"
170+
// );
171+
172+
uint256 timestampOfLastClaim = lastClaimTimestamp[conditionId][_claimer];
173+
require(timestampOfLastClaim == 0 || block.timestamp >= timestampOfLastClaim + currentClaimPhase.waitTimeInSecondsBetweenClaims, "cannot claim.");
174+
}
175+
176+
/// @dev Checks whether a claimer meets the claim condition's allowlist criteria.
177+
function verifyClaimMerkleProof(
178+
address _claimer,
179+
uint256 _quantity,
180+
AllowlistProof calldata _allowlistProof
181+
) public view returns (bool validMerkleProof, uint256 merkleProofIndex) {
182+
ClaimCondition memory currentClaimPhase = claimCondition;
183+
184+
if (currentClaimPhase.merkleRoot != bytes32(0)) {
185+
(validMerkleProof, merkleProofIndex) = MerkleProof.verify(
186+
_allowlistProof.proof,
187+
currentClaimPhase.merkleRoot,
188+
keccak256(abi.encodePacked(_claimer, _allowlistProof.maxQuantityInAllowlist))
189+
);
190+
require(validMerkleProof, "not in whitelist.");
191+
require(!usedAllowlistSpot[conditionId].get(merkleProofIndex), "proof claimed.");
192+
require(
193+
_allowlistProof.maxQuantityInAllowlist == 0 || _quantity <= _allowlistProof.maxQuantityInAllowlist,
194+
"invalid quantity proof."
195+
);
196+
}
197+
}
198+
199+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol";
5+
6+
/**
7+
* Thirdweb's 'Drop' contracts are distribution mechanisms for tokens.
8+
*
9+
* A contract admin (i.e. a holder of `DEFAULT_ADMIN_ROLE`) can set a series of claim conditions,
10+
* ordered by their respective `startTimestamp`. A claim condition defines criteria under which
11+
* accounts can mint tokens. Claim conditions can be overwritten or added to by the contract admin.
12+
* At any moment, there is only one active claim condition.
13+
*/
14+
15+
interface IClaimCondition {
16+
/**
17+
* @notice The criteria that make up a claim condition.
18+
*
19+
* @param startTimestamp The unix timestamp after which the claim condition applies.
20+
* The same claim condition applies until the `startTimestamp`
21+
* of the next claim condition.
22+
*
23+
* @param maxClaimableSupply The maximum total number of tokens that can be claimed under
24+
* the claim condition.
25+
*
26+
* @param supplyClaimed At any given point, the number of tokens that have been claimed
27+
* under the claim condition.
28+
*
29+
* @param quantityLimitPerTransaction The maximum number of tokens that can be claimed in a single
30+
* transaction.
31+
*
32+
* @param waitTimeInSecondsBetweenClaims The least number of seconds an account must wait after claiming
33+
* tokens, to be able to claim tokens again.
34+
*
35+
* @param merkleRoot The allowlist of addresses that can claim tokens under the claim
36+
* condition.
37+
*
38+
* @param pricePerToken The price required to pay per token claimed.
39+
*
40+
* @param currency The currency in which the `pricePerToken` must be paid.
41+
*/
42+
struct ClaimCondition {
43+
uint256 startTimestamp;
44+
uint256 maxClaimableSupply;
45+
uint256 supplyClaimed;
46+
uint256 quantityLimitPerTransaction;
47+
uint256 waitTimeInSecondsBetweenClaims;
48+
bytes32 merkleRoot;
49+
uint256 pricePerToken;
50+
address currency;
51+
}
52+
53+
/**
54+
* @notice The set of all claim conditions, at any given moment.
55+
* Claim Phase ID = [currentStartId, currentStartId + length - 1];
56+
*
57+
* @param currentStartId The uid for the first claim condition amongst the current set of
58+
* claim conditions. The uid for each next claim condition is one
59+
* more than the previous claim condition's uid.
60+
*
61+
* @param count The total number of phases / claim conditions in the list
62+
* of claim conditions.
63+
*
64+
* @param conditions The claim conditions at a given uid. Claim conditions
65+
* are ordered in an ascending order by their `startTimestamp`.
66+
*
67+
* @param lastClaimTimestamp Map from an account and uid for a claim condition, to the last timestamp
68+
* at which the account claimed tokens under that claim condition.
69+
*
70+
* @param usedAllowlistSpot Map from a claim condition uid to whether an address in an allowlist
71+
* has already claimed tokens i.e. used their place in the allowlist.
72+
*/
73+
struct ClaimConditionList {
74+
uint256 currentStartId;
75+
uint256 count;
76+
mapping(uint256 => ClaimCondition) conditions;
77+
mapping(uint256 => mapping(address => uint256)) lastClaimTimestamp;
78+
mapping(uint256 => BitMapsUpgradeable.BitMap) usedAllowlistSpot;
79+
}
80+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./IClaimCondition.sol";
5+
6+
interface IDropMultiPhase is IClaimCondition {
7+
8+
struct AllowlistProof {
9+
bytes32[] proof;
10+
uint256 maxQuantityInAllowlist;
11+
}
12+
13+
/**
14+
* @notice Lets an account claim a given quantity of NFTs.
15+
*
16+
* @param receiver The receiver of the NFTs to claim.
17+
* @param quantity The quantity of NFTs to claim.
18+
* @param currency The currency in which to pay for the claim.
19+
* @param pricePerToken The price per token to pay for the claim.
20+
* @param allowlistProof The proof of the claimer's inclusion in the merkle root allowlist
21+
* of the claim conditions that apply.
22+
* @param data Arbitrary bytes data that can be leveraged in the implementation of this interface.
23+
*/
24+
function claim(
25+
address receiver,
26+
uint256 quantity,
27+
address currency,
28+
uint256 pricePerToken,
29+
AllowlistProof calldata allowlistProof,
30+
bytes memory data
31+
) external payable;
32+
33+
/**
34+
* @notice Lets a contract admin (account with `DEFAULT_ADMIN_ROLE`) set claim conditions.
35+
*
36+
* @param phases Claim conditions in ascending order by `startTimestamp`.
37+
*
38+
* @param resetClaimEligibility Whether to reset `limitLastClaimTimestamp` and `limitMerkleProofClaim` values when setting new
39+
* claim conditions.
40+
*
41+
* @param data Arbitrary bytes data that can be leveraged in the implementation of this interface.
42+
*/
43+
function setClaimConditions(ClaimCondition[] calldata phases, bool resetClaimEligibility, bytes memory data) external;
44+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./IClaimCondition.sol";
5+
6+
interface IDropSinglePhase is IClaimCondition {
7+
8+
struct AllowlistProof {
9+
bytes32[] proof;
10+
uint256 maxQuantityInAllowlist;
11+
}
12+
13+
event TokensClaimed(
14+
ClaimCondition condition,
15+
address indexed claimer,
16+
address indexed receiver,
17+
uint256 quantityClaimed,
18+
uint256 indexed aux
19+
);
20+
21+
event ClaimConditionUpdated(ClaimCondition condition, bool resetEligibility);
22+
23+
/**
24+
* @notice Lets an account claim a given quantity of NFTs.
25+
*
26+
* @param receiver The receiver of the NFTs to claim.
27+
* @param quantity The quantity of NFTs to claim.
28+
* @param currency The currency in which to pay for the claim.
29+
* @param pricePerToken The price per token to pay for the claim.
30+
* @param allowlistProof The proof of the claimer's inclusion in the merkle root allowlist
31+
* of the claim conditions that apply.
32+
* @param data Arbitrary bytes data that can be leveraged in the implementation of this interface.
33+
*/
34+
function claim(
35+
address receiver,
36+
uint256 quantity,
37+
address currency,
38+
uint256 pricePerToken,
39+
AllowlistProof calldata allowlistProof,
40+
bytes memory data
41+
) external payable;
42+
43+
/**
44+
* @notice Lets a contract admin (account with `DEFAULT_ADMIN_ROLE`) set claim conditions.
45+
*
46+
* @param phase Claim condition to set.
47+
*
48+
* @param resetClaimEligibility Whether to reset `limitLastClaimTimestamp` and `limitMerkleProofClaim` values when setting new
49+
* claim conditions.
50+
*
51+
* @param data Arbitrary bytes data that can be leveraged in the implementation of this interface.
52+
*/
53+
function setClaimConditions(ClaimCondition calldata phase, bool resetClaimEligibility, bytes memory data) external;
54+
}

0 commit comments

Comments
 (0)