Skip to content

Commit

Permalink
Cleanup claim condition extension classes (#171)
Browse files Browse the repository at this point in the history
* cleanup claim condition extension classes

* fix import casing

* more cleanup, fix foundry config

* rename Bitmaps to TWBitMaps

* fix imports
  • Loading branch information
joaquim-verges authored Jun 8, 2022
1 parent ad487f2 commit d331efc
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 63 deletions.
57 changes: 31 additions & 26 deletions contracts/feature/meta-tx/Drop.sol → contracts/feature/Drop.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "../interface/IDrop.sol";
import "../../lib/MerkleProof.sol";
import "./ExecutionContext.sol";
import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol";
import "./interface/IDrop.sol";
import "../lib/MerkleProof.sol";
import "../lib/TWBitMaps.sol";

abstract contract Drop is IDrop, ExecutionContext {
using BitMapsUpgradeable for BitMapsUpgradeable.BitMap;
abstract contract Drop is IDrop {
using TWBitMaps for TWBitMaps.BitMap;

/*///////////////////////////////////////////////////////////////
State variables
Expand Down Expand Up @@ -44,7 +43,7 @@ abstract contract Drop is IDrop, ExecutionContext {
// Verify inclusion in allowlist.
(bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof(
activeConditionId,
_msgSender(),
_dropMsgSender(),
_quantity,
_allowlistProof
);
Expand All @@ -54,7 +53,7 @@ abstract contract Drop is IDrop, ExecutionContext {

verifyClaim(
activeConditionId,
_msgSender(),
_dropMsgSender(),
_quantity,
_currency,
_pricePerToken,
Expand All @@ -74,25 +73,27 @@ abstract contract Drop is IDrop, ExecutionContext {
// claimCondition.supplyClaimed += _quantity;
// lastClaimTimestamp[activeConditionId][_msgSender()] = block.timestamp;
claimCondition.conditions[activeConditionId].supplyClaimed += _quantity;
claimCondition.lastClaimTimestamp[activeConditionId][_msgSender()] = block.timestamp;
claimCondition.lastClaimTimestamp[activeConditionId][_dropMsgSender()] = block.timestamp;

// If there's a price, collect price.
collectPriceOnClaim(_quantity, _currency, _pricePerToken);

// Mint the relevant NFTs to claimer.
uint256 startTokenId = transferTokensOnClaim(_receiver, _quantity);

emit TokensClaimed(activeConditionId, _msgSender(), _receiver, startTokenId, _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,
bytes memory
) external virtual override {
function setClaimConditions(ClaimCondition[] calldata _conditions, bool _resetClaimEligibility)
external
virtual
override
{
require(_canSetClaimConditions(), "Not authorized");

uint256 existingStartIndex = claimCondition.currentStartId;
uint256 existingPhaseCount = claimCondition.count;

Expand Down Expand Up @@ -179,13 +180,6 @@ abstract contract Drop is IDrop, ExecutionContext {
"exceed max claimable supply."
);

// uint256 timestampOfLastClaim = lastClaimTimestamp[conditionId][_claimer];
// uint256 timestampOfLastClaim = claimCondition.lastClaimTimestamp[_conditionId][_claimer];
// require(
// timestampOfLastClaim == 0 ||
// block.timestamp >= timestampOfLastClaim + currentClaimPhase.waitTimeInSecondsBetweenClaims,
// "cannot claim."
// );
(uint256 lastClaimTimestamp, uint256 nextValidClaimTimestamp) = getClaimTimestamp(_conditionId, _claimer);
require(lastClaimTimestamp == 0 || block.timestamp >= nextValidClaimTimestamp, "cannot claim.");
}
Expand All @@ -206,7 +200,6 @@ abstract contract Drop is IDrop, ExecutionContext {
keccak256(abi.encodePacked(_claimer, _allowlistProof.maxQuantityInAllowlist))
);
require(validMerkleProof, "not in whitelist.");
// require(!usedAllowlistSpot[conditionId].get(merkleProofIndex), "proof claimed.");
require(!claimCondition.usedAllowlistSpot[_conditionId].get(merkleProofIndex), "proof claimed.");
require(
_allowlistProof.maxQuantityInAllowlist == 0 || _quantity <= _allowlistProof.maxQuantityInAllowlist,
Expand Down Expand Up @@ -250,9 +243,14 @@ abstract contract Drop is IDrop, ExecutionContext {
}
}

/*///////////////////////////////////////////////////////////////
Virtual functions: to be implemented in derived contract
//////////////////////////////////////////////////////////////*/
/*////////////////////////////////////////////////////////////////////
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(
Expand All @@ -274,6 +272,10 @@ abstract contract Drop is IDrop, ExecutionContext {
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(
uint256 _quantityToClaim,
Expand All @@ -286,4 +288,7 @@ abstract contract Drop is IDrop, ExecutionContext {
internal
virtual
returns (uint256 startTokenId);

/// @dev Determine what wallet can update claim conditions
function _canSetClaimConditions() internal virtual returns (bool);
}
31 changes: 20 additions & 11 deletions contracts/feature/DropSinglePhase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ pragma solidity ^0.8.0;

import "./interface/IDropSinglePhase.sol";
import "../lib/MerkleProof.sol";
import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol";
import "../lib/TWBitMaps.sol";

abstract contract DropSinglePhase is IDropSinglePhase {
using BitMapsUpgradeable for BitMapsUpgradeable.BitMap;
using TWBitMaps for TWBitMaps.BitMap;

/*///////////////////////////////////////////////////////////////
State variables
Expand All @@ -32,7 +32,7 @@ abstract contract DropSinglePhase is IDropSinglePhase {
* @dev Map from a claim condition uid to whether an address in an allowlist
* has already claimed tokens i.e. used their place in the allowlist.
*/
mapping(bytes32 => BitMapsUpgradeable.BitMap) private usedAllowlistSpot;
mapping(bytes32 => TWBitMaps.BitMap) private usedAllowlistSpot;

/*///////////////////////////////////////////////////////////////
Drop logic
Expand Down Expand Up @@ -60,15 +60,15 @@ abstract contract DropSinglePhase is IDropSinglePhase {

// Verify inclusion in allowlist.
(bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof(
msg.sender,
_dropMsgSender(),
_quantity,
_allowlistProof
);

// Verify claim validity. If not valid, revert.
bool toVerifyMaxQuantityPerTransaction = _allowlistProof.maxQuantityInAllowlist == 0;

verifyClaim(msg.sender, _quantity, _currency, _pricePerToken, toVerifyMaxQuantityPerTransaction);
verifyClaim(_dropMsgSender(), _quantity, _currency, _pricePerToken, toVerifyMaxQuantityPerTransaction);

if (validMerkleProof && _allowlistProof.maxQuantityInAllowlist > 0) {
/**
Expand All @@ -80,15 +80,15 @@ abstract contract DropSinglePhase is IDropSinglePhase {

// Update contract state.
claimCondition.supplyClaimed += _quantity;
lastClaimTimestamp[activeConditionId][msg.sender] = block.timestamp;
lastClaimTimestamp[activeConditionId][_dropMsgSender()] = block.timestamp;

// If there's a price, collect price.
collectPriceOnClaim(_quantity, _currency, _pricePerToken);

// Mint the relevant NFTs to claimer.
uint256 startTokenId = transferTokensOnClaim(_receiver, _quantity);

emit TokensClaimed(claimCondition, msg.sender, _receiver, _quantity, startTokenId);
emit TokensClaimed(claimCondition, _dropMsgSender(), _receiver, _quantity, startTokenId);

_afterClaim(_receiver, _quantity, _currency, _pricePerToken, _allowlistProof, _data);
}
Expand All @@ -104,7 +104,7 @@ abstract contract DropSinglePhase is IDropSinglePhase {

if (_resetClaimEligibility) {
supplyClaimedAlready = 0;
targetConditionId = keccak256(abi.encodePacked(msg.sender, block.number));
targetConditionId = keccak256(abi.encodePacked(_dropMsgSender(), block.number));
}

require(supplyClaimedAlready <= _condition.maxClaimableSupply, "max supply claimed already");
Expand Down Expand Up @@ -181,9 +181,14 @@ abstract contract DropSinglePhase is IDropSinglePhase {
}
}

/*///////////////////////////////////////////////////////////////
Virtual functions: to be implemented in derived contract
//////////////////////////////////////////////////////////////*/
/*////////////////////////////////////////////////////////////////////
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(
Expand All @@ -205,6 +210,10 @@ abstract contract DropSinglePhase is IDropSinglePhase {
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(
uint256 _quantityToClaim,
Expand Down
4 changes: 2 additions & 2 deletions contracts/feature/interface/IClaimCondition.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/utils/structs/BitMapsUpgradeable.sol";
import "../../lib/TWBitMaps.sol";

/**
* Thirdweb's 'Drop' contracts are distribution mechanisms for tokens.
Expand Down Expand Up @@ -75,6 +75,6 @@ interface IClaimCondition {
uint256 count;
mapping(uint256 => ClaimCondition) conditions;
mapping(uint256 => mapping(address => uint256)) lastClaimTimestamp;
mapping(uint256 => BitMapsUpgradeable.BitMap) usedAllowlistSpot;
mapping(uint256 => TWBitMaps.BitMap) usedAllowlistSpot;
}
}
7 changes: 1 addition & 6 deletions contracts/feature/interface/IDrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ interface IDrop is IClaimCondition {
* @param resetClaimEligibility Whether to reset `limitLastClaimTimestamp` and `limitMerkleProofClaim` values when setting new
* claim conditions.
*
* @param data Arbitrary bytes data that can be leveraged in the implementation of this interface.
*/
function setClaimConditions(
ClaimCondition[] calldata phases,
bool resetClaimEligibility,
bytes memory data
) external;
function setClaimConditions(ClaimCondition[] calldata phases, bool resetClaimEligibility) external;
}
2 changes: 1 addition & 1 deletion contracts/feature/interface/ILazyMint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ interface ILazyMint {
function lazyMint(
uint256 amount,
string calldata baseURIForTokens,
bytes calldata encryptedBaseURI
bytes calldata extraData
) external returns (uint256 batchId);
}
55 changes: 55 additions & 0 deletions contracts/lib/TWBitMaps.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/BitMaps.sol)
pragma solidity ^0.8.0;

/**
* @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential.
* Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
*/
library TWBitMaps {
struct BitMap {
mapping(uint256 => uint256) _data;
}

/**
* @dev Returns whether the bit at `index` is set.
*/
function get(BitMap storage bitmap, uint256 index) internal view returns (bool) {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
return bitmap._data[bucket] & mask != 0;
}

/**
* @dev Sets the bit at `index` to the boolean `value`.
*/
function setTo(
BitMap storage bitmap,
uint256 index,
bool value
) internal {
if (value) {
set(bitmap, index);
} else {
unset(bitmap, index);
}
}

/**
* @dev Sets the bit at `index`.
*/
function set(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] |= mask;
}

/**
* @dev Unsets the bit at `index`.
*/
function unset(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] &= ~mask;
}
}
15 changes: 12 additions & 3 deletions contracts/signature-drop/SignatureDrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import "../feature/Ownable.sol";
import "../feature/DelayedReveal.sol";
import "../feature/LazyMint.sol";
import "../feature/PermissionsEnumerable.sol";
import "../feature/meta-tx/Drop.sol";
import "../feature/Drop.sol";
import "../feature/interface/ISignatureMintERC721.sol";

contract SignatureDrop is
Expand Down Expand Up @@ -325,6 +325,11 @@ contract SignatureDrop is
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Returns whether claim conditions can be set in the given execution context.
function _canSetClaimConditions() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/*///////////////////////////////////////////////////////////////
Miscellaneous
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -357,11 +362,15 @@ contract SignatureDrop is
}
}

function _dropMsgSender() internal view virtual override returns (address) {
return _msgSender();
}

function _msgSender()
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable, ExecutionContext)
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (address sender)
{
return ERC2771ContextUpgradeable._msgSender();
Expand All @@ -371,7 +380,7 @@ contract SignatureDrop is
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable, ExecutionContext)
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (bytes calldata)
{
return ERC2771ContextUpgradeable._msgData();
Expand Down
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ remappings = [
'contracts/=contracts/',
'erc721a-upgradeable/=node_modules/erc721a-upgradeable/',
]
src = 'src'
test = 'test'
src = 'contracts'
test = 'src/test'
verbosity = 0
#ignored_error_codes = []
#fuzz_runs = 256
Expand Down
Loading

0 comments on commit d331efc

Please sign in to comment.