From 26028b8a8fe8f22962db7573df3419b725c99341 Mon Sep 17 00:00:00 2001 From: Iain Date: Fri, 22 Apr 2022 10:04:38 -0400 Subject: [PATCH 1/7] adding finalize functions, organizing functions within contract, adding metadata settings getter --- contracts/ERC721Drop.sol | 103 ++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 22 deletions(-) diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index e054728..5e04a68 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -44,6 +44,11 @@ contract ERC721Drop is address indexed changedBy ); + event OpenMintFinalized( + address indexed sender, + uint256 numberOfMints + ); + /// @notice Error string constants string private constant SOLD_OUT = "Sold out"; string private constant TOO_MANY = "Too many"; @@ -292,14 +297,25 @@ contract ERC721Drop is return super.isApprovedForAll(nftOwner, operator); } + /// @dev Gets the zora fee for amount of withdraw + /// @param amount amount of funds to get fee for + function zoraFeeForAmount(uint256 amount) + public + returns (address payable, uint256) + { + (address payable recipient, uint256 bps) = zoraFeeManager + .getZORAWithdrawFeesBPS(address(this)); + return (recipient, (amount * bps) / 10_000); + } + /** - *** ---------------------------------- *** - *** *** - *** PUBLIC MINTING FUNCTIONS *** - *** *** - *** ---------------------------------- *** - *** - ** */ + *** ---------------------------------- *** + *** *** + *** PUBLIC MINTING FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ /** @dev This allows the user to purchase a edition edition @@ -318,7 +334,9 @@ contract ERC721Drop is // TODO(iain): Should Use tx.origin here to allow for minting from proxy contracts to not break limit and require unique accounts require(msg.value == salePrice * quantity, "Wrong price"); require( - _numberMinted(_msgSender()) + quantity - presaleMintsByAddress[_msgSender()] <= + _numberMinted(_msgSender()) + + quantity - + presaleMintsByAddress[_msgSender()] <= salesConfig.maxSalePurchasePerAddress, TOO_MANY ); @@ -372,7 +390,10 @@ contract ERC721Drop is "Needs to be approved" ); require(msg.value == pricePerToken * quantity, "Wrong price"); - require(presaleMintsByAddress[_msgSender()] + quantity <= maxQuantity, TOO_MANY); + require( + presaleMintsByAddress[_msgSender()] + quantity <= maxQuantity, + TOO_MANY + ); _mintNFTs(_msgSender(), quantity); uint256 firstMintedTokenId = _lastMintedTokenId() - quantity; @@ -389,7 +410,14 @@ contract ERC721Drop is return firstMintedTokenId; } - /** ADMIN MINTING FUNCTIONS */ + /** + *** ---------------------------------- *** + *** *** + *** ADMIN MINTING FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ /// @notice Mint admin /// @param recipient recipient to mint to @@ -429,6 +457,15 @@ contract ERC721Drop is return _lastMintedTokenId(); } + /** + *** ---------------------------------- *** + *** *** + *** ADMIN CONFIGURATION FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ + /// @dev Set new owner for royalties / opensea /// @param newOwner new owner to set function setOwner(address newOwner) public onlyAdmin { @@ -455,23 +492,20 @@ contract ERC721Drop is emit FundsRecipientChanged(newRecipientAddress, _msgSender()); } - /// @dev Gets the zora fee for amount of withdraw - /// @param amount amount of funds to get fee for - function zoraFeeForAmount(uint256 amount) - public - returns (address payable, uint256) - { - (address payable recipient, uint256 bps) = zoraFeeManager - .getZORAWithdrawFeesBPS(address(this)); - return (recipient, (amount * bps) / 10_000); - } - /// @dev This withdraws ETH from the contract to the contract owner. function withdraw() external - onlyRoleOrAdmin(SALES_MANAGER_ROLE) nonReentrant { + address sender = _msgSender(); + require( + hasRole(DEFAULT_ADMIN_ROLE, sender) || + hasRole(SALES_MANAGER_Role, sender) || + sender == feeRecipient || + sender == fundsRecipient, + "Does not have proper role to withdraw" + ); + uint256 funds = address(this).balance; (address payable feeRecipient, uint256 zoraFee) = zoraFeeForAmount( funds @@ -484,6 +518,26 @@ contract ERC721Drop is config.fundsRecipient.sendValue(funds); } + /// @notice Admin function to finalize and open edition sale + function finalizeOpenEdition() + external + onlyRoleOrAdmin(SALES_MANAGER_ROLE) + { + if (config.editionSize === type(uint64).max) { + config.editionSize = _totalMinted(); + } + emit OpenMintFinalized(_msgSender, config.editionSize); + } + + /** + *** ---------------------------------- *** + *** *** + *** GENERAL GETTER FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ + /// @notice Simple override for owner interface. /// @return user owner address function owner() @@ -501,6 +555,11 @@ contract ERC721Drop is return config.metadataRenderer.contractURI(); } + /// @notice Getter for metadataRenderer contract + function metadataRenderer() external view returns (address) { + return config.metadataRenderer; + } + /// @notice Token URI Getter, proxies to metadataRenderer /// @param tokenId id of token to get URI for /// @return Token URI From ec6fed0b9131579df3089fbac811dd6bf8914417 Mon Sep 17 00:00:00 2001 From: Iain Date: Fri, 22 Apr 2022 15:21:27 -0400 Subject: [PATCH 2/7] fix compiler --- contracts/ERC721Drop.sol | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index 5e04a68..6cc4b63 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -498,19 +498,20 @@ contract ERC721Drop is nonReentrant { address sender = _msgSender(); - require( - hasRole(DEFAULT_ADMIN_ROLE, sender) || - hasRole(SALES_MANAGER_Role, sender) || - sender == feeRecipient || - sender == fundsRecipient, - "Does not have proper role to withdraw" - ); uint256 funds = address(this).balance; (address payable feeRecipient, uint256 zoraFee) = zoraFeeForAmount( funds ); + require( + hasRole(DEFAULT_ADMIN_ROLE, sender) || + hasRole(SALES_MANAGER_ROLE, sender) || + sender == feeRecipient || + sender == config.fundsRecipient, + "Does not have proper role to withdraw" + ); + // No need for gas limit to trusted address. feeRecipient.sendValue(zoraFee); funds -= zoraFee; @@ -523,10 +524,10 @@ contract ERC721Drop is external onlyRoleOrAdmin(SALES_MANAGER_ROLE) { - if (config.editionSize === type(uint64).max) { - config.editionSize = _totalMinted(); + if (config.editionSize == type(uint64).max) { + config.editionSize = uint64(_totalMinted()); } - emit OpenMintFinalized(_msgSender, config.editionSize); + emit OpenMintFinalized(_msgSender(), config.editionSize); } /** @@ -556,8 +557,8 @@ contract ERC721Drop is } /// @notice Getter for metadataRenderer contract - function metadataRenderer() external view returns (address) { - return config.metadataRenderer; + function metadataRenderer() external view returns (IMetadataRenderer) { + return IMetadataRenderer(config.metadataRenderer); } /// @notice Token URI Getter, proxies to metadataRenderer From 367100b51477803bc5926796a4785886c732c503 Mon Sep 17 00:00:00 2001 From: Iain Date: Fri, 22 Apr 2022 19:06:28 -0400 Subject: [PATCH 3/7] add contract count --- contracts/ERC721Drop.sol | 75 +++++++++++++------------ contracts/interfaces/IZoraDrop.sol | 88 +++++++++++++++++------------- contracts/test/ERC721Drop.t.sol | 2 +- 3 files changed, 92 insertions(+), 73 deletions(-) diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index 6cc4b63..621e3e2 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -44,10 +44,7 @@ contract ERC721Drop is address indexed changedBy ); - event OpenMintFinalized( - address indexed sender, - uint256 numberOfMints - ); + event OpenMintFinalized(address indexed sender, uint256 numberOfMints); /// @notice Error string constants string private constant SOLD_OUT = "Sold out"; @@ -276,9 +273,20 @@ contract ERC721Drop is } /// @dev Number of NFTs the user has minted per address - /// @param minter to get count for - function mintedPerAddress(address minter) external view returns (uint256) { - return _numberMinted(minter); + /// @param minter to get counts for + function mintedPerAddress(address minter) + external + view + override + returns (IZoraDrop.AddressMintDetails memory) + { + return + IZoraDrop.AddressMintDetails({ + presaleMints: presaleMintsByAddress[minter], + publicMints: _numberMinted(minter) - + presaleMintsByAddress[minter], + totalMints: _numberMinted(minter) + }); } /// @dev Setup auto-approval for Zora v3 access to sell NFT @@ -411,13 +419,13 @@ contract ERC721Drop is } /** - *** ---------------------------------- *** - *** *** - *** ADMIN MINTING FUNCTIONS *** - *** *** - *** ---------------------------------- *** - *** - ***/ + *** ---------------------------------- *** + *** *** + *** ADMIN MINTING FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ /// @notice Mint admin /// @param recipient recipient to mint to @@ -458,13 +466,13 @@ contract ERC721Drop is } /** - *** ---------------------------------- *** - *** *** - *** ADMIN CONFIGURATION FUNCTIONS *** - *** *** - *** ---------------------------------- *** - *** - ***/ + *** ---------------------------------- *** + *** *** + *** ADMIN CONFIGURATION FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ /// @dev Set new owner for royalties / opensea /// @param newOwner new owner to set @@ -493,10 +501,7 @@ contract ERC721Drop is } /// @dev This withdraws ETH from the contract to the contract owner. - function withdraw() - external - nonReentrant - { + function withdraw() external nonReentrant { address sender = _msgSender(); uint256 funds = address(this).balance; @@ -506,9 +511,9 @@ contract ERC721Drop is require( hasRole(DEFAULT_ADMIN_ROLE, sender) || - hasRole(SALES_MANAGER_ROLE, sender) || - sender == feeRecipient || - sender == config.fundsRecipient, + hasRole(SALES_MANAGER_ROLE, sender) || + sender == feeRecipient || + sender == config.fundsRecipient, "Does not have proper role to withdraw" ); @@ -531,13 +536,13 @@ contract ERC721Drop is } /** - *** ---------------------------------- *** - *** *** - *** GENERAL GETTER FUNCTIONS *** - *** *** - *** ---------------------------------- *** - *** - ***/ + *** ---------------------------------- *** + *** *** + *** GENERAL GETTER FUNCTIONS *** + *** *** + *** ---------------------------------- *** + *** + ***/ /// @notice Simple override for owner interface. /// @return user owner address diff --git a/contracts/interfaces/IZoraDrop.sol b/contracts/interfaces/IZoraDrop.sol index 94dfb28..ccbff35 100644 --- a/contracts/interfaces/IZoraDrop.sol +++ b/contracts/interfaces/IZoraDrop.sol @@ -2,40 +2,54 @@ pragma solidity ^0.8.10; interface IZoraDrop { - struct SaleDetails { - // built-in eth sales - bool presaleActive; - bool publicSaleActive; - - uint64 publicSaleStart; - uint64 publicSaleEnd; - uint256 publicSalePrice; - - uint64 presaleStart; - uint64 presaleEnd; - bytes32 presaleMerkleRoot; - - uint256 maxSalePurchasePerAddress; - - // Information about the rest of the supply - uint256 totalMinted; - uint256 maxSupply; - } - - event Sale(address indexed to, uint256 indexed quantity, uint256 indexed pricePerToken, uint256 firstPurchasedTokenId); - - function purchase(uint256 quantity) external payable returns (uint256); - function purchasePresale( - uint256 quantity, - uint256 maxQuantity, - uint256 pricePerToken, - bytes32[] memory merkleProof - ) external payable returns (uint256); - - function saleDetails() external view returns (SaleDetails memory); - - function owner() external view returns (address); - - function adminMint(address to, uint256 quantity) external returns (uint256); - function adminMintAirdrop(address[] memory to) external returns (uint256); -} \ No newline at end of file + struct SaleDetails { + // built-in eth sales + bool presaleActive; + bool publicSaleActive; + uint64 publicSaleStart; + uint64 publicSaleEnd; + uint256 publicSalePrice; + uint64 presaleStart; + uint64 presaleEnd; + bytes32 presaleMerkleRoot; + uint256 maxSalePurchasePerAddress; + // Information about the rest of the supply + uint256 totalMinted; + uint256 maxSupply; + } + + struct AddressMintDetails { + uint256 totalMints; + uint256 presaleMints; + uint256 publicMints; + } + + event Sale( + address indexed to, + uint256 indexed quantity, + uint256 indexed pricePerToken, + uint256 firstPurchasedTokenId + ); + + function purchase(uint256 quantity) external payable returns (uint256); + + function purchasePresale( + uint256 quantity, + uint256 maxQuantity, + uint256 pricePerToken, + bytes32[] memory merkleProof + ) external payable returns (uint256); + + function saleDetails() external view returns (SaleDetails memory); + + function mintedPerAddress(address minter) + external + view + returns (AddressMintDetails memory); + + function owner() external view returns (address); + + function adminMint(address to, uint256 quantity) external returns (uint256); + + function adminMintAirdrop(address[] memory to) external returns (uint256); +} diff --git a/contracts/test/ERC721Drop.t.sol b/contracts/test/ERC721Drop.t.sol index 88c2233..27fc3ff 100644 --- a/contracts/test/ERC721Drop.t.sol +++ b/contracts/test/ERC721Drop.t.sol @@ -238,7 +238,7 @@ contract ERC721DropTest is DSTest { } function test_WithdrawNotAllowed() public setupZoraNFTBase(10) { - vm.expectRevert("Does not have proper role or admin"); + vm.expectRevert("Does not have proper role to withdraw"); zoraNFTBase.withdraw(); } From bd3dbf0580d394b5e3db2c14c93f918546a99dd7 Mon Sep 17 00:00:00 2001 From: Iain Date: Fri, 22 Apr 2022 19:19:39 -0400 Subject: [PATCH 4/7] test deployments and fix freezer script for open editions --- contracts/ERC721Drop.sol | 5 ++--- deployments/2022-04-22.json | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deployments/2022-04-22.json diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index 621e3e2..93871f5 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -529,9 +529,8 @@ contract ERC721Drop is external onlyRoleOrAdmin(SALES_MANAGER_ROLE) { - if (config.editionSize == type(uint64).max) { - config.editionSize = uint64(_totalMinted()); - } + require(config.editionSize == type(uint64).max, "Not open edition"); + config.editionSize = uint64(_totalMinted()); emit OpenMintFinalized(_msgSender(), config.editionSize); } diff --git a/deployments/2022-04-22.json b/deployments/2022-04-22.json new file mode 100644 index 0000000..85a34ee --- /dev/null +++ b/deployments/2022-04-22.json @@ -0,0 +1 @@ +{"feeManager":{"deployed":{"deploy":{"deployedTo":"0xeb7b0685ebf85830313f65a7eca44f9b8ac51cdd","deployer":"0x9444390c01dd5b7249e53fac31290f7dff53450d","transactionHash":"0xd47e17ae9169fbeba947b79e2e7eabe009c0f748205144bb749c9ada8cc085de"},"contractName":"ZoraFeeManager","contractPath":"contracts/ZoraFeeManager.sol","args":["500","0x9444390c01Dd5b7249E53FAc31290F7dFF53450D"]},"verify":{"verify":"Submitted contract for verification:\n Response: `OK`\n GUID: `ynnt6ipt1ufhxm4mpduavhwgyjsph3iph7p9fqcpk2dma5zuij`\n url: https://rinkeby.etherscan.io/address/0xeb7b0685ebf85830313f65a7eca44f9b8ac51cdd#code\n","contractName":"ZoraFeeManager","contractPath":"contracts/ZoraFeeManager.sol"}},"dropContract":{"deployed":{"deploy":{"deployedTo":"0x5f17b9f44cd2a628c2b9d2e85b512dcfb56e6b31","deployer":"0x9444390c01dd5b7249e53fac31290f7dff53450d","transactionHash":"0x60452d417af75c4fa01ede983dc408682a6fb5c7c30ca0cc1f682a8dc6ab5e17"},"contractName":"ERC721Drop","contractPath":"contracts/ERC721Drop.sol","args":["0xeb7b0685ebf85830313f65a7eca44f9b8ac51cdd","0x029AA5a949C9C90916729D50537062cb73b5Ac92"]},"verify":{"verify":"Submitted contract for verification:\n Response: `OK`\n GUID: `rkmqbgjymnh1kychzqqugycfquuigtjxfpbybtw7q84fyjgeah`\n url: https://rinkeby.etherscan.io/address/0x5f17b9f44cd2a628c2b9d2e85b512dcfb56e6b31#code\n","contractName":"ERC721Drop","contractPath":"contracts/ERC721Drop.sol"}},"dropMetadataContract":{"deployed":{"deploy":{"deployedTo":"0x421b1922444998a1fb55ea41c2098ed763685069","deployer":"0x9444390c01dd5b7249e53fac31290f7dff53450d","transactionHash":"0x89fb09ee7d665aed7496dbc434e74755b5200a3a00c1d5ecb9463b212de4b962"},"contractName":"DropMetadataRenderer","contractPath":"contracts/metadata/DropMetadataRenderer.sol"},"verify":{"verify":"Submitted contract for verification:\n Response: `OK`\n GUID: `knez6sixymu3cunb2gbwjifhkrqx7jnxcd6u7beqrqmtmgecrb`\n url: https://rinkeby.etherscan.io/address/0x421b1922444998a1fb55ea41c2098ed763685069#code\n","contractName":"DropMetadataRenderer","contractPath":"contracts/metadata/DropMetadataRenderer.sol"}},"creator":{"deployed":{"deploy":{"deployedTo":"0x72a19db71fbff750622be6fd2422fa210187dcf0","deployer":"0x9444390c01dd5b7249e53fac31290f7dff53450d","transactionHash":"0x94a501ec14d07b9c7cdba41a2ed72b077ba45dd64ce9611a295c70e19f8eaeca"},"contractName":"ZoraNFTDropDeployer","contractPath":"contracts/ZoraNFTDropDeployer.sol","args":["0x5f17b9f44cd2a628c2b9d2e85b512dcfb56e6b31","0x421b1922444998a1fb55ea41c2098ed763685069"]},"verify":{"verify":"Submitted contract for verification:\n Response: `OK`\n GUID: `rgqx7bi2z86wbi3hdwnvnimjzvttzsvpiw5xmbp7j7a4ypz6x5`\n url: https://rinkeby.etherscan.io/address/0x72a19db71fbff750622be6fd2422fa210187dcf0#code\n","contractName":"ZoraNFTDropDeployer","contractPath":"contracts/ZoraNFTDropDeployer.sol"}}} \ No newline at end of file From 428a75bc6c59c5cd72548e6e1f863643d755ccad Mon Sep 17 00:00:00 2001 From: Iain Date: Mon, 25 Apr 2022 09:39:51 -0400 Subject: [PATCH 5/7] add tests --- contracts/ZoraNFTDropDeployer.sol | 4 +-- contracts/test/ERC721Drop.t.sol | 42 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/contracts/ZoraNFTDropDeployer.sol b/contracts/ZoraNFTDropDeployer.sol index 168401b..af77efe 100644 --- a/contracts/ZoraNFTDropDeployer.sol +++ b/contracts/ZoraNFTDropDeployer.sol @@ -7,7 +7,7 @@ import {DropMetadataRenderer} from "./metadata/DropMetadataRenderer.sol"; import {IMetadataRenderer} from "./interfaces/IMetadataRenderer.sol"; contract ZoraNFTDropDeployer { - event DeployedNewContract(address indexed); + event DeployedNewContract(address indexed from, address indexed newContract); address private immutable implementation; IMetadataRenderer private immutable metadataDropRendererAddress; constructor(address mediaContractBase, IMetadataRenderer _metadataDropRendererAddress) { @@ -40,7 +40,7 @@ contract ZoraNFTDropDeployer { _metadataRenderer: metadataDropRendererAddress, _metadataRendererInit: metadataInitializer }); - emit DeployedNewContract(newMediaContract); + emit DeployedNewContract(msg.sender, newMediaContract); return newMediaContract; } diff --git a/contracts/test/ERC721Drop.t.sol b/contracts/test/ERC721Drop.t.sol index 27fc3ff..176e655 100644 --- a/contracts/test/ERC721Drop.t.sol +++ b/contracts/test/ERC721Drop.t.sol @@ -242,6 +242,48 @@ contract ERC721DropTest is DSTest { zoraNFTBase.withdraw(); } + function test_InvalidFinalizeOpenEdition() public setupZoraNFTBase(5) { + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.setSaleConfiguration(ERC721Drop.SalesConfiguration({ + publicSaleStart: 0, + publicSaleEnd: type(uint64).max, + presaleStart: 0, + presaleEnd: 0, + publicSalePrice: 0.2 ether, + presaleMerkleRoot: bytes32(0), + maxSalePurchasePerAddress: 5 + })); + zoraNFTBase.purchase{value: 0.6 ether}(3); + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.adminMint(address(0x1234), 2); + vm.prank(DEFAULT_OWNER_ADDRESS); + vm.expectRevert("Not open edition"); + zoraNFTBase.finalizeOpenEdition(); + } + + function test_ValidFinalizeOpenEdition() public setupZoraNFTBase(type(uint64).max) { + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.setSaleConfiguration(ERC721Drop.SalesConfiguration({ + publicSaleStart: 0, + publicSaleEnd: type(uint64).max, + presaleStart: 0, + presaleEnd: 0, + publicSalePrice: 0.2 ether, + presaleMerkleRoot: bytes32(0), + maxSalePurchasePerAddress: 10 + })); + zoraNFTBase.purchase{value: 0.6 ether}(3); + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.adminMint(address(0x1234), 2); + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.finalizeOpenEdition(); + vm.expectRevert("Too many"); + vm.prank(DEFAULT_OWNER_ADDRESS); + zoraNFTBase.adminMint(address(0x1234), 2); + vm.expectRevert("Too many"); + zoraNFTBase.purchase{value: 0.6 ether}(3); + } + function test_AdminMint() public setupZoraNFTBase(10) { address minter = address(0x32402); vm.startPrank(DEFAULT_OWNER_ADDRESS); From c0269b67ce666c503bd5c7ec3113017cbb7b63a5 Mon Sep 17 00:00:00 2001 From: Iain Date: Mon, 25 Apr 2022 09:56:44 -0400 Subject: [PATCH 6/7] add comments and ascii --- contracts/ERC721Drop.sol | 22 ++++++-- contracts/interfaces/IZoraDrop.sol | 70 +++++++++++++++++++++++--- contracts/test/ERC721Drop.t.sol | 14 +++--- contracts/test/merkle/MerkleDrop.t.sol | 6 +-- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index 93871f5..cdf9df4 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -1,6 +1,21 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.10; +/** + + + ________ _____ ____ ______ ____ +/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ +\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ + //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ + //'/'___ \ \ \_\ \ \ \\ \\ \ \/\ \ \ \ \_\ \ \ \//\ \L\ \ \ \L\ \/\__, `\ + /\_______\\ \_____\ \_\ \_\ \_\ \_\ \ \____/\ \_\\ \____/\ \ ,__/\/\____/ + \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ + \ \_\ + \/_/ `.. + + */ + import {ERC721AUpgradeable} from "erc721a-upgradeable/ERC721AUpgradeable.sol"; import {IERC2981Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol"; import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; @@ -55,8 +70,8 @@ contract ERC721Drop is bytes32 public immutable SALES_MANAGER_ROLE = keccak256("SALES_MANAGER"); /// @dev Returns the version of the contract. - function contractVersion() external pure returns (uint8) { - return uint8(VERSION); + function contractVersion() external pure returns (uint256) { + return VERSION; } /// @notice General configuration for NFT Minting and bookkeeping @@ -339,7 +354,6 @@ contract ERC721Drop is { uint256 salePrice = salesConfig.publicSalePrice; - // TODO(iain): Should Use tx.origin here to allow for minting from proxy contracts to not break limit and require unique accounts require(msg.value == salePrice * quantity, "Wrong price"); require( _numberMinted(_msgSender()) + @@ -482,7 +496,7 @@ contract ERC721Drop is /// @dev This sets the sales configuration /// @param newConfig new configuration to set for sales information - function setSaleConfiguration(SalesConfiguration memory newConfig) + function setDropSaleConfiguration(SalesConfiguration memory newConfig) external onlyAdmin { diff --git a/contracts/interfaces/IZoraDrop.sol b/contracts/interfaces/IZoraDrop.sol index ccbff35..97e8c8e 100644 --- a/contracts/interfaces/IZoraDrop.sol +++ b/contracts/interfaces/IZoraDrop.sol @@ -1,38 +1,77 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.10; +/** + + + ________ _____ ____ ______ ____ +/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ +\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ + //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ + //'/'___ \ \ \_\ \ \ \\ \\ \ \/\ \ \ \ \_\ \ \ \//\ \L\ \ \ \L\ \/\__, `\ + /\_______\\ \_____\ \_\ \_\ \_\ \_\ \ \____/\ \_\\ \____/\ \ ,__/\/\____/ + \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ + \ \_\ + \/_/ `.. + + */ + +/// @notice Interface for ZORA Drops contract interface IZoraDrop { + /// @notice Event emitted for each sale + event Sale( + address indexed to, + uint256 indexed quantity, + uint256 indexed pricePerToken, + uint256 firstPurchasedTokenId + ); + + /// @notice Return value for sales details to use with front-ends struct SaleDetails { - // built-in eth sales + // Synthesized status variables for sale and presale bool presaleActive; bool publicSaleActive; + + // Timed sale actions for public sale uint64 publicSaleStart; uint64 publicSaleEnd; + // Price for public sale uint256 publicSalePrice; + // Timed sale actions for presale uint64 presaleStart; uint64 presaleEnd; + // Merkle root (includes address, quantity, and price data for each entry) bytes32 presaleMerkleRoot; + // Limit public sale to a specific number of mints per wallet uint256 maxSalePurchasePerAddress; // Information about the rest of the supply + // Total that have been minted uint256 totalMinted; + // The total supply available uint256 maxSupply; } + /// @notice Return type of specific mint counts and details per address struct AddressMintDetails { + /// Number of total mints from the given address uint256 totalMints; + /// Number of presale mints from the given address uint256 presaleMints; + /// Number of public mints from the given address uint256 publicMints; } - event Sale( - address indexed to, - uint256 indexed quantity, - uint256 indexed pricePerToken, - uint256 firstPurchasedTokenId - ); - + /// @notice External purchase function (payable in eth) + /// @param quantity to purchase + /// @return first minted token ID function purchase(uint256 quantity) external payable returns (uint256); + /// @notice External purchase presale function (takes a merkle proof and matches to root) (payable in eth) + /// @param quantity to purchase + /// @param maxQuantity can purchase (verified by merkle root) + /// @param pricePerToken price per token allowed (verified by merkle root) + /// @param merkleProof input for merkle proof leaf verified by merkle root + /// @return first minted token ID function purchasePresale( uint256 quantity, uint256 maxQuantity, @@ -40,16 +79,31 @@ interface IZoraDrop { bytes32[] memory merkleProof ) external payable returns (uint256); + /// @notice Function to return the global sales details for the given drop function saleDetails() external view returns (SaleDetails memory); + /// @notice Function to return the specific sales details for a given address + /// @param minter address for minter to return mint information for function mintedPerAddress(address minter) external view returns (AddressMintDetails memory); + /// @notice This is the opensea/public owner setting that can be set by the contract admin function owner() external view returns (address); + /// @notice This is an admin mint function to mint a quantity to a specific address + /// @param to address to mint to + /// @param quantity quantity to mint + /// @return the id of the first minted NFT function adminMint(address to, uint256 quantity) external returns (uint256); + /// @notice This is an admin mint function to mint a single nft each to a list of addresses + /// @param to list of addresses to mint an NFT each to + /// @return the id of the first minted NFT function adminMintAirdrop(address[] memory to) external returns (uint256); + + /// @notice The version of the contract + /// @return The version ID of this contract implementation + function contractVersion() external pure returns (uint256); } diff --git a/contracts/test/ERC721Drop.t.sol b/contracts/test/ERC721Drop.t.sol index 176e655..33fd916 100644 --- a/contracts/test/ERC721Drop.t.sol +++ b/contracts/test/ERC721Drop.t.sol @@ -63,7 +63,7 @@ contract ERC721DropTest is DSTest { function test_Purchase(uint64 amount) public setupZoraNFTBase(10) { vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, @@ -90,7 +90,7 @@ contract ERC721DropTest is DSTest { function test_PurchaseTime() public setupZoraNFTBase(10) { vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: 0, @@ -113,7 +113,7 @@ contract ERC721DropTest is DSTest { assertEq(zoraNFTBase.saleDetails().totalMinted, 0); vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 9 * 3600, publicSaleEnd: 11 * 3600, @@ -155,7 +155,7 @@ contract ERC721DropTest is DSTest { vm.expectRevert("Sale inactive"); zoraNFTBase.purchase{value: 0.12 ether}(1); vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, @@ -199,7 +199,7 @@ contract ERC721DropTest is DSTest { // set limit to speed up tests vm.assume(limit > 0 && limit < 50); vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, @@ -244,7 +244,7 @@ contract ERC721DropTest is DSTest { function test_InvalidFinalizeOpenEdition() public setupZoraNFTBase(5) { vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration(ERC721Drop.SalesConfiguration({ + zoraNFTBase.setDropSaleConfiguration(ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, presaleStart: 0, @@ -263,7 +263,7 @@ contract ERC721DropTest is DSTest { function test_ValidFinalizeOpenEdition() public setupZoraNFTBase(type(uint64).max) { vm.prank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration(ERC721Drop.SalesConfiguration({ + zoraNFTBase.setDropSaleConfiguration(ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, presaleStart: 0, diff --git a/contracts/test/merkle/MerkleDrop.t.sol b/contracts/test/merkle/MerkleDrop.t.sol index e4057bc..5c99c65 100644 --- a/contracts/test/merkle/MerkleDrop.t.sol +++ b/contracts/test/merkle/MerkleDrop.t.sol @@ -48,7 +48,7 @@ contract ZoraNFTBaseTest is DSTest { function test_MerklePurchaseActiveSuccess() public setupZoraNFTBase { vm.startPrank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: 0, @@ -108,7 +108,7 @@ contract ZoraNFTBaseTest is DSTest { function test_MerklePurchaseAndPublicSalePurchaseLimits() public setupZoraNFTBase { vm.startPrank(DEFAULT_OWNER_ADDRESS); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: type(uint64).max, @@ -170,7 +170,7 @@ contract ZoraNFTBaseTest is DSTest { vm.startPrank(DEFAULT_OWNER_ADDRESS); // block.timestamp returning zero allows sales to go through. vm.warp(100); - zoraNFTBase.setSaleConfiguration( + zoraNFTBase.setDropSaleConfiguration( ERC721Drop.SalesConfiguration({ publicSaleStart: 0, publicSaleEnd: 0, From 618cb494a565d1d3925e6a6f8e69e4d5e82508ae Mon Sep 17 00:00:00 2001 From: Iain Date: Mon, 25 Apr 2022 09:59:08 -0400 Subject: [PATCH 7/7] comment updates --- contracts/ERC721Drop.sol | 15 +++++++-------- contracts/interfaces/IZoraDrop.sol | 17 ++++++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/contracts/ERC721Drop.sol b/contracts/ERC721Drop.sol index cdf9df4..dd2d845 100644 --- a/contracts/ERC721Drop.sol +++ b/contracts/ERC721Drop.sol @@ -3,16 +3,15 @@ pragma solidity ^0.8.10; /** - - ________ _____ ____ ______ ____ -/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ -\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ - //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ + ________ _____ ____ ______ ____ +/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ +\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ + //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ //'/'___ \ \ \_\ \ \ \\ \\ \ \/\ \ \ \ \_\ \ \ \//\ \L\ \ \ \L\ \/\__, `\ /\_______\\ \_____\ \_\ \_\ \_\ \_\ \ \____/\ \_\\ \____/\ \ ,__/\/\____/ - \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ - \ \_\ - \/_/ `.. + \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ + \ \_\ + \/_/ */ diff --git a/contracts/interfaces/IZoraDrop.sol b/contracts/interfaces/IZoraDrop.sol index 97e8c8e..74f8d52 100644 --- a/contracts/interfaces/IZoraDrop.sol +++ b/contracts/interfaces/IZoraDrop.sol @@ -3,18 +3,17 @@ pragma solidity ^0.8.10; /** - - ________ _____ ____ ______ ____ -/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ -\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ - //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ + ________ _____ ____ ______ ____ +/\_____ \ /\ __`\/\ _`\ /\ _ \ /\ _`\ +\/____//'/'\ \ \/\ \ \ \L\ \ \ \L\ \ \ \ \/\ \ _ __ ___ _____ ____ + //'/' \ \ \ \ \ \ , /\ \ __ \ \ \ \ \ \/\`'__\/ __`\/\ '__`\ /',__\ //'/'___ \ \ \_\ \ \ \\ \\ \ \/\ \ \ \ \_\ \ \ \//\ \L\ \ \ \L\ \/\__, `\ /\_______\\ \_____\ \_\ \_\ \_\ \_\ \ \____/\ \_\\ \____/\ \ ,__/\/\____/ - \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ - \ \_\ - \/_/ `.. + \/_______/ \/_____/\/_/\/ /\/_/\/_/ \/___/ \/_/ \/___/ \ \ \/ \/___/ + \ \_\ + \/_/ - */ +*/ /// @notice Interface for ZORA Drops contract interface IZoraDrop {