Skip to content

Commit

Permalink
Use storage structs for all TieredDrop extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
nkrishang committed Feb 22, 2023
1 parent 391cf66 commit 3c72569
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 49 deletions.
49 changes: 35 additions & 14 deletions contracts/extension/BatchMintMetadata.sol
@@ -1,6 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

library BatchMintMetadataStorage {
bytes32 public constant BATCH_MINT_METADATA_STORAGE_POSITION = keccak256("batch.mint.metadata.storage");

struct Data {
/// @dev Largest tokenId of each batch of tokens with the same baseURI.
uint256[] batchIds;
/// @dev Mapping from id of a batch of tokens => to base URI for the respective batch of tokens.
mapping(uint256 => string) baseURI;
}

function batchMintMetadataStorage() internal pure returns (Data storage batchMintMetadataData) {
bytes32 position = BATCH_MINT_METADATA_STORAGE_POSITION;
assembly {
batchMintMetadataData.slot := position
}
}
}

/**
* @title Batch-mint Metadata
* @notice The `BatchMintMetadata` is a contract extension for any base NFT contract. It lets the smart contract
Expand All @@ -9,19 +27,14 @@ pragma solidity ^0.8.0;
*/

contract BatchMintMetadata {
/// @dev Largest tokenId of each batch of tokens with the same baseURI.
uint256[] private batchIds;

/// @dev Mapping from id of a batch of tokens => to base URI for the respective batch of tokens.
mapping(uint256 => string) private baseURI;

/**
* @notice Returns the count of batches of NFTs.
* @dev Each batch of tokens has an in ID and an associated `baseURI`.
* See {batchIds}.
*/
function getBaseURICount() public view returns (uint256) {
return batchIds.length;
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();
return data.batchIds.length;
}

/**
Expand All @@ -30,16 +43,20 @@ contract BatchMintMetadata {
* @param _index ID of a token.
*/
function getBatchIdAtIndex(uint256 _index) public view returns (uint256) {
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();

if (_index >= getBaseURICount()) {
revert("Invalid index");
}
return batchIds[_index];
return data.batchIds[_index];
}

/// @dev Returns the id for the batch of tokens the given tokenId belongs to.
function _getBatchId(uint256 _tokenId) internal view returns (uint256 batchId, uint256 index) {
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();

uint256 numOfTokenBatches = getBaseURICount();
uint256[] memory indices = batchIds;
uint256[] memory indices = data.batchIds;

for (uint256 i = 0; i < numOfTokenBatches; i += 1) {
if (_tokenId < indices[i]) {
Expand All @@ -55,20 +72,23 @@ contract BatchMintMetadata {

/// @dev Returns the baseURI for a token. The intended metadata URI for the token is baseURI + tokenId.
function _getBaseURI(uint256 _tokenId) internal view returns (string memory) {
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();

uint256 numOfTokenBatches = getBaseURICount();
uint256[] memory indices = batchIds;
uint256[] memory indices = data.batchIds;

for (uint256 i = 0; i < numOfTokenBatches; i += 1) {
if (_tokenId < indices[i]) {
return baseURI[indices[i]];
return data.baseURI[indices[i]];
}
}
revert("Invalid tokenId");
}

/// @dev Sets the base URI for the batch of tokens with the given batchId.
function _setBaseURI(uint256 _batchId, string memory _baseURI) internal {
baseURI[_batchId] = _baseURI;
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();
data.baseURI[_batchId] = _baseURI;
}

/// @dev Mints a batch of tokenIds and associates a common baseURI to all those Ids.
Expand All @@ -80,8 +100,9 @@ contract BatchMintMetadata {
batchId = _startId + _amountToMint;
nextTokenIdToMint = batchId;

batchIds.push(batchId);
BatchMintMetadataStorage.Data storage data = BatchMintMetadataStorage.batchMintMetadataStorage();

baseURI[batchId] = _baseURIForTokens;
data.batchIds.push(batchId);
data.baseURI[batchId] = _baseURIForTokens;
}
}
35 changes: 29 additions & 6 deletions contracts/extension/DelayedReveal.sol
Expand Up @@ -3,6 +3,22 @@ pragma solidity ^0.8.0;

import "./interface/IDelayedReveal.sol";

library DelayedRevealStorage {
bytes32 public constant DELAYED_REVEAL_STORAGE_POSITION = keccak256("delayed.reveal.storage");

struct Data {
/// @dev Mapping from tokenId of a batch of tokens => to delayed reveal data.
mapping(uint256 => bytes) encryptedData;
}

function delayedRevealStorage() internal pure returns (Data storage delayedRevealData) {
bytes32 position = DELAYED_REVEAL_STORAGE_POSITION;
assembly {
delayedRevealData.slot := position
}
}
}

/**
* @title Delayed Reveal
* @notice Thirdweb's `DelayedReveal` is a contract extension for base NFT contracts. It lets you create batches of
Expand All @@ -11,11 +27,15 @@ import "./interface/IDelayedReveal.sol";

abstract contract DelayedReveal is IDelayedReveal {
/// @dev Mapping from tokenId of a batch of tokens => to delayed reveal data.
mapping(uint256 => bytes) public encryptedData;
function encryptedData(uint256 _tokenId) public view returns (bytes memory) {
DelayedRevealStorage.Data storage data = DelayedRevealStorage.delayedRevealStorage();
return data.encryptedData[_tokenId];
}

/// @dev Sets the delayed reveal data for a batchId.
function _setEncryptedData(uint256 _batchId, bytes memory _encryptedData) internal {
encryptedData[_batchId] = _encryptedData;
DelayedRevealStorage.Data storage data = DelayedRevealStorage.delayedRevealStorage();
data.encryptedData[_batchId] = _encryptedData;
}

/**
Expand All @@ -30,12 +50,14 @@ abstract contract DelayedReveal is IDelayedReveal {
* @return revealedURI Decrypted base URI.
*/
function getRevealURI(uint256 _batchId, bytes calldata _key) public view returns (string memory revealedURI) {
bytes memory data = encryptedData[_batchId];
if (data.length == 0) {
DelayedRevealStorage.Data storage data = DelayedRevealStorage.delayedRevealStorage();

bytes memory dataForBatch = data.encryptedData[_batchId];
if (dataForBatch.length == 0) {
revert("Nothing to reveal");
}

(bytes memory encryptedURI, bytes32 provenanceHash) = abi.decode(data, (bytes, bytes32));
(bytes memory encryptedURI, bytes32 provenanceHash) = abi.decode(dataForBatch, (bytes, bytes32));

revealedURI = string(encryptDecrypt(encryptedURI, _key));

Expand Down Expand Up @@ -93,6 +115,7 @@ abstract contract DelayedReveal is IDelayedReveal {
* @param _batchId ID of a batch of NFTs.
*/
function isEncryptedBatch(uint256 _batchId) public view returns (bool) {
return encryptedData[_batchId].length > 0;
DelayedRevealStorage.Data storage data = DelayedRevealStorage.delayedRevealStorage();
return data.encryptedData[_batchId].length > 0;
}
}
65 changes: 41 additions & 24 deletions contracts/extension/LazyMintWithTier.sol
Expand Up @@ -4,33 +4,43 @@ pragma solidity ^0.8.0;
import "./interface/ILazyMintWithTier.sol";
import "../extension/BatchMintMetadata.sol";

library LazyMintWithTierStorage {
bytes32 public constant LAZY_MINT_WITH_TIER_STORAGE_POSITION = keccak256("lazy.mint.with.tier.storage");

struct Data {
/// @notice The tokenId assigned to the next new NFT to be lazy minted.
uint256 nextTokenIdToLazyMint;
/// @notice Mapping from a tier -> the token IDs grouped under that tier.
mapping(string => ILazyMintWithTier.TokenRange[]) tokensInTier;
/// @notice A list of tiers used in this contract.
string[] tiers;
}

function lazyMintWithTierStorage() internal pure returns (Data storage lazyMintWithTierData) {
bytes32 position = LAZY_MINT_WITH_TIER_STORAGE_POSITION;
assembly {
lazyMintWithTierData.slot := position
}
}
}

/**
* The `LazyMint` is a contract extension for any base NFT contract. It lets you 'lazy mint' any number of NFTs
* at once. Here, 'lazy mint' means defining the metadata for particular tokenIds of your NFT contract, without actually
* minting a non-zero balance of NFTs of those tokenIds.
*/

abstract contract LazyMintWithTier is ILazyMintWithTier, BatchMintMetadata {
struct TokenRange {
uint256 startIdInclusive;
uint256 endIdNonInclusive;
function nextTokenIdToLazyMint() internal view returns (uint256) {
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();
return data.nextTokenIdToLazyMint;
}

struct TierMetadata {
string tier;
TokenRange[] ranges;
string[] baseURIs;
function tokensInTier(string memory _tier) internal view returns (TokenRange[] memory) {
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();
return data.tokensInTier[_tier];
}

/// @notice The tokenId assigned to the next new NFT to be lazy minted.
uint256 internal nextTokenIdToLazyMint;

/// @notice Mapping from a tier -> the token IDs grouped under that tier.
mapping(string => TokenRange[]) internal tokensInTier;

/// @notice A list of tiers used in this contract.
string[] private tiers;

/**
* @notice Lets an authorized address lazy mint a given amount of NFTs.
*
Expand All @@ -54,15 +64,17 @@ abstract contract LazyMintWithTier is ILazyMintWithTier, BatchMintMetadata {
revert("0 amt");
}

uint256 startId = nextTokenIdToLazyMint;
uint256 startId = nextTokenIdToLazyMint();

(nextTokenIdToLazyMint, batchId) = _batchMintMetadata(startId, _amount, _baseURIForTokens);
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();

(data.nextTokenIdToLazyMint, batchId) = _batchMintMetadata(startId, _amount, _baseURIForTokens);

// Handle tier info.
if (!(tokensInTier[_tier].length > 0)) {
tiers.push(_tier);
if (!(data.tokensInTier[_tier].length > 0)) {
data.tiers.push(_tier);
}
tokensInTier[_tier].push(TokenRange(startId, batchId));
data.tokensInTier[_tier].push(TokenRange(startId, batchId));

emit TokensLazyMinted(_tier, startId, startId + _amount - 1, _baseURIForTokens, _data);

Expand All @@ -75,7 +87,9 @@ abstract contract LazyMintWithTier is ILazyMintWithTier, BatchMintMetadata {
view
returns (TokenRange[] memory tokens, string[] memory baseURIs)
{
tokens = tokensInTier[_tier];
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();

tokens = data.tokensInTier[_tier];

uint256 len = tokens.length;
baseURIs = new string[](len);
Expand All @@ -87,7 +101,9 @@ abstract contract LazyMintWithTier is ILazyMintWithTier, BatchMintMetadata {

/// @notice Returns all metadata for all tiers created on the contract.
function getMetadataForAllTiers() external view returns (TierMetadata[] memory metadataForAllTiers) {
string[] memory allTiers = tiers;
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();

string[] memory allTiers = data.tiers;
uint256 len = allTiers.length;

metadataForAllTiers = new TierMetadata[](len);
Expand All @@ -104,7 +120,8 @@ abstract contract LazyMintWithTier is ILazyMintWithTier, BatchMintMetadata {
* @param _tier We check whether this given tier is empty.
*/
function isTierEmpty(string memory _tier) internal view returns (bool) {
return tokensInTier[_tier].length == 0;
LazyMintWithTierStorage.Data storage data = LazyMintWithTierStorage.lazyMintWithTierStorage();
return data.tokensInTier[_tier].length == 0;
}

/// @dev Returns whether lazy minting can be performed in the given execution context.
Expand Down
11 changes: 11 additions & 0 deletions contracts/extension/interface/ILazyMintWithTier.sol
Expand Up @@ -17,6 +17,17 @@ interface ILazyMintWithTier {
bytes encryptedBaseURI
);

struct TokenRange {
uint256 startIdInclusive;
uint256 endIdNonInclusive;
}

struct TierMetadata {
string tier;
TokenRange[] ranges;
string[] baseURIs;
}

/**
* @notice Lazy mints a given amount of NFTs.
*
Expand Down
11 changes: 6 additions & 5 deletions contracts/tiered-drop/plugin/TieredDropLogic.sol
Expand Up @@ -111,16 +111,17 @@ contract TieredDropLogic is
) public override returns (uint256 batchId) {
TieredDropStorage.Data storage data = TieredDropStorage.tieredDropStorage();

uint256 nextId = nextTokenIdToLazyMint();
if (_data.length > 0) {
(bytes memory encryptedURI, bytes32 provenanceHash) = abi.decode(_data, (bytes, bytes32));
if (encryptedURI.length != 0 && provenanceHash != "") {
_setEncryptedData(nextTokenIdToLazyMint + _amount, _data);
_setEncryptedData(nextId + _amount, _data);
}
}

data.totalRemainingInTier[_tier] += _amount;

uint256 startId = nextTokenIdToLazyMint;
uint256 startId = nextId;
if (isTierEmpty(_tier) || data.nextMetadataIdToMapFromTier[_tier] == type(uint256).max) {
data.nextMetadataIdToMapFromTier[_tier] = startId;
}
Expand Down Expand Up @@ -171,7 +172,7 @@ contract TieredDropLogic is
}

uint256 tokenIdToMint = data._currentIndex;
if (tokenIdToMint + quantity > nextTokenIdToLazyMint) {
if (tokenIdToMint + quantity > nextTokenIdToLazyMint()) {
revert("!Tokens");
}

Expand Down Expand Up @@ -269,7 +270,7 @@ contract TieredDropLogic is
uint256 nextIdFromTier = data.nextMetadataIdToMapFromTier[_tier];
uint256 startTokenId = _startIdToMap;

TokenRange[] memory tokensInTier = tokensInTier[_tier];
TokenRange[] memory tokensInTier = tokensInTier(_tier);
uint256 len = tokensInTier.length;

uint256 qtyRemaining = _quantity;
Expand Down Expand Up @@ -503,7 +504,7 @@ contract TieredDropLogic is

/// @dev The tokenId of the next NFT that will be minted / lazy minted.
function nextTokenIdToMint() external view returns (uint256) {
return nextTokenIdToLazyMint;
return nextTokenIdToLazyMint();
}

/// @dev Burns `tokenId`. See {ERC721-_burn}.
Expand Down

0 comments on commit 3c72569

Please sign in to comment.