Skip to content

Commit

Permalink
Allow minters to be set and initilized on token initilization
Browse files Browse the repository at this point in the history
  • Loading branch information
neokry committed Sep 15, 2023
1 parent ee2e551 commit a3f9dac
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 57 deletions.
58 changes: 40 additions & 18 deletions src/minters/CollectionPlusMinter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { IERC6551Registry } from "../lib/interfaces/IERC6551Registry.sol";
import { IPartialSoulboundToken } from "../token/partial-soulbound/IPartialSoulboundToken.sol";
import { IManager } from "../manager/IManager.sol";
import { IOwnable } from "../lib/interfaces/IOwnable.sol";
import { IMintStrategy } from "./interfaces/IMintStrategy.sol";

/// @title CollectionPlusMinter
/// @notice A mint strategy that mints and locks reserved tokens to ERC6551 accounts
/// @author @neokry
contract CollectionPlusMinter {
contract CollectionPlusMinter is IMintStrategy {
/// ///
/// EVENTS ///
/// ///
Expand Down Expand Up @@ -92,6 +93,19 @@ contract CollectionPlusMinter {
/// @notice Stores the collection plus settings for a token
mapping(address => CollectionPlusSettings) public allowedCollections;

/// ///
/// MODIFIERS ///
/// ///

/// @notice Checks if the caller is the token contract or the owner of the token contract
/// @param tokenContract Token contract to check
modifier onlyTokenOwner(address tokenContract) {
if (!_isContractOwner(msg.sender, tokenContract)) {
revert NOT_TOKEN_OWNER();
}
_;
}

/// ///
/// CONSTRUCTOR ///
/// ///
Expand Down Expand Up @@ -242,7 +256,7 @@ contract CollectionPlusMinter {
// Pay out fees to the Builder DAO
(bool builderSuccess, ) = builderFundsRecipent.call{ value: builderFee }("");

// Sanity check: revert if Builder DAO recipent cannot accept funds
// Revert if Builder DAO recipent cannot accept funds
if (!builderSuccess) {
revert TRANSFER_FAILED();
}
Expand All @@ -251,7 +265,7 @@ contract CollectionPlusMinter {
if (value > builderFee) {
(bool treasurySuccess, ) = treasury.call{ value: value - builderFee }("");

// Sanity check: revert if treasury cannot accept funds
// Revert if treasury cannot accept funds
if (!builderSuccess || !treasurySuccess) {
revert TRANSFER_FAILED();
}
Expand All @@ -262,32 +276,40 @@ contract CollectionPlusMinter {
/// SETTINGS ///
/// ///

// @notice Sets the minter settings from the token contract with generic data
/// @param data Encoded settings to set
function setMintSettings(bytes calldata data) external {
CollectionPlusSettings memory settings = abi.decode(data, (CollectionPlusSettings));
_setMintSettings(msg.sender, settings);
}

/// @notice Sets the minter settings for a token
/// @param tokenContract Token contract to set settings for
/// @param collectionPlusSettings Settings to set
function setSettings(address tokenContract, CollectionPlusSettings memory collectionPlusSettings) external {
if (IOwnable(tokenContract).owner() != msg.sender) {
revert NOT_TOKEN_OWNER();
}

/// @param settings Settings to set
function setMintSettings(address tokenContract, CollectionPlusSettings memory settings) external onlyTokenOwner(tokenContract) {
// Set new collection settings
allowedCollections[tokenContract] = collectionPlusSettings;

// Emit event for new settings
emit MinterSet(tokenContract, collectionPlusSettings);
_setMintSettings(tokenContract, settings);
}

/// @notice Resets the minter settings for a token
/// @param tokenContract Token contract to reset settings for
function resetSettings(address tokenContract) external {
if (IOwnable(tokenContract).owner() != msg.sender) {
revert NOT_TOKEN_OWNER();
}

function resetMintSettings(address tokenContract) external onlyTokenOwner(tokenContract) {
// Reset collection settings to null
delete allowedCollections[tokenContract];

// Emit event with null settings
emit MinterSet(tokenContract, allowedCollections[tokenContract]);
}

function _setMintSettings(address tokenContract, CollectionPlusSettings memory settings) internal {
// Set new collection settings
allowedCollections[tokenContract] = settings;

// Emit event for new settings
emit MinterSet(tokenContract, settings);
}

function _isContractOwner(address caller, address tokenContract) internal view returns (bool) {
return IOwnable(tokenContract).owner() == caller;
}
}
38 changes: 25 additions & 13 deletions src/minters/MerkleReserveMinter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { IOwnable } from "../lib/interfaces/IOwnable.sol";
import { IToken } from "../token/default/IToken.sol";
import { IManager } from "../manager/IManager.sol";
import { IMintStrategy } from "./interfaces/IMintStrategy.sol";

/// @title MerkleReserveMinter
/// @notice A mint strategy that mints reserved tokens based on a merkle tree
/// @author @neokry
contract MerkleReserveMinter {
contract MerkleReserveMinter is IMintStrategy {
/// ///
/// EVENTS ///
/// ///
Expand Down Expand Up @@ -89,10 +90,10 @@ contract MerkleReserveMinter {
/// MODIFIERS ///
/// ///

/// @notice Checks if the caller is the contract owner
/// @notice Checks if the caller is the token contract or the owner of the token contract
/// @param tokenContract Token contract to check
modifier onlyContractOwner(address tokenContract) {
if (!_isContractOwner(tokenContract)) {
modifier onlyTokenOwner(address tokenContract) {
if (!_isContractOwner(msg.sender, tokenContract)) {
revert NOT_TOKEN_OWNER();
}
_;
Expand Down Expand Up @@ -167,30 +168,41 @@ contract MerkleReserveMinter {
/// Settings ///
/// ///

/// @notice Sets the minter settings from the token contract with generic data
/// @param data Encoded settings to set
function setMintSettings(bytes calldata data) external {
MerkleMinterSettings memory settings = abi.decode(data, (MerkleMinterSettings));
_setMintSettings(msg.sender, settings);
}

/// @notice Sets the minter settings for a token
/// @param tokenContract Token contract to set settings for
/// @param merkleMinterSettings Settings to set
function setSettings(address tokenContract, MerkleMinterSettings memory merkleMinterSettings) external onlyContractOwner(tokenContract) {
allowedMerkles[tokenContract] = merkleMinterSettings;

// Emit event for new settings
emit MinterSet(tokenContract, merkleMinterSettings);
/// @param settings Settings to set
function setMintSettings(address tokenContract, MerkleMinterSettings memory settings) external onlyTokenOwner(tokenContract) {
_setMintSettings(tokenContract, settings);
}

/// @notice Resets the minter settings for a token
/// @param tokenContract Token contract to reset settings for
function resetSettings(address tokenContract) external onlyContractOwner(tokenContract) {
function resetMintSettings(address tokenContract) external onlyTokenOwner(tokenContract) {
delete allowedMerkles[tokenContract];

// Emit event with null settings
emit MinterSet(tokenContract, allowedMerkles[tokenContract]);
}

function _setMintSettings(address tokenContract, MerkleMinterSettings memory settings) internal {
allowedMerkles[tokenContract] = settings;

// Emit event for new settings
emit MinterSet(tokenContract, settings);
}

/// ///
/// Ownership ///
/// ///

function _isContractOwner(address tokenContract) internal view returns (bool) {
return IOwnable(tokenContract).owner() == msg.sender;
function _isContractOwner(address caller, address tokenContract) internal view returns (bool) {
return IOwnable(tokenContract).owner() == caller;
}
}
11 changes: 11 additions & 0 deletions src/minters/interfaces/IMintStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

/// @title IMintStrategy
/// @notice The interface for external token minting strategies
/// @author @neokry
interface IMintStrategy {
/// @notice Sets the mint settings for a token
/// @param data The encoded mint settings
function setMintSettings(bytes calldata data) external;
}
4 changes: 4 additions & 0 deletions src/token/default/IToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ interface IToken is IUUPS, IERC721Votes, IBaseToken, TokenTypesV1, TokenTypesV2
string symbol;
/// @notice The tokenId that a DAO's auctions will start at
uint256 reservedUntilTokenId;
/// @notice The minter a DAO enables by default
address initalMinter;
/// @notice The initilization data for the inital minter
bytes initalMinterData;
}

/// ///
Expand Down
21 changes: 20 additions & 1 deletion src/token/default/Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { IToken } from "./IToken.sol";
import { IBaseToken } from "../interfaces/IBaseToken.sol";
import { VersionedContract } from "../../VersionedContract.sol";
import { IBaseMetadata } from "../../metadata/interfaces/IBaseMetadata.sol";
import { IMintStrategy } from "../../minters/interfaces/IMintStrategy.sol";

/// @title Token
/// @author Rohan Kulkarni
/// @author Rohan Kulkarni & Neokry
/// @custom:repo github.com/ourzora/nouns-protocol
/// @notice A DAO's ERC-721 governance token
contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC721Votes, TokenStorageV1, TokenStorageV2, TokenStorageV3 {
Expand Down Expand Up @@ -100,6 +101,16 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
settings.metadataRenderer = IBaseMetadata(_metadataRenderer);
settings.auction = _auction;
reservedUntilTokenId = params.reservedUntilTokenId;

// Check if an inital minter was specified
if (params.initalMinter != address(0)) {
minter[params.initalMinter] = true;

// Set minter settings if specified
if (params.initalMinterData.length > 0) {
IMintStrategy(params.initalMinter).setMintSettings(params.initalMinterData);
}
}
}

/// @notice Called by the auction upon the first unpause / token mint to transfer ownership from founder to treasury
Expand Down Expand Up @@ -208,7 +219,10 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC

/// @notice Mints tokens from the reserve to the recipient
function mintFromReserveTo(address recipient, uint256 tokenId) external nonReentrant onlyMinter {
// Token must be reserved
if (tokenId >= reservedUntilTokenId) revert TOKEN_NOT_RESERVED();

// Mint the token without vesting (reserved tokens do not count towards founders vesting)
_mint(recipient, tokenId);
}

Expand Down Expand Up @@ -287,6 +301,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
/// @notice Burns a token owned by the caller
/// @param _tokenId The ERC-721 token id
function burn(uint256 _tokenId) external onlyAuctionOrMinter {
// Ensure the caller owns the token
if (ownerOf(_tokenId) != msg.sender) {
revert ONLY_TOKEN_OWNER();
}
Expand All @@ -295,8 +310,10 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
}

function _burn(uint256 _tokenId) internal override {
// Call the parent burn function
super._burn(_tokenId);

// Reduce the total supply
unchecked {
--settings.totalSupply;
}
Expand Down Expand Up @@ -421,6 +438,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
}
}

// Clear values from storage before adding new founders
settings.numFounders = 0;
settings.totalOwnership = 0;
emit FounderAllocationsCleared(newFounders);
Expand All @@ -447,6 +465,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
return address(settings.metadataRenderer);
}

/// @notice The contract owner
function owner() public view override(IToken, Ownable) returns (address) {
return super.owner();
}
Expand Down
4 changes: 4 additions & 0 deletions src/token/partial-soulbound/IPartialSoulboundToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ interface IPartialSoulboundToken is IUUPS, IERC721Votes, IBaseToken, IERC5192, P
string symbol;
/// @notice The tokenId that a DAO's auctions will start at
uint256 reservedUntilTokenId;
/// @notice The minter a DAO enables by default
address initalMinter;
/// @notice The initilization data for the inital minter
bytes initalMinterData;
}

/// ///
Expand Down
Loading

0 comments on commit a3f9dac

Please sign in to comment.