Skip to content

Commit

Permalink
Merge pull request #52 from ourzora/add_getters_mint_limit
Browse files Browse the repository at this point in the history
Add upgrade script and set mint limit
  • Loading branch information
iainnash authored Mar 17, 2023
2 parents 0474936 + 53d1bbc commit 8d08ce2
Show file tree
Hide file tree
Showing 22 changed files with 1,218 additions and 82 deletions.
6 changes: 4 additions & 2 deletions addresses/1.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"FIXED_PRICE_SALE_STRATEGY": "0x6ae87b1D0909e8DC03F7b9c240e22CD5131Ed4Ee",
"MERKLE_MINT_SALE_STRATEGY": "0xAF9f59A6a437c2d72ca60569A8508Ccbddd6f219",
"CREATOR_1155_IMPL": "0xD0561AEF1D5cd30a1779f01B41B3436027177d9A",
"1155_IMPL": "0xD0561AEF1D5cd30a1779f01B41B3436027177d9A",
"FACTORY_IMPL": "0xB897dC4D2cBE136908798d53563b66a13B6f2eB1",
"FACTORY_PROXY": "0x784A410B891EE92612102521281a3e222a6E326D"
"FACTORY_PROXY": "0x784A410B891EE92612102521281a3e222a6E326D",
"MINT_FEE_AMOUNT": "777000000000000",
"MINT_FEE_RECIPIENT": "0xd1d1d4e36117ab794ec5d4c78cbd3a8904e691d0"
}
12 changes: 7 additions & 5 deletions addresses/5.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"FIXED_PRICE_SALE_STRATEGY": "0x5857bC4EafFB91B6227C6e232bf4a4AEf97F78aB",
"MERKLE_MINT_SALE_STRATEGY": "0x8a2872Ad6082373c7090EDB15d46dfC7647AC7bE",
"CREATOR_1155_IMPL": "0x828b7D5116f1d65889Ba8498075a023e9A5729a2",
"FACTORY_IMPL": "0xDE4E2e44ef77CFe0EBc7D14B090e99264670892d",
"FACTORY_PROXY": "0x8732b4bCa198509bB9c40f9a24638Be1eaB7D30c"
"FIXED_PRICE_SALE_STRATEGY": "0x10f7d171fbf4ac14a3250e9313D97b2Dfe30EcfD",
"MERKLE_MINT_SALE_STRATEGY": "0x91724462892148fb0bAcD300d7503A80baB32aA5",
"1155_IMPL": "0x828b7D5116f1d65889Ba8498075a023e9A5729a2",
"FACTORY_IMPL": "0xb532480c7251F6f4E868cfA032C9921BF7ab2078",
"FACTORY_PROXY": "0x8732b4bCa198509bB9c40f9a24638Be1eaB7D30c",
"MINT_FEE_AMOUNT": "777000000000000",
"MINT_FEE_RECIPIENT": "0xDC498668B5e6CC518fD58A2ADBF614Fd3A13D3a0"
}
109 changes: 109 additions & 0 deletions broadcast/Upgrade.s.sol/5/run-1679059552.json

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions broadcast/Upgrade.s.sol/5/run-1679059565.json

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions broadcast/Upgrade.s.sol/5/run-1679059640.json

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions broadcast/Upgrade.s.sol/5/run-latest.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ solc_version = '0.8.17'
optimizer = true
optimizer_runs = 5000
via_ir = true
fs_permissions = [{ access = "read", path = "./addresses"}]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zoralabs/zora-creator-contracts",
"version": "1.0.0",
"version": "1.0.1",
"repository": "[email protected]:ourzora/creator-contracts.git",
"author": "Iain <[email protected]>",
"license": "MIT",
Expand Down
82 changes: 43 additions & 39 deletions script/Upgrade.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,72 @@ import {IMinter1155} from "../src/interfaces/IMinter1155.sol";
import {IZoraCreator1155} from "../src/interfaces/IZoraCreator1155.sol";
import {ZoraCreatorFixedPriceSaleStrategy} from "../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol";
import {ZoraCreatorMerkleMinterStrategy} from "../src/minters/merkle/ZoraCreatorMerkleMinterStrategy.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

contract DeployScript is Script {
function setUp() public {}
contract UpgradeScript is Script {
using Strings for uint256;

string configFile;

function _getKey(string memory key) internal view returns (address result) {
(result) = abi.decode(vm.parseJson(configFile, string.concat(".", key)), (address));
}

function _getKeyNumber(string memory key) internal view returns (uint256 result) {
(result) = abi.decode(vm.parseJson(configFile, string.concat(".", key)), (uint256));
}

function setUp() public {
uint256 chainID = vm.envUint("CHAIN_ID");
console.log("CHAIN_ID", chainID);

console2.log("Starting ---");

configFile = vm.readFile(string.concat("./addresses/", Strings.toString(chainID), ".json"));
}

function run() public {
address payable deployer = payable(vm.envAddress("DEPLOYER"));
// vm.startBroadcast(deployer);

vm.startBroadcast(deployer);

ZoraCreatorFixedPriceSaleStrategy fixedPricedMinter = new ZoraCreatorFixedPriceSaleStrategy();
ZoraCreatorMerkleMinterStrategy merkleMinter = new ZoraCreatorMerkleMinterStrategy();

ZoraCreator1155Impl creatorImpl = new ZoraCreator1155Impl(100, deployer);
address nftImpl = _getKey("1155_IMPL");
if (nftImpl == address(0)) {
uint256 mintFeeAmount = _getKeyNumber("MINT_FEE_AMOUNT");
address mintFeeRecipient = _getKey("MINT_FEE_RECIPIENT");
console2.log("mintFeeAmount", mintFeeAmount);
console2.log("minFeeRecipient", mintFeeRecipient);
nftImpl = address(new ZoraCreator1155Impl(mintFeeAmount, mintFeeRecipient));
console2.log("New NFT_IMPL", nftImpl);
} else {
console2.log("Existing NFT_IMPL", nftImpl);
}

address factoryProxy = _getKey("FACTORY_PROXY");

ZoraCreator1155FactoryImpl factoryImpl = new ZoraCreator1155FactoryImpl({
_implementation: creatorImpl,
_implementation: IZoraCreator1155(nftImpl),
_merkleMinter: merkleMinter,
_fixedPriceMinter: fixedPricedMinter
});

Zora1155Factory factoryProxy = new Zora1155Factory(
address(factoryImpl),
abi.encodeWithSelector(ZoraCreator1155FactoryImpl.initialize.selector, deployer)
);
console2.log("New Factory Impl", address(factoryImpl));
console2.log("Upgrade to this new factory impl from ", factoryProxy);

console2.log("Factory Proxy", address(factoryProxy));
console2.log("Implementation Address", address(creatorImpl));

vm.startPrank(deployer);
bytes[] memory initUpdate = new bytes[](3);
initUpdate[0] = abi.encodeWithSelector(ZoraCreator1155Impl.setupNewToken.selector, "https://", 100);
initUpdate[1] = abi.encodeWithSelector(ZoraCreator1155Impl.adminMint.selector, deployer, 1, 100, "");
initUpdate[1] = abi.encodeWithSelector(ZoraCreator1155Impl.addPermission.selector, 1, address(fixedPricedMinter), creatorImpl.PERMISSION_BIT_MINTER());
initUpdate[2] = abi.encodeWithSelector(
ZoraCreator1155Impl.callSale.selector,
1,
fixedPricedMinter,
abi.encodeWithSelector(
ZoraCreatorFixedPriceSaleStrategy.setSale.selector,
1,
ZoraCreatorFixedPriceSaleStrategy.SalesConfig({
saleStart: 0,
saleEnd: type(uint64).max,
maxTokensPerAddress: 100,
pricePerToken: 0.01 ether,
fundsRecipient: address(0)
})
)
);
bytes[] memory setup = new bytes[](0);
// initUpdate[2] = abi.encodeWithSelector(ZoraCreator1155Impl.setupNewToken.selector, "https://", 100);
// initUpdate[3] = abi.encodeWithSelector(ZoraCreator1155Impl.adminMint.selector, deployer, 2, 10, "");
address newContract = address(
IZoraCreator1155Factory(address(factoryProxy)).createContract(
"ipfs://bafybeicgolwqpozsc7iwgytavete56a2nnytzix2nb2rxefdvbtwwtnnoe/metadata",
"testing contract",
"ipfs://bafkreigu544g6wjvqcysurpzy5pcskbt45a5f33m6wgythpgb3rfqi3lzi",
"+++",
ICreatorRoyaltiesControl.RoyaltyConfiguration({royaltyBPS: 0, royaltyRecipient: address(0), royaltyMintSchedule: 0}),
deployer,
setup
)
);
ZoraCreator1155Impl(newContract).multicall(initUpdate);

console2.log("New 1155 contract address", newContract);
console2.log("Testing 1155 contract address", newContract);
}
}
8 changes: 4 additions & 4 deletions src/factory/ZoraCreator1155FactoryImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ contract ZoraCreator1155FactoryImpl is IZoraCreator1155Factory, ContractVersionB
}

/// @notice Creates a new ZoraCreator1155 contract
/// @param contractURI The URI for the contract metadata
/// @param newContractURI The URI for the contract metadata
/// @param name The name of the contract
/// @param defaultRoyaltyConfiguration The default royalty configuration for the contract
/// @param defaultAdmin The default admin for the contract
/// @param setupActions The actions to perform on the new contract upon initialization
function createContract(
string memory contractURI,
string memory newContractURI,
string calldata name,
ICreatorRoyaltiesControl.RoyaltyConfiguration memory defaultRoyaltyConfiguration,
address payable defaultAdmin,
Expand All @@ -74,12 +74,12 @@ contract ZoraCreator1155FactoryImpl is IZoraCreator1155Factory, ContractVersionB
newContract: address(newContract),
creator: msg.sender,
defaultAdmin: defaultAdmin,
contractURI: contractURI,
contractURI: newContractURI,
name: name,
defaultRoyaltyConfiguration: defaultRoyaltyConfiguration
});

newContract.initialize(contractURI, defaultRoyaltyConfiguration, defaultAdmin, setupActions);
newContract.initialize(newContractURI, defaultRoyaltyConfiguration, defaultAdmin, setupActions);

return address(newContract);
}
Expand Down
10 changes: 10 additions & 0 deletions src/interfaces/ILimitedMintPerAddress.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC165Upgradeable.sol";

interface ILimitedMintPerAddress is IERC165Upgradeable {
error UserExceedsMintLimit(address user, uint256 limit, uint256 requestedAmount);

function getMintedPerWallet(address token, uint256 tokenId, address wallet) external view returns (uint256);
}
2 changes: 1 addition & 1 deletion src/minters/SaleStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IMinter1155} from "../interfaces/IMinter1155.sol";
import {IContractMetadata} from "../interfaces/IContractMetadata.sol";
import {IVersionedContract} from "../interfaces/IVersionedContract.sol";
import {ICreatorCommands} from "../interfaces/ICreatorCommands.sol";
import {SaleCommandHelper} from "./SaleCommandHelper.sol";
import {SaleCommandHelper} from "./utils/SaleCommandHelper.sol";

/// @notice Sales Strategy Helper contract template on top of IMinter1155
/// @author @iainnash / @tbtstl
Expand Down
19 changes: 10 additions & 9 deletions src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {Enjoy} from "_imagine/mint/Enjoy.sol";
import {IMinter1155} from "../../interfaces/IMinter1155.sol";
import {ICreatorCommands} from "../../interfaces/ICreatorCommands.sol";
import {TransferHelperUtils} from "../../utils/TransferHelperUtils.sol";
import {SaleStrategy} from "../SaleStrategy.sol";
import {SaleCommandHelper} from "../SaleCommandHelper.sol";
import {SaleCommandHelper} from "../utils/SaleCommandHelper.sol";
import {LimitedMintPerAddress} from "../utils/LimitedMintPerAddress.sol";

/*
Expand Down Expand Up @@ -35,7 +37,7 @@ import {SaleCommandHelper} from "../SaleCommandHelper.sol";
/// @title ZoraCreatorFixedPriceSaleStrategy
/// @notice A sale strategy for ZoraCreator that allows for fixed price sales over a given time period
/// @author @iainnash / @tbtstl
contract ZoraCreatorFixedPriceSaleStrategy is SaleStrategy {
contract ZoraCreatorFixedPriceSaleStrategy is Enjoy, SaleStrategy, LimitedMintPerAddress {
struct SalesConfig {
/// @notice Unix timestamp for the sale start
uint64 saleStart;
Expand All @@ -52,7 +54,6 @@ contract ZoraCreatorFixedPriceSaleStrategy is SaleStrategy {
mapping(address => mapping(uint256 => SalesConfig)) internal salesConfigs;
// target -> tokenId -> settings

mapping(address => mapping(uint256 => mapping(address => uint256))) internal mintedPerAddress;
// target -> tokenId -> minter -> count

using SaleCommandHelper for ICreatorCommands.CommandSet;
Expand All @@ -68,13 +69,12 @@ contract ZoraCreatorFixedPriceSaleStrategy is SaleStrategy {

/// @notice The version of the sale strategy
function contractVersion() external pure override returns (string memory) {
return "0.0.1";
return "1.0.0";
}

error WrongValueSent();
error SaleEnded();
error SaleHasNotStarted();
error MintedTooManyForAddress();

event SaleSet(address indexed mediaContract, uint256 indexed tokenId, SalesConfig salesConfig);

Expand Down Expand Up @@ -113,10 +113,7 @@ contract ZoraCreatorFixedPriceSaleStrategy is SaleStrategy {

// Check minted per address limit
if (config.maxTokensPerAddress > 0) {
mintedPerAddress[msg.sender][tokenId][mintTo] += quantity;
if (mintedPerAddress[msg.sender][tokenId][mintTo] > config.maxTokensPerAddress) {
revert MintedTooManyForAddress();
}
_requireMintNotOverLimitAndUpdate(config.maxTokensPerAddress, quantity, msg.sender, tokenId, mintTo);
}

bool shouldTransferFunds = config.fundsRecipient != address(0);
Expand Down Expand Up @@ -151,4 +148,8 @@ contract ZoraCreatorFixedPriceSaleStrategy is SaleStrategy {
function sale(address tokenContract, uint256 tokenId) external view returns (SalesConfig memory) {
return salesConfigs[tokenContract][tokenId];
}

function supportsInterface(bytes4 interfaceId) public pure virtual override(LimitedMintPerAddress, SaleStrategy) returns (bool) {
return super.supportsInterface(interfaceId) || LimitedMintPerAddress.supportsInterface(interfaceId) || SaleStrategy.supportsInterface(interfaceId);
}
}
26 changes: 14 additions & 12 deletions src/minters/merkle/ZoraCreatorMerkleMinterStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {Enjoy} from "_imagine/mint/Enjoy.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {IMinter1155} from "../../interfaces/IMinter1155.sol";
import {ICreatorCommands} from "../../interfaces/ICreatorCommands.sol";
import {TransferHelperUtils} from "../../utils/TransferHelperUtils.sol";
import {SaleStrategy} from "../SaleStrategy.sol";
import {ICreatorCommands} from "../../interfaces/ICreatorCommands.sol";
import {SaleCommandHelper} from "../SaleCommandHelper.sol";
import {SaleCommandHelper} from "../utils/SaleCommandHelper.sol";
import {LimitedMintPerAddress} from "../utils/LimitedMintPerAddress.sol";

/*
Expand Down Expand Up @@ -37,7 +39,7 @@ import {SaleCommandHelper} from "../SaleCommandHelper.sol";
/// @title ZoraCreatorMerkleMinterStrategy
/// @notice Mints tokens based on a merkle tree, for presales for example
/// @author @iainnash / @tbtstl
contract ZoraCreatorMerkleMinterStrategy is SaleStrategy {
contract ZoraCreatorMerkleMinterStrategy is Enjoy, SaleStrategy, LimitedMintPerAddress {
using SaleCommandHelper for ICreatorCommands.CommandSet;

/// @notice General merkle sale settings
Expand All @@ -59,13 +61,8 @@ contract ZoraCreatorMerkleMinterStrategy is SaleStrategy {
mapping(address => mapping(uint256 => MerkleSaleSettings)) public allowedMerkles;
// target -> tokenId -> settings

/// @notice Storage for the number minted per address
mapping(address => mapping(uint256 => mapping(address => uint256))) public mintedPerAddress;
// target -> tokenId -> minter -> count

error SaleEnded();
error SaleHasNotStarted();
error MintedTooManyForAddress();
error WrongValueSent();
error InvalidMerkleProof(address mintTo, bytes32[] merkleProof, bytes32 merkleRoot);

Expand All @@ -81,7 +78,7 @@ contract ZoraCreatorMerkleMinterStrategy is SaleStrategy {

/// @notice The version of the sale strategy
function contractVersion() external pure override returns (string memory) {
return "0.0.1";
return "1.0.0";
}

error MerkleClaimsExceeded();
Expand Down Expand Up @@ -120,10 +117,7 @@ contract ZoraCreatorMerkleMinterStrategy is SaleStrategy {
}

if (maxQuantity > 0) {
mintedPerAddress[msg.sender][tokenId][mintTo] += quantity;
if (mintedPerAddress[msg.sender][tokenId][mintTo] > maxQuantity) {
revert MintedTooManyForAddress();
}
_requireMintNotOverLimitAndUpdate(maxQuantity, quantity, msg.sender, tokenId, mintTo);
}

if (quantity * pricePerToken != ethValueSent) {
Expand Down Expand Up @@ -162,7 +156,15 @@ contract ZoraCreatorMerkleMinterStrategy is SaleStrategy {
}

/// @notice Gets the sale configuration for a token
/// @param tokenContract address to look up sale for
/// @param tokenId token ID to look up sale for
function sale(address tokenContract, uint256 tokenId) external view returns (MerkleSaleSettings memory) {
return allowedMerkles[tokenContract][tokenId];
}

/// @notice IERC165 interface
/// @param interfaceId intrfaceinterface id to match
function supportsInterface(bytes4 interfaceId) public pure virtual override(LimitedMintPerAddress, SaleStrategy) returns (bool) {
return super.supportsInterface(interfaceId) || LimitedMintPerAddress.supportsInterface(interfaceId) || SaleStrategy.supportsInterface(interfaceId);
}
}
26 changes: 26 additions & 0 deletions src/minters/utils/LimitedMintPerAddress.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {ILimitedMintPerAddress} from "../../interfaces/ILimitedMintPerAddress.sol";

contract LimitedMintPerAddress is ILimitedMintPerAddress {
/// @notice Storage for slot to check user mints
/// @notice target contract -> tokenId -> minter user -> numberMinted
/// @dev No gap or stroage interface since this is used within non-upgradeable contracts
mapping(address => mapping(uint256 => mapping(address => uint256))) internal mintedPerAddress;

function getMintedPerWallet(address tokenContract, uint256 tokenId, address wallet) external view returns (uint256) {
return mintedPerAddress[tokenContract][tokenId][wallet];
}

function _requireMintNotOverLimitAndUpdate(uint256 limit, uint256 numRequestedMint, address tokenContract, uint256 tokenId, address wallet) internal {
mintedPerAddress[tokenContract][tokenId][wallet] += numRequestedMint;
if (mintedPerAddress[tokenContract][tokenId][wallet] > limit) {
revert UserExceedsMintLimit(wallet, limit, mintedPerAddress[tokenContract][tokenId][wallet]);
}
}

function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
return interfaceId == type(ILimitedMintPerAddress).interfaceId;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {ICreatorCommands} from "../interfaces/ICreatorCommands.sol";
import {ICreatorCommands} from "../../interfaces/ICreatorCommands.sol";

/// @title SaleCommandHelper
/// @notice Helper library for creating commands for the sale contract
Expand Down
2 changes: 1 addition & 1 deletion src/version/ContractVersionBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import {IVersionedContract} from "../interfaces/IVersionedContract.sol";
contract ContractVersionBase is IVersionedContract {
/// @notice The version of the contract
function contractVersion() external pure override returns (string memory) {
return "1.0.0";
return "1.0.1";
}
}
Loading

0 comments on commit 8d08ce2

Please sign in to comment.