Created
October 28, 2023 18:52
-
-
Save brianyang/2f8142eea741bdd7c6b500d78db64c49 to your computer and use it in GitHub Desktop.
claim tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Apache-2.0 | |
pragma solidity ^0.8.0; | |
import "./interface/IDrop.sol"; | |
import "../lib/MerkleProof.sol"; | |
abstract contract Drop is IDrop { | |
/*/////////////////////////////////////////////////////////////// | |
State variables | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev The active conditions for claiming tokens. | |
ClaimConditionList public claimCondition; | |
/*/////////////////////////////////////////////////////////////// | |
Drop logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Lets an account claim tokens. | |
function claim( | |
address _receiver, | |
uint256 _quantity, | |
address _currency, | |
uint256 _pricePerToken, | |
AllowlistProof calldata _allowlistProof, | |
bytes memory _data | |
) public payable virtual override { | |
_beforeClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data); | |
uint256 activeConditionId = getActiveClaimConditionId(); | |
verifyClaim(activeConditionId, _dropMsgSender(), _quantity, _currency, _pricePerToken, _allowlistProof); | |
// Update contract state. | |
claimCondition.conditions[activeConditionId].supplyClaimed += _quantity; | |
claimCondition.supplyClaimedByWallet[activeConditionId][_dropMsgSender()] += _quantity; | |
// If there's a price, collect price. | |
_collectPriceOnClaim(address(0), _quantity, _currency, _pricePerToken); | |
// Mint the relevant tokens to claimer. | |
uint256 startTokenId = _transferTokensOnClaim(_receiver, _quantity); | |
emit TokensClaimed(activeConditionId, _dropMsgSender(), _receiver, startTokenId, _quantity); | |
_afterClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data); | |
} | |
/// @dev Lets a contract admin set claim conditions. | |
function setClaimConditions(ClaimCondition[] calldata _conditions, bool _resetClaimEligibility) | |
external | |
virtual | |
override | |
{ | |
if (!_canSetClaimConditions()) { | |
revert("Not authorized"); | |
} | |
uint256 existingStartIndex = claimCondition.currentStartId; | |
uint256 existingPhaseCount = claimCondition.count; | |
/** | |
* The mapping `supplyClaimedByWallet` uses a claim condition's UID as a key. | |
* | |
* If `_resetClaimEligibility == true`, we assign completely new UIDs to the claim | |
* conditions in `_conditions`, effectively resetting the restrictions on claims expressed | |
* by `supplyClaimedByWallet`. | |
*/ | |
uint256 newStartIndex = existingStartIndex; | |
if (_resetClaimEligibility) { | |
newStartIndex = existingStartIndex + existingPhaseCount; | |
} | |
claimCondition.count = _conditions.length; | |
claimCondition.currentStartId = newStartIndex; | |
uint256 lastConditionStartTimestamp; | |
for (uint256 i = 0; i < _conditions.length; i++) { | |
require(i == 0 || lastConditionStartTimestamp < _conditions[i].startTimestamp, "ST"); | |
uint256 supplyClaimedAlready = claimCondition.conditions[newStartIndex + i].supplyClaimed; | |
if (supplyClaimedAlready > _conditions[i].maxClaimableSupply) { | |
revert("max supply claimed"); | |
} | |
claimCondition.conditions[newStartIndex + i] = _conditions[i]; | |
claimCondition.conditions[newStartIndex + i].supplyClaimed = supplyClaimedAlready; | |
lastConditionStartTimestamp = _conditions[i].startTimestamp; | |
} | |
/** | |
* Gas refunds (as much as possible) | |
* | |
* If `_resetClaimEligibility == true`, we assign completely new UIDs to the claim | |
* conditions in `_conditions`. So, we delete claim conditions with UID < `newStartIndex`. | |
* | |
* If `_resetClaimEligibility == false`, and there are more existing claim conditions | |
* than in `_conditions`, we delete the existing claim conditions that don't get replaced | |
* by the conditions in `_conditions`. | |
*/ | |
if (_resetClaimEligibility) { | |
for (uint256 i = existingStartIndex; i < newStartIndex; i++) { | |
delete claimCondition.conditions[i]; | |
} | |
} else { | |
if (existingPhaseCount > _conditions.length) { | |
for (uint256 i = _conditions.length; i < existingPhaseCount; i++) { | |
delete claimCondition.conditions[newStartIndex + i]; | |
} | |
} | |
} | |
emit ClaimConditionsUpdated(_conditions, _resetClaimEligibility); | |
} | |
/// @dev Checks a request to claim NFTs against the active claim condition's criteria. | |
function verifyClaim( | |
uint256 _conditionId, | |
address _claimer, | |
uint256 _quantity, | |
address _currency, | |
uint256 _pricePerToken, | |
AllowlistProof calldata _allowlistProof | |
) public view returns (bool isOverride) { | |
ClaimCondition memory currentClaimPhase = claimCondition.conditions[_conditionId]; | |
uint256 claimLimit = currentClaimPhase.quantityLimitPerWallet; | |
uint256 claimPrice = currentClaimPhase.pricePerToken; | |
address claimCurrency = currentClaimPhase.currency; | |
if (currentClaimPhase.merkleRoot != bytes32(0)) { | |
(isOverride, ) = MerkleProof.verify( | |
_allowlistProof.proof, | |
currentClaimPhase.merkleRoot, | |
keccak256( | |
abi.encodePacked( | |
_claimer, | |
_allowlistProof.quantityLimitPerWallet, | |
_allowlistProof.pricePerToken, | |
_allowlistProof.currency | |
) | |
) | |
); | |
} | |
if (isOverride) { | |
claimLimit = _allowlistProof.quantityLimitPerWallet != 0 | |
? _allowlistProof.quantityLimitPerWallet | |
: claimLimit; | |
claimPrice = _allowlistProof.pricePerToken != type(uint256).max | |
? _allowlistProof.pricePerToken | |
: claimPrice; | |
claimCurrency = _allowlistProof.pricePerToken != type(uint256).max && _allowlistProof.currency != address(0) | |
? _allowlistProof.currency | |
: claimCurrency; | |
} | |
uint256 supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[_conditionId][_claimer]; | |
if (_currency != claimCurrency || _pricePerToken != claimPrice) { | |
revert("!PriceOrCurrency"); | |
} | |
if (_quantity == 0 || (_quantity + supplyClaimedByWallet > claimLimit)) { | |
revert("!Qty"); | |
} | |
if (currentClaimPhase.supplyClaimed + _quantity > currentClaimPhase.maxClaimableSupply) { | |
revert("!MaxSupply"); | |
} | |
if (currentClaimPhase.startTimestamp > block.timestamp) { | |
revert("cant claim yet"); | |
} | |
} | |
/// @dev At any given moment, returns the uid for the active claim condition. | |
function getActiveClaimConditionId() public view returns (uint256) { | |
for (uint256 i = claimCondition.currentStartId + claimCondition.count; i > claimCondition.currentStartId; i--) { | |
if (block.timestamp >= claimCondition.conditions[i - 1].startTimestamp) { | |
return i - 1; | |
} | |
} | |
revert("!CONDITION."); | |
} | |
/// @dev Returns the claim condition at the given uid. | |
function getClaimConditionById(uint256 _conditionId) external view returns (ClaimCondition memory condition) { | |
condition = claimCondition.conditions[_conditionId]; | |
} | |
/// @dev Returns the supply claimed by claimer for a given conditionId. | |
function getSupplyClaimedByWallet(uint256 _conditionId, address _claimer) | |
public | |
view | |
returns (uint256 supplyClaimedByWallet) | |
{ | |
supplyClaimedByWallet = claimCondition.supplyClaimedByWallet[_conditionId][_claimer]; | |
} | |
/*//////////////////////////////////////////////////////////////////// | |
Optional hooks that can be implemented in the derived contract | |
///////////////////////////////////////////////////////////////////*/ | |
/// @dev Exposes the ability to override the msg sender. | |
function _dropMsgSender() internal virtual returns (address) { | |
return msg.sender; | |
} | |
/// @dev Runs before every `claim` function call. | |
function _beforeClaim( | |
address _receiver, | |
uint256 _quantity, | |
address _currency, | |
uint256 _pricePerToken, | |
AllowlistProof calldata _allowlistProof, | |
bytes memory _data | |
) internal virtual {} | |
/// @dev Runs after every `claim` function call. | |
function _afterClaim( | |
address _receiver, | |
uint256 _quantity, | |
address _currency, | |
uint256 _pricePerToken, | |
AllowlistProof calldata _allowlistProof, | |
bytes memory _data | |
) internal virtual {} | |
/*/////////////////////////////////////////////////////////////// | |
Virtual functions: to be implemented in derived contract | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Collects and distributes the primary sale value of NFTs being claimed. | |
function _collectPriceOnClaim( | |
address _primarySaleRecipient, | |
uint256 _quantityToClaim, | |
address _currency, | |
uint256 _pricePerToken | |
) internal virtual; | |
/// @dev Transfers the NFTs being claimed. | |
function _transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed) | |
internal | |
virtual | |
returns (uint256 startTokenId); | |
/// @dev Determine what wallet can update claim conditions | |
function _canSetClaimConditions() internal view virtual returns (bool); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment