Skip to content

Commit

Permalink
Support dynamic premint sale (#389)
Browse files Browse the repository at this point in the history
  • Loading branch information
oveddan authored May 16, 2024
1 parent d83bc08 commit 7ec997e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 3 deletions.
17 changes: 16 additions & 1 deletion packages/1155-contracts/src/interfaces/IERC20Minter.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IERC20Minter {
import {IMinterPremintSetup} from "./IMinterPremintSetup.sol";

interface IERC20Minter is IMinterPremintSetup {
struct RewardsSettings {
/// @notice Amount of the create referral reward
uint256 createReferralReward;
Expand All @@ -28,6 +30,14 @@ interface IERC20Minter {
address currency;
}

struct PremintSalesConfig {
uint64 duration;
uint64 maxTokensPerAddress;
uint256 pricePerToken;
address fundsRecipient;
address currency;
}

struct ERC20MinterConfig {
/// @notice The address of the Zora rewards recipient
address zoraRewardRecipientAddress;
Expand Down Expand Up @@ -136,6 +146,11 @@ interface IERC20Minter {
/// @param salesConfig The sale config to set
function setSale(uint256 tokenId, SalesConfig memory salesConfig) external;

/// @notice Dynamically builds a SalesConfig from a PremintSalesConfig, taking into consideration the current block timestamp
/// and the PremintSalesConfig's duration.
/// @param config The PremintSalesConfig to build the SalesConfig from
function buildSalesConfigForPremint(PremintSalesConfig memory config) external view returns (SalesConfig memory);

/// @notice Returns the sale config for a given token
/// @param tokenContract The TokenContract address
/// @param tokenId The ID of the token to get the sale config for
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/// Used on Minters to support setting the sales config for a premint based token config
interface IMinterPremintSetup {
/// Sets the sales config for ta token based on the premint sales config, which's values
/// are to be decoded by the corresponding minter.
function setPremintSale(uint256 tokenId, bytes calldata premintSalesConfig) external;
}
38 changes: 36 additions & 2 deletions packages/1155-contracts/src/minters/erc20/ERC20Minter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProtocolRewards.sol";
import {IERC20Minter} from "../../interfaces/IERC20Minter.sol";
import {IMinterPremintSetup} from "../../interfaces/IMinterPremintSetup.sol";
import {LimitedMintPerAddress} from "../../minters/utils/LimitedMintPerAddress.sol";
import {SaleStrategy} from "../../minters/SaleStrategy.sol";
import {ICreatorCommands} from "../../interfaces/ICreatorCommands.sol";
Expand Down Expand Up @@ -273,7 +274,7 @@ contract ERC20Minter is ReentrancyGuard, IERC20Minter, SaleStrategy, LimitedMint
/// @notice Sets the sale config for a given token
/// @param tokenId The ID of the token to set the sale config for
/// @param salesConfig The sale config to set
function setSale(uint256 tokenId, SalesConfig memory salesConfig) external {
function setSale(uint256 tokenId, SalesConfig memory salesConfig) public {
_requireNotAddressZero(salesConfig.currency);
_requireNotAddressZero(salesConfig.fundsRecipient);

Expand All @@ -287,6 +288,35 @@ contract ERC20Minter is ReentrancyGuard, IERC20Minter, SaleStrategy, LimitedMint
emit SaleSet(msg.sender, tokenId, salesConfig);
}

/// @notice Dynamically builds a SalesConfig from a PremintSalesConfig, taking into consideration the current block timestamp
/// and the PremintSalesConfig's duration.
/// @param config The PremintSalesConfig to build the SalesConfig from
function buildSalesConfigForPremint(PremintSalesConfig memory config) public view returns (ERC20Minter.SalesConfig memory) {
uint64 saleStart = uint64(block.timestamp);
uint64 saleEnd = config.duration == 0 ? type(uint64).max : saleStart + config.duration;
return
IERC20Minter.SalesConfig({
saleStart: saleStart,
saleEnd: saleEnd,
maxTokensPerAddress: config.maxTokensPerAddress,
pricePerToken: config.pricePerToken,
fundsRecipient: config.fundsRecipient,
currency: config.currency
});
}

/// @notice Sets the sales config based for the msg.sender on the tokenId, from the abi encoded premint sales config, by
/// abi decoding it, and dynamically building the SalesConfig. The saleStart will be the current block timestamp,
/// and saleEnd will be the current block timestamp + the duration in the PremintSalesConfig.
/// @param tokenId The ID of the token to set the sale config for
/// @param encodedPremintSalesConfig The abi encoded PremintSalesConfig
function setPremintSale(uint256 tokenId, bytes calldata encodedPremintSalesConfig) external override {
PremintSalesConfig memory premintSalesConfig = abi.decode(encodedPremintSalesConfig, (PremintSalesConfig));
SalesConfig memory salesConfig = buildSalesConfigForPremint(premintSalesConfig);

setSale(tokenId, salesConfig);
}

/// @notice Deletes the sale config for a given token
/// @param tokenId The ID of the token to reset the sale config for
function resetSale(uint256 tokenId) external override {
Expand All @@ -305,7 +335,11 @@ contract ERC20Minter is ReentrancyGuard, IERC20Minter, SaleStrategy, LimitedMint

/// @notice IERC165 interface support
function supportsInterface(bytes4 interfaceId) public pure virtual override(LimitedMintPerAddress, SaleStrategy) returns (bool) {
return super.supportsInterface(interfaceId) || LimitedMintPerAddress.supportsInterface(interfaceId) || SaleStrategy.supportsInterface(interfaceId);
return
super.supportsInterface(interfaceId) ||
LimitedMintPerAddress.supportsInterface(interfaceId) ||
SaleStrategy.supportsInterface(interfaceId) ||
interfaceId == type(IMinterPremintSetup).interfaceId;
}

/// @notice Reverts as `requestMint` is not used in the ERC20 minter. Call `mint` instead.
Expand Down
24 changes: 24 additions & 0 deletions packages/1155-contracts/test/minters/erc20/ERC20Minter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -521,4 +521,28 @@ contract ERC20MinterTest is Test {
emit ERC20MinterConfigSet(newConfig);
minter.setERC20MinterConfig(newConfig);
}

function test_ERC20MinterSetPremintSale() public {
IERC20Minter.PremintSalesConfig memory newConfig = IERC20Minter.PremintSalesConfig({
duration: 3000,
maxTokensPerAddress: 200,
pricePerToken: 50000,
fundsRecipient: makeAddr("fundsRecipient"),
currency: makeAddr("currency")
});
address erc1155Contract = makeAddr("contract");
uint256 tokenId = 10;

vm.prank(erc1155Contract);
minter.setPremintSale(10, abi.encode(newConfig));

IERC20Minter.SalesConfig memory salesConfig = minter.sale(erc1155Contract, tokenId);

assertEq(salesConfig.pricePerToken, newConfig.pricePerToken);
assertEq(salesConfig.saleStart, block.timestamp);
assertEq(salesConfig.saleEnd, block.timestamp + newConfig.duration);
assertEq(salesConfig.maxTokensPerAddress, newConfig.maxTokensPerAddress);
assertEq(salesConfig.fundsRecipient, newConfig.fundsRecipient);
assertEq(salesConfig.currency, newConfig.currency);
}
}

0 comments on commit 7ec997e

Please sign in to comment.