From 4d770b65671b5ea23e3638d8877adf702a640701 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Thu, 21 Jul 2022 17:19:33 +0000 Subject: [PATCH] Add initial code --- contracts/SoundEdition/ISoundEditionV1.sol | 2 + contracts/SoundEdition/ISoundNftV1.sol | 40 ++++++++++++ contracts/SoundEdition/SoundEditionV1.sol | 25 +++++-- contracts/SoundEdition/SoundNftV1.sol | 65 +++++++++++++++++++ .../Minting/EditionMintControllers.sol | 54 +++++++++++++++ .../FixedPricePermissionedSaleMinter.sol | 49 ++++++++++++++ .../Minting/FixedPricePublicSaleMinter.sol | 53 +++++++++++++++ 7 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 contracts/SoundEdition/ISoundNftV1.sol create mode 100644 contracts/SoundEdition/SoundNftV1.sol create mode 100644 contracts/modules/Minting/EditionMintControllers.sol create mode 100644 contracts/modules/Minting/FixedPricePermissionedSaleMinter.sol create mode 100644 contracts/modules/Minting/FixedPricePublicSaleMinter.sol diff --git a/contracts/SoundEdition/ISoundEditionV1.sol b/contracts/SoundEdition/ISoundEditionV1.sol index 22730a97..a3567706 100644 --- a/contracts/SoundEdition/ISoundEditionV1.sol +++ b/contracts/SoundEdition/ISoundEditionV1.sol @@ -38,4 +38,6 @@ interface ISoundEditionV1 is IERC721AUpgradeable { string memory _name, string memory _symbol ) external; + + function mint(address _to, uint256 _quantity) external payable; } diff --git a/contracts/SoundEdition/ISoundNftV1.sol b/contracts/SoundEdition/ISoundNftV1.sol new file mode 100644 index 00000000..1abb2fa8 --- /dev/null +++ b/contracts/SoundEdition/ISoundNftV1.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.15; + +import "chiru-labs/ERC721A-Upgradeable/IERC721AUpgradeable.sol"; +import "openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; + +/* + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▒███████████████████████████████████████████████████████████ + ▒███████████████████████████████████████████████████████████ + ▒▓▓▓▓▓▓▓▓▓▓▓▓▓████████████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓██████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒ + █████████████████████████████▓ ████████████████████████████████████████████ + █████████████████████████████▓ ████████████████████████████████████████████ + █████████████████████████████▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████████████ + █████████████████████████████▓ ▒█████████████████████████████ + █████████████████████████████▓ ▒████████████████████████████ + █████████████████████████████████████████████████████████▓ + ███████████████████████████████████████████████████████████ + ███████████████████████████████████████████████████████████▒ + ███████████████████████████████████████████████████████████▒ + ▓██████████████████████████████████████████████████████████▒ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓███████████████████████████████▒ + █████████████████████████████ ▒█████████████████████████████▒ + ██████████████████████████████ ▒█████████████████████████████▒ + ██████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒█████████████████████████████▒ + ████████████████████████████████████████████▒ ▒█████████████████████████████▒ + ████████████████████████████████████████████▒ ▒█████████████████████████████▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████▓▓▓▓▓▓▓▓▓▓▓▓▓███████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓██████████████████████████████████████████████████████████▒ + ▓██████████████████████████████████████████████████████████ +*/ + +/// @title ISoundNftV1 +/// @author Sound.xyz +interface ISoundNftV1 is IERC721AUpgradeable { + function initialize(string memory _name, string memory _symbol) external; + + function mint(address to, uint256 quantity) external payable; +} diff --git a/contracts/SoundEdition/SoundEditionV1.sol b/contracts/SoundEdition/SoundEditionV1.sol index 38c3757f..74cb07f6 100644 --- a/contracts/SoundEdition/SoundEditionV1.sol +++ b/contracts/SoundEdition/SoundEditionV1.sol @@ -6,6 +6,7 @@ import "chiru-labs/ERC721A-Upgradeable/ERC721AUpgradeable.sol"; import "openzeppelin-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; import "openzeppelin-upgradeable/interfaces/IERC2981Upgradeable.sol"; +import "openzeppelin-upgradeable/access/AccessControlUpgradeable.sol"; /* ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ @@ -35,7 +36,9 @@ import "openzeppelin-upgradeable/interfaces/IERC2981Upgradeable.sol"; /// @title SoundEditionV1 /// @author Sound.xyz -contract SoundEditionV1 is ERC721AUpgradeable, IERC2981Upgradeable, OwnableUpgradeable { +contract SoundEditionV1 is ERC721AUpgradeable, IERC2981Upgradeable, OwnableUpgradeable, AccessControlUpgradeable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + /// @notice Initializes the contract /// @param _owner Owner of contract (artist) /// @param _name Name of the token @@ -47,9 +50,13 @@ contract SoundEditionV1 is ERC721AUpgradeable, IERC2981Upgradeable, OwnableUpgra ) public initializerERC721A initializer { __ERC721A_init(_name, _symbol); __Ownable_init(); - + __AccessControl_init(); + // Set ownership to owner transferOwnership(_owner); + + // Give owner the DEFAULT_ADMIN_ROLE + _grantRole(DEFAULT_ADMIN_ROLE, _owner); } /// @notice Informs other contracts which interfaces this contract supports @@ -58,10 +65,12 @@ contract SoundEditionV1 is ERC721AUpgradeable, IERC2981Upgradeable, OwnableUpgra function supportsInterface(bytes4 _interfaceId) public view - override(ERC721AUpgradeable, IERC165Upgradeable) + override(ERC721AUpgradeable, AccessControlUpgradeable, IERC165Upgradeable) returns (bool) { - // todo + return + ERC721AUpgradeable.supportsInterface(_interfaceId) || + AccessControlUpgradeable.supportsInterface(_interfaceId); } /// @notice Get royalty information for token @@ -75,4 +84,12 @@ contract SoundEditionV1 is ERC721AUpgradeable, IERC2981Upgradeable, OwnableUpgra { // todo } + + /// @notice Mints `_quantity` tokens to addrress `_to` + /// Each token will be assigned a token ID that is consecutively increasing + /// @param _to Address to mint to + /// @param _quantity Number of tokens to mint + function mint(address _to, uint256 _quantity) public payable onlyRole(MINTER_ROLE) { + _mint(_to, _quantity); + } } diff --git a/contracts/SoundEdition/SoundNftV1.sol b/contracts/SoundEdition/SoundNftV1.sol new file mode 100644 index 00000000..597b6355 --- /dev/null +++ b/contracts/SoundEdition/SoundNftV1.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.15; + +import "chiru-labs/ERC721A-Upgradeable/ERC721AUpgradeable.sol"; +import "openzeppelin-upgradeable/access/OwnableUpgradeable.sol"; +import "openzeppelin-upgradeable/access/AccessControlUpgradeable.sol"; + +/* + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▒███████████████████████████████████████████████████████████ + ▒███████████████████████████████████████████████████████████ + ▒▓▓▓▓▓▓▓▓▓▓▓▓▓████████████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓██████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒ + █████████████████████████████▓ ████████████████████████████████████████████ + █████████████████████████████▓ ████████████████████████████████████████████ + █████████████████████████████▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████████████ + █████████████████████████████▓ ▒█████████████████████████████ + █████████████████████████████▓ ▒████████████████████████████ + █████████████████████████████████████████████████████████▓ + ███████████████████████████████████████████████████████████ + ███████████████████████████████████████████████████████████▒ + ███████████████████████████████████████████████████████████▒ + ▓██████████████████████████████████████████████████████████▒ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓███████████████████████████████▒ + █████████████████████████████ ▒█████████████████████████████▒ + ██████████████████████████████ ▒█████████████████████████████▒ + ██████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒█████████████████████████████▒ + ████████████████████████████████████████████▒ ▒█████████████████████████████▒ + ████████████████████████████████████████████▒ ▒█████████████████████████████▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████▓▓▓▓▓▓▓▓▓▓▓▓▓███████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓██████████████████████████████████████████████████████████▒ + ▓██████████████████████████████████████████████████████████ +*/ + +/// @title SoundNftV1 +/// @author Sound.xyz +contract SoundNftV1 is ERC721AUpgradeable, OwnableUpgradeable, AccessControlUpgradeable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + function initialize(string memory _name, string memory _symbol) + public + initializerERC721A + initializer + { + __ERC721A_init(_name, _symbol); + __Ownable_init(); + __AccessControl_init(); + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + function mint(address to, uint256 quantity) public payable onlyRole(MINTER_ROLE) { + _mint(to, quantity); + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(ERC721AUpgradeable, AccessControlUpgradeable) + returns (bool) + { + return + ERC721AUpgradeable.supportsInterface(interfaceId) || + AccessControlUpgradeable.supportsInterface(interfaceId); + } +} diff --git a/contracts/modules/Minting/EditionMintControllers.sol b/contracts/modules/Minting/EditionMintControllers.sol new file mode 100644 index 00000000..6397a9da --- /dev/null +++ b/contracts/modules/Minting/EditionMintControllers.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.15; + +contract EditionMintControllers { + + event EditionMintControllerUpdated(address indexed edition, address indexed newController); + + mapping(address => address) private _controllers; + + modifier onlyEditionMintController(address edition) virtual { + require(msg.sender == _controllers[edition], "Unauthorized."); + _; + } + + function _initEditionMintController(address edition) internal { + _initEditionMintController(edition, msg.sender); + } + + function _initEditionMintController(address edition, address editionMintController) internal { + require(editionMintController != address(0), "Edition mint controller cannot be the zero address."); + require(_controllers[edition] == address(0), "Edition mint controller already exists."); + + _controllers[edition] = editionMintController; + + emit EditionMintControllerUpdated(edition, editionMintController); + } + + function _deleteEditionMintController(address edition) internal { + require(_controllers[edition] != address(0), "Edition mint controller does not exist."); + + delete _controllers[edition]; + + emit EditionMintControllerUpdated(edition, address(0)); + } + + function _deleteEditionMintController() internal { + _deleteEditionMintController(msg.sender); + } + + function _editionMintController(address edition) internal view returns (address) { + return _controllers[edition]; + } + + function setEditionMintController( + address edition, + address newController + ) public virtual onlyEditionMintController(edition) { + require(newController != address(0), ""); + + _controllers[edition] = newController; + emit EditionMintControllerUpdated(edition, newController); + } +} diff --git a/contracts/modules/Minting/FixedPricePermissionedSaleMinter.sol b/contracts/modules/Minting/FixedPricePermissionedSaleMinter.sol new file mode 100644 index 00000000..5a9a49cb --- /dev/null +++ b/contracts/modules/Minting/FixedPricePermissionedSaleMinter.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.15; + +import "./EditionMintControllers.sol"; +import "../../SoundEdition/ISoundEditionV1.sol"; + +contract FixedPricePermissionedMinter is EditionMintControllers { + + struct EditionMintData { + // The price at which each token will be sold, in ETH. + uint256 price; + // Whitelist signer address. + address signer; + // The maximum number of tokens that can can be minted for this sale. + uint32 maxMinted; + // The total number of tokens minted so far for this sale. + uint32 totalMinted; + } + + mapping(address => EditionMintData) public editionMintData; + + function createEditionMint( + address edition, + uint256 price, + address signer, + uint32 maxMinted + ) public { + _initEditionMintController(edition); + EditionMintData storage data = editionMintData[edition]; + data.price = price; + data.signer = signer; + data.maxMinted = maxMinted; + } + + function deleteMintee(address edition) public onlyEditionMintController(edition) { + _deleteEditionMintController(); + delete editionMintData[edition]; + } + + function mint(address edition, uint256 quantity) public payable { + // EditionMintData storage data = editionMintData[edition]; + // require(data.startTime <= block.timestamp, "Mint not started."); + // require(data.endTime > block.timestamp, "Mint has ended."); + // require(data.price * quantity == msg.value, "Wrong ether value."); + // require((data.totalMinted += quantity) <= data.maxMinted, "No more mints."); + ISoundEditionV1(edition).mint{value: msg.value}(edition, quantity); + } +} diff --git a/contracts/modules/Minting/FixedPricePublicSaleMinter.sol b/contracts/modules/Minting/FixedPricePublicSaleMinter.sol new file mode 100644 index 00000000..24013f8b --- /dev/null +++ b/contracts/modules/Minting/FixedPricePublicSaleMinter.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.15; + +import "./EditionMintControllers.sol"; +import "../../SoundEdition/ISoundEditionV1.sol"; + +contract FixedPricePublicSaleMinter is EditionMintControllers { + + struct EditionMintData { + // The price at which each token will be sold, in ETH. + uint256 price; + // Start timestamp of sale (in seconds since unix epoch). + uint32 startTime; + // End timestamp of sale (in seconds since unix epoch). + uint32 endTime; + // The maximum number of tokens that can can be minted for this sale. + uint32 maxMinted; + // The total number of tokens minted so far for this sale. + uint32 totalMinted; + } + + mapping(address => EditionMintData) public editionMintData; + + function createEditionMint( + address edition, + uint256 price, + uint32 startTime, + uint32 endTime, + uint32 maxMinted + ) public { + _initEditionMintController(edition); + EditionMintData storage data = editionMintData[edition]; + data.price = price; + data.startTime = startTime; + data.endTime = endTime; + data.maxMinted = maxMinted; + } + + function deleteEditionMint(address edition) public onlyEditionMintController(edition) { + _deleteEditionMintController(); + delete editionMintData[edition]; + } + + function mint(address edition, uint32 quantity) public payable { + EditionMintData storage data = editionMintData[edition]; + require(data.startTime <= block.timestamp, "Mint not started."); + require(data.endTime > block.timestamp, "Mint has ended."); + require(data.price * quantity == msg.value, "Wrong ether value."); + require((data.totalMinted += quantity) <= data.maxMinted, "No more mints."); + ISoundEditionV1(edition).mint{value: msg.value}(edition, quantity); + } +}