diff --git a/contracts/drop/DropERC1155.sol b/contracts/drop/DropERC1155.sol index 831268da3..ac1a9c3c5 100644 --- a/contracts/drop/DropERC1155.sol +++ b/contracts/drop/DropERC1155.sol @@ -73,14 +73,14 @@ contract DropERC1155 is /// @dev The address that receives all platform fees from all sales. address private platformFeeRecipient; + /// @dev The % of primary sales collected as platform fees. + uint16 private platformFeeBps; + /// @dev The recipient of who gets the royalty. address private royaltyRecipient; /// @dev The (default) address that receives all royalty value. - uint128 private royaltyBps; - - /// @dev The % of primary sales collected as platform fees. - uint128 private platformFeeBps; + uint16 private royaltyBps; /// @dev Contract level metadata. string public contractURI; @@ -149,11 +149,11 @@ contract DropERC1155 is name = _name; symbol = _symbol; royaltyRecipient = _royaltyRecipient; - royaltyBps = _royaltyBps; + royaltyBps = uint16(_royaltyBps); platformFeeRecipient = _platformFeeRecipient; primarySaleRecipient = _saleRecipient; contractURI = _contractURI; - platformFeeBps = _platformFeeBps; + platformFeeBps = uint16(_platformFeeBps); _owner = _defaultAdmin; _setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); @@ -257,10 +257,14 @@ contract DropERC1155 is // Get the active claim condition index. uint256 activeConditionId = getActiveClaimConditionId(_tokenId); - // Verify claim validity. If not valid, revert. - verifyClaim(activeConditionId, _msgSender(), _tokenId, _quantity, _currency, _pricePerToken); + /** + * We make allowlist checks (i.e. verifyClaimMerkleProof) before verifying the claim's general + * validity (i.e. verifyClaim) because we give precedence to the check of allow list quantity + * restriction over the check of the general claim condition's quantityLimitPerTransaction + * restriction. + */ - // Verify inclusion in allowlist + // Verify inclusion in allowlist. (bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof( activeConditionId, _msgSender(), @@ -270,8 +274,23 @@ contract DropERC1155 is _proofMaxQuantityPerTransaction ); + // Verify claim validity. If not valid, revert. + bool toVerifyMaxQuantityPerTransaction = _proofMaxQuantityPerTransaction == 0; + verifyClaim( + activeConditionId, + _msgSender(), + _tokenId, + _quantity, + _currency, + _pricePerToken, + toVerifyMaxQuantityPerTransaction + ); + if (validMerkleProof && _proofMaxQuantityPerTransaction > 0) { - // Mark the claimer's use of their position in the allowlist. + /** + * Mark the claimer's use of their position in the allowlist. A spot in an allowlist + * can be used only once. + */ claimCondition[_tokenId].limitMerkleProofClaim[activeConditionId].set(merkleProofIndex); } @@ -317,8 +336,11 @@ contract DropERC1155 is "startTimestamp must be in ascending order." ); + uint256 supplyClaimedAlready = condition.phases[newStartIndex + i].supplyClaimed; + require(supplyClaimedAlready < _phases[i].maxClaimableSupply, "max supply claimed already"); + condition.phases[newStartIndex + i] = _phases[i]; - condition.phases[newStartIndex + i].supplyClaimed = 0; + condition.phases[newStartIndex + i].supplyClaimed = supplyClaimedAlready; lastConditionStartTimestamp = _phases[i].startTimestamp; } @@ -402,7 +424,8 @@ contract DropERC1155 is uint256 _tokenId, uint256 _quantity, address _currency, - uint256 _pricePerToken + uint256 _pricePerToken, + bool verifyMaxQuantityPerTransaction ) public view { ClaimCondition memory currentClaimPhase = claimCondition[_tokenId].phases[_conditionId]; @@ -410,8 +433,10 @@ contract DropERC1155 is _currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken, "invalid currency or price specified." ); + // If we're checking for an allowlist quantity restriction, ignore the general quantity restriction. require( - _quantity > 0 && _quantity <= currentClaimPhase.quantityLimitPerTransaction, + _quantity > 0 && + (!verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction), "invalid quantity claimed." ); require( @@ -569,7 +594,7 @@ contract DropERC1155 is require(_royaltyBps <= MAX_BPS, "exceed royalty bps"); royaltyRecipient = _royaltyRecipient; - royaltyBps = uint128(_royaltyBps); + royaltyBps = uint16(_royaltyBps); emit DefaultRoyalty(_royaltyRecipient, _royaltyBps); } @@ -594,7 +619,7 @@ contract DropERC1155 is { require(_platformFeeBps <= MAX_BPS, "bps <= 10000."); - platformFeeBps = uint64(_platformFeeBps); + platformFeeBps = uint16(_platformFeeBps); platformFeeRecipient = _platformFeeRecipient; emit PlatformFeeInfoUpdated(_platformFeeRecipient, _platformFeeBps); diff --git a/contracts/drop/DropERC20.sol b/contracts/drop/DropERC20.sol index 56454e54a..afe4cf6b4 100644 --- a/contracts/drop/DropERC20.sol +++ b/contracts/drop/DropERC20.sol @@ -181,10 +181,14 @@ contract DropERC20 is // Get the claim conditions. uint256 activeConditionId = getActiveClaimConditionId(); - // Verify claim validity. If not valid, revert. - verifyClaim(activeConditionId, _msgSender(), _quantity, _currency, _pricePerToken); + /** + * We make allowlist checks (i.e. verifyClaimMerkleProof) before verifying the claim's general + * validity (i.e. verifyClaim) because we give precedence to the check of allow list quantity + * restriction over the check of the general claim condition's quantityLimitPerTransaction + * restriction. + */ - // Verify inclusion in allowlist + // Verify inclusion in allowlist. (bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof( activeConditionId, _msgSender(), @@ -193,8 +197,22 @@ contract DropERC20 is _proofMaxQuantityPerTransaction ); + // Verify claim validity. If not valid, revert. + bool toVerifyMaxQuantityPerTransaction = _proofMaxQuantityPerTransaction == 0; + verifyClaim( + activeConditionId, + _msgSender(), + _quantity, + _currency, + _pricePerToken, + toVerifyMaxQuantityPerTransaction + ); + if (validMerkleProof && _proofMaxQuantityPerTransaction > 0) { - // Mark the claimer's use of their position in the allowlist. + /** + * Mark the claimer's use of their position in the allowlist. A spot in an allowlist + * can be used only once. + */ claimCondition.limitMerkleProofClaim[activeConditionId].set(merkleProofIndex); } @@ -238,8 +256,11 @@ contract DropERC20 is "startTimestamp must be in ascending order." ); + uint256 supplyClaimedAlready = claimCondition.phases[newStartIndex + i].supplyClaimed; + require(supplyClaimedAlready < _phases[i].maxClaimableSupply, "max supply claimed already"); + claimCondition.phases[newStartIndex + i] = _phases[i]; - claimCondition.phases[newStartIndex + i].supplyClaimed = 0; + claimCondition.phases[newStartIndex + i].supplyClaimed = supplyClaimedAlready; lastConditionStartTimestamp = _phases[i].startTimestamp; } @@ -324,7 +345,8 @@ contract DropERC20 is address _claimer, uint256 _quantity, address _currency, - uint256 _pricePerToken + uint256 _pricePerToken, + bool verifyMaxQuantityPerTransaction ) public view { ClaimCondition memory currentClaimPhase = claimCondition.phases[_conditionId]; @@ -332,8 +354,10 @@ contract DropERC20 is _currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken, "invalid currency or price specified." ); + // If we're checking for an allowlist quantity restriction, ignore the general quantity restriction. require( - _quantity > 0 && _quantity <= currentClaimPhase.quantityLimitPerTransaction, + _quantity > 0 && + (!verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction), "invalid quantity claimed." ); require( diff --git a/contracts/drop/DropERC721.sol b/contracts/drop/DropERC721.sol index a37ff7211..6a6c8513b 100644 --- a/contracts/drop/DropERC721.sol +++ b/contracts/drop/DropERC721.sol @@ -75,14 +75,14 @@ contract DropERC721 is /// @dev The address that receives all platform fees from all sales. address private platformFeeRecipient; + /// @dev The % of primary sales collected as platform fees. + uint16 private platformFeeBps; + /// @dev The (default) address that receives all royalty value. address private royaltyRecipient; /// @dev The (default) % of a sale to take as royalty (in basis points). - uint128 private royaltyBps; - - /// @dev The % of primary sales collected as platform fees. - uint128 private platformFeeBps; + uint16 private royaltyBps; /// @dev Contract level metadata. string public contractURI; @@ -143,9 +143,9 @@ contract DropERC721 is // Initialize this contract's state. royaltyRecipient = _royaltyRecipient; - royaltyBps = _royaltyBps; + royaltyBps = uint16(_royaltyBps); platformFeeRecipient = _platformFeeRecipient; - platformFeeBps = _platformFeeBps; + platformFeeBps = uint16(_platformFeeBps); primarySaleRecipient = _saleRecipient; contractURI = _contractURI; _owner = _defaultAdmin; @@ -322,10 +322,14 @@ contract DropERC721 is // Get the claim conditions. uint256 activeConditionId = getActiveClaimConditionId(); - // Verify claim validity. If not valid, revert. - verifyClaim(activeConditionId, _msgSender(), _quantity, _currency, _pricePerToken); + /** + * We make allowlist checks (i.e. verifyClaimMerkleProof) before verifying the claim's general + * validity (i.e. verifyClaim) because we give precedence to the check of allow list quantity + * restriction over the check of the general claim condition's quantityLimitPerTransaction + * restriction. + */ - // Verify inclusion in allowlist + // Verify inclusion in allowlist. (bool validMerkleProof, uint256 merkleProofIndex) = verifyClaimMerkleProof( activeConditionId, _msgSender(), @@ -334,8 +338,22 @@ contract DropERC721 is _proofMaxQuantityPerTransaction ); + // Verify claim validity. If not valid, revert. + bool toVerifyMaxQuantityPerTransaction = _proofMaxQuantityPerTransaction == 0; + verifyClaim( + activeConditionId, + _msgSender(), + _quantity, + _currency, + _pricePerToken, + toVerifyMaxQuantityPerTransaction + ); + if (validMerkleProof && _proofMaxQuantityPerTransaction > 0) { - // Mark the claimer's use of their position in the allowlist. + /** + * Mark the claimer's use of their position in the allowlist. A spot in an allowlist + * can be used only once. + */ claimCondition.limitMerkleProofClaim[activeConditionId].set(merkleProofIndex); } @@ -379,8 +397,11 @@ contract DropERC721 is "startTimestamp must be in ascending order." ); + uint256 supplyClaimedAlready = claimCondition.phases[newStartIndex + i].supplyClaimed; + require(supplyClaimedAlready < _phases[i].maxClaimableSupply, "max supply claimed already"); + claimCondition.phases[newStartIndex + i] = _phases[i]; - claimCondition.phases[newStartIndex + i].supplyClaimed = 0; + claimCondition.phases[newStartIndex + i].supplyClaimed = supplyClaimedAlready; lastConditionStartTimestamp = _phases[i].startTimestamp; } @@ -471,7 +492,8 @@ contract DropERC721 is address _claimer, uint256 _quantity, address _currency, - uint256 _pricePerToken + uint256 _pricePerToken, + bool verifyMaxQuantityPerTransaction ) public view { ClaimCondition memory currentClaimPhase = claimCondition.phases[_conditionId]; @@ -479,8 +501,11 @@ contract DropERC721 is _currency == currentClaimPhase.currency && _pricePerToken == currentClaimPhase.pricePerToken, "invalid currency or price specified." ); + + // If we're checking for an allowlist quantity restriction, ignore the general quantity restriction. require( - _quantity > 0 && _quantity <= currentClaimPhase.quantityLimitPerTransaction, + _quantity > 0 && + (!verifyMaxQuantityPerTransaction || _quantity <= currentClaimPhase.quantityLimitPerTransaction), "invalid quantity claimed." ); require( @@ -624,7 +649,7 @@ contract DropERC721 is require(_royaltyBps <= MAX_BPS, "exceed royalty bps"); royaltyRecipient = _royaltyRecipient; - royaltyBps = uint128(_royaltyBps); + royaltyBps = uint16(_royaltyBps); emit DefaultRoyalty(_royaltyRecipient, _royaltyBps); } @@ -649,7 +674,7 @@ contract DropERC721 is { require(_platformFeeBps <= MAX_BPS, "bps <= 10000."); - platformFeeBps = uint64(_platformFeeBps); + platformFeeBps = uint16(_platformFeeBps); platformFeeRecipient = _platformFeeRecipient; emit PlatformFeeInfoUpdated(_platformFeeRecipient, _platformFeeBps); diff --git a/contracts/lib/CurrencyTransferLib.sol b/contracts/lib/CurrencyTransferLib.sol index e88effdbe..ca5de846e 100644 --- a/contracts/lib/CurrencyTransferLib.sol +++ b/contracts/lib/CurrencyTransferLib.sol @@ -4,8 +4,11 @@ pragma solidity ^0.8.11; // Helper interfaces import { IWETH } from "../interfaces/IWETH.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; library CurrencyTransferLib { + using SafeERC20Upgradeable for IERC20Upgradeable; + /// @dev The address interpreted as native token of the chain. address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -67,11 +70,11 @@ library CurrencyTransferLib { return; } - bool success = _from == address(this) - ? IERC20Upgradeable(_currency).transfer(_to, _amount) - : IERC20Upgradeable(_currency).transferFrom(_from, _to, _amount); - - require(success, "currency transfer failed."); + if (_from == address(this)) { + IERC20Upgradeable(_currency).safeTransfer(_to, _amount); + } else { + IERC20Upgradeable(_currency).safeTransferFrom(_from, _to, _amount); + } } /// @dev Transfers `amount` of native token to `to`. @@ -93,7 +96,7 @@ library CurrencyTransferLib { (bool success, ) = to.call{ value: value }(""); if (!success) { IWETH(_nativeTokenWrapper).deposit{ value: value }(); - require(IERC20Upgradeable(_nativeTokenWrapper).transfer(to, value), "transfer failed"); + IERC20Upgradeable(_nativeTokenWrapper).safeTransfer(to, value); } } } diff --git a/docs/DropERC1155.md b/docs/DropERC1155.md index da626226c..16978ff22 100644 --- a/docs/DropERC1155.md +++ b/docs/DropERC1155.md @@ -1072,7 +1072,7 @@ function uri(uint256 _tokenId) external view returns (string _tokenURI) ### verifyClaim ```solidity -function verifyClaim(uint256 _conditionId, address _claimer, uint256 _tokenId, uint256 _quantity, address _currency, uint256 _pricePerToken) external view +function verifyClaim(uint256 _conditionId, address _claimer, uint256 _tokenId, uint256 _quantity, address _currency, uint256 _pricePerToken, bool verifyMaxQuantityPerTransaction) external view ``` @@ -1089,6 +1089,7 @@ function verifyClaim(uint256 _conditionId, address _claimer, uint256 _tokenId, u | _quantity | uint256 | undefined | _currency | address | undefined | _pricePerToken | uint256 | undefined +| verifyMaxQuantityPerTransaction | bool | undefined ### verifyClaimMerkleProof diff --git a/docs/DropERC20.md b/docs/DropERC20.md index b650cee7b..2ff35eb39 100644 --- a/docs/DropERC20.md +++ b/docs/DropERC20.md @@ -1091,7 +1091,7 @@ function transferFrom(address from, address to, uint256 amount) external nonpaya ### verifyClaim ```solidity -function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, address _currency, uint256 _pricePerToken) external view +function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, address _currency, uint256 _pricePerToken, bool verifyMaxQuantityPerTransaction) external view ``` @@ -1107,6 +1107,7 @@ function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, | _quantity | uint256 | undefined | _currency | address | undefined | _pricePerToken | uint256 | undefined +| verifyMaxQuantityPerTransaction | bool | undefined ### verifyClaimMerkleProof diff --git a/docs/DropERC721.md b/docs/DropERC721.md index 62c6ac35f..1393e0734 100644 --- a/docs/DropERC721.md +++ b/docs/DropERC721.md @@ -1202,7 +1202,7 @@ function transferFrom(address from, address to, uint256 tokenId) external nonpay ### verifyClaim ```solidity -function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, address _currency, uint256 _pricePerToken) external view +function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, address _currency, uint256 _pricePerToken, bool verifyMaxQuantityPerTransaction) external view ``` @@ -1218,6 +1218,7 @@ function verifyClaim(uint256 _conditionId, address _claimer, uint256 _quantity, | _quantity | uint256 | undefined | _currency | address | undefined | _pricePerToken | uint256 | undefined +| verifyMaxQuantityPerTransaction | bool | undefined ### verifyClaimMerkleProof diff --git a/src/test/drop/DropERC721.t.sol b/src/test/drop/DropERC721.t.sol index 957800495..e650a4b3f 100644 --- a/src/test/drop/DropERC721.t.sol +++ b/src/test/drop/DropERC721.t.sol @@ -22,7 +22,9 @@ contract BaseDropERC721Test is BaseTest { DropERC721.ClaimCondition[] memory conditions = new DropERC721.ClaimCondition[](2); conditions[0].startTimestamp = 0; + conditions[0].maxClaimableSupply = 10; conditions[1].startTimestamp = 1; + conditions[1].maxClaimableSupply = 10; drop.setClaimConditions(conditions, false); (currentStartId, count) = drop.claimCondition();