Skip to content

Cleanup claim condition extension classes #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only thing that's maybe a weird pattern is this. But saves us from inheriting ContextExecutable which adds burden for most ppl who don't use meta-tx.

let me know what you think @nkrishang @jakeloo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup this is similar to ERC721A pattern, maybe we can turn this into _thirdwebMsgSender() and we use it across the board or do you think this has to be isolated to each feature offering

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pb is that it will clash if we declare it multiple times... So annoying. Hope solidity gets better overtime with this

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