diff --git a/contracts/BucketAuction.sol b/contracts/BucketAuction.sol index 854282b..415fa4c 100644 --- a/contracts/BucketAuction.sol +++ b/contracts/BucketAuction.sol @@ -12,9 +12,11 @@ import "./ERC721M.sol"; contract BucketAuction is IBucketAuction, ERC721M { bool private _claimable; + bool private _firstTokenSent; + uint64 private _startTimeUnixSeconds; + uint64 private _endTimeUnixSeconds; uint256 private _minimumContributionInWei; uint256 private _price; - bool private _auctionActive; mapping(address => User) private _userData; constructor( @@ -24,7 +26,10 @@ contract BucketAuction is IBucketAuction, ERC721M { uint256 maxMintableSupply, uint256 globalWalletLimit, address cosigner, - uint256 minimumContributionInWei + uint256 minimumContributionInWei, + uint64 startTimeUnixSeconds, + uint64 endTimeUnixSeconds + ) ERC721M( collectionName, @@ -32,13 +37,14 @@ contract BucketAuction is IBucketAuction, ERC721M { tokenURISuffix, maxMintableSupply, globalWalletLimit, - cosigner, - /* timestampExpirySeconds= */ - 300 + cosigner ) { _claimable = false; _minimumContributionInWei = minimumContributionInWei; + _startTimeUnixSeconds = startTimeUnixSeconds; + _endTimeUnixSeconds = endTimeUnixSeconds ; + _firstTokenSent = false; } modifier isClaimable() { @@ -47,12 +53,12 @@ contract BucketAuction is IBucketAuction, ERC721M { } modifier isAuctionActive() { - if (!_auctionActive) revert BucketAuctionNotActive(); + if (_startTimeUnixSeconds > block.timestamp || _endTimeUnixSeconds <= block.timestamp) revert BucketAuctionNotActive(); _; } modifier isAuctionInactive() { - if (_auctionActive) revert BucketAuctionActive(); + if (_startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds) revert BucketAuctionActive(); _; } @@ -64,8 +70,16 @@ contract BucketAuction is IBucketAuction, ERC721M { return _price; } + function getStartTimeUnixSecods() external view returns (uint64) { + return _startTimeUnixSeconds; + } + + function getEndTimeUnixSecods() external view returns (uint64) { + return _endTimeUnixSeconds; + } + function getAuctionActive() external view returns (bool) { - return _auctionActive; + return _startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds; } function getUserData(address user) external view returns (User memory) { @@ -82,13 +96,17 @@ contract BucketAuction is IBucketAuction, ERC721M { } /** - * @notice begin the auction. + * @notice set the start and end times in unix seconds for the bucket auction. * @dev cannot be reactivated after price has been set. - * @param b set 'true' to start auction, set 'false' to stop auction. + * @param startTime set to unix timestamp for the auction start time. + * @param endTime set to unix timestamp for the auction end time. */ - function setAuctionActive(bool b) external onlyOwner cannotMint { + function setStartAndEndTimeUnixSeconds(uint64 startTime, uint64 endTime) external onlyOwner { if (_price != 0) revert PriceHasBeenSet(); - _auctionActive = b; + if (endTime <= startTime) revert InvalidStartAndEndTimestamp(); + + _startTimeUnixSeconds = startTime; + _endTimeUnixSeconds = endTime; } /** @@ -96,7 +114,12 @@ contract BucketAuction is IBucketAuction, ERC721M { * multiple times will increase your bid amount. All bids placed are final * and cannot be reversed. */ - function bid() external payable isAuctionActive nonReentrant cannotMint { + function bid() + external + payable + isAuctionActive + nonReentrant + { User storage bidder = _userData[msg.sender]; // get user's current bid total uint256 contribution_ = bidder.contribution; // bidder.contribution is uint216 unchecked { @@ -129,11 +152,11 @@ contract BucketAuction is IBucketAuction, ERC721M { */ function setPrice(uint256 priceInWei) external - isAuctionInactive onlyOwner - cannotMint { if (_claimable) revert CannotSetPriceIfClaimable(); + if (block.timestamp <= _endTimeUnixSeconds) revert BucketAuctionActive(); + if (_firstTokenSent) revert CannotSetPriceIfFirstTokenSent(); _price = priceInWei; emit SetPrice(priceInWei); @@ -149,6 +172,7 @@ contract BucketAuction is IBucketAuction, ERC721M { hasSupply(numberOfTokens) { _safeMint(to, numberOfTokens); + if (!_firstTokenSent && numberOfTokens > 0) _firstTokenSent = true; } /** diff --git a/contracts/DutchAuction.sol b/contracts/DutchAuction.sol index 515a155..50ca0f8 100644 --- a/contracts/DutchAuction.sol +++ b/contracts/DutchAuction.sol @@ -33,9 +33,7 @@ contract DutchAuction is IDutchAuction, ERC721M { tokenURISuffix, maxMintableSupply, globalWalletLimit, - cosigner, - /* timestampExpirySeconds= */ - 300 + cosigner ) { _refundable = refundable; diff --git a/contracts/ERC721M.sol b/contracts/ERC721M.sol index 3572208..9d538f2 100644 --- a/contracts/ERC721M.sol +++ b/contracts/ERC721M.sol @@ -12,20 +12,21 @@ import "./IERC721M.sol"; contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { using ECDSA for bytes32; + uint64 public constant MIN_STAGE_INTERVAL_SECONDS = 60; + uint64 public constant CROSSMINT_TIMESTAMP_EXPIRY_SECONDS = 300; + bool private _mintable; - bool private _baseURIPermanent; - // @notice Specify how long a signature from cosigner is valid for recommend 300 seconds - uint64 private _timestampExpirySeconds; - address private _cosigner; - address private _crossmintAddress; - uint256 private _activeStage; + string private _currentBaseURI; uint256 private _maxMintableSupply; uint256 private _globalWalletLimit; - string private _currentBaseURI; string private _tokenURISuffix; + bool private _baseURIPermanent; + address private _cosigner; + address private _crossmintAddress; MintStageInfo[] private _mintStages; + // Need this because struct cannot have nested mapping mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; mapping(uint256 => uint256) private _stageMintedCounts; @@ -36,8 +37,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { string memory tokenURISuffix, uint256 maxMintableSupply, uint256 globalWalletLimit, - address cosigner, - uint64 timestampExpirySeconds + address cosigner ) ERC721A(collectionName, collectionSymbol) { if (globalWalletLimit > maxMintableSupply) revert GlobalWalletLimitOverflow(); @@ -47,7 +47,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _globalWalletLimit = globalWalletLimit; _tokenURISuffix = tokenURISuffix; _cosigner = cosigner; // ethers.constants.AddressZero for no cosigning - _timestampExpirySeconds = timestampExpirySeconds; } modifier canMint() { @@ -78,11 +77,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit SetCosigner(cosigner); } - function setTimestampExpirySeconds(uint64 expiry) external onlyOwner { - _timestampExpirySeconds = expiry; - emit SetTimestampExpirySeconds(expiry); - } - function getCrossmintAddress() external view override returns (address) { return _crossmintAddress; } @@ -98,12 +92,12 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { _mintStages.pop(); } - uint64 timestampExpirySeconds = getTimestampExpirySeconds(); for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { if ( newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + timestampExpirySeconds + newStages[i - 1].endTimeUnixSeconds + + MIN_STAGE_INTERVAL_SECONDS ) { revert InsufficientStageTimeGap(); } @@ -176,16 +170,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { emit SetGlobalWalletLimit(globalWalletLimit); } - function getActiveStage() external view override returns (uint256) { - return _activeStage; - } - - function setActiveStage(uint256 activeStage) external onlyOwner { - if (activeStage >= _mintStages.length) revert InvalidStage(); - _activeStage = activeStage; - emit SetActiveStage(activeStage); - } - function totalMintedByAddress(address a) external view @@ -227,7 +211,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { if ( startTimeUnixSeconds < _mintStages[index - 1].endTimeUnixSeconds + - getTimestampExpirySeconds() + MIN_STAGE_INTERVAL_SECONDS ) { revert InsufficientStageTimeGap(); } @@ -285,17 +269,17 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { uint64 timestamp, bytes calldata signature ) internal canMint hasSupply(qty) { - uint256 activeStage = _activeStage; - - if (activeStage >= _mintStages.length) revert InvalidStage(); + uint64 stageTimestamp = uint64(block.timestamp); MintStageInfo memory stage; if (_cosigner != address(0)) { assertValidCosign(msg.sender, qty, timestamp, signature); _assertValidTimestamp(timestamp); - activeStage = getActiveStageFromTimestamp(timestamp); + stageTimestamp = timestamp; } + uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); + stage = _mintStages[activeStage]; // Check value @@ -448,13 +432,11 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { revert InvalidStage(); } - function getTimestampExpirySeconds() public view override returns (uint64) { - return _timestampExpirySeconds; - } - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - getTimestampExpirySeconds()) - revert TimestampExpired(); + uint64 threshold = msg.sender == _crossmintAddress + ? CROSSMINT_TIMESTAMP_EXPIRY_SECONDS + : MIN_STAGE_INTERVAL_SECONDS; + if (timestamp < block.timestamp - threshold) revert TimestampExpired(); } function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) diff --git a/contracts/ERC721MCallback.sol b/contracts/ERC721MCallback.sol index c151f13..638a5cc 100644 --- a/contracts/ERC721MCallback.sol +++ b/contracts/ERC721MCallback.sol @@ -14,8 +14,7 @@ contract ERC721MCallback is ERC721M, IERC721MCallback { string memory tokenURISuffix, uint256 maxMintableSupply, uint256 globalWalletLimit, - address cosigner, - uint64 timestampExpirySeconds + address cosigner ) ERC721M( collectionName, @@ -23,8 +22,7 @@ contract ERC721MCallback is ERC721M, IERC721MCallback { tokenURISuffix, maxMintableSupply, globalWalletLimit, - cosigner, - timestampExpirySeconds + cosigner ) {} diff --git a/contracts/IBucketAuction.sol b/contracts/IBucketAuction.sol index f1029f5..5da7c63 100644 --- a/contracts/IBucketAuction.sol +++ b/contracts/IBucketAuction.sol @@ -7,6 +7,7 @@ interface IBucketAuction { error BucketAuctionNotActive(); error CannotSendMoreThanUserPurchased(); error CannotSetPriceIfClaimable(); + error CannotSetPriceIfFirstTokenSent(); error LowerThanMinBidAmount(); error NotClaimable(); error PriceHasBeenSet(); diff --git a/contracts/IERC721M.sol b/contracts/IERC721M.sol index 4a83c61..7e8c553 100644 --- a/contracts/IERC721M.sol +++ b/contracts/IERC721M.sol @@ -52,7 +52,6 @@ interface IERC721M is IERC721AQueryable { event SetGlobalWalletLimit(uint256 globalWalletLimit); event SetActiveStage(uint256 activeStage); event SetBaseURI(string baseURI); - event SetTimestampExpirySeconds(uint64 expiry); event PermanentBaseURI(string baseURI); event Withdraw(uint256 value); @@ -81,10 +80,6 @@ interface IERC721M is IERC721AQueryable { uint256 ); - function getActiveStage() external view returns (uint256); - - function getTimestampExpirySeconds() external view returns (uint64); - function getActiveStageFromTimestamp(uint64 timestamp) external view diff --git a/hardhat.config.ts b/hardhat.config.ts index 678e1e0..8addb87 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -8,11 +8,11 @@ import 'hardhat-watcher'; import { HardhatUserConfig, task, types } from 'hardhat/config'; import 'solidity-coverage'; -import { setActiveStage } from './scripts/setActiveStage'; import { setCrossmintAddress } from './scripts/setCrossmintAddress'; import { setStages } from './scripts/setStages'; import { setMintable } from './scripts/setMintable'; import { deploy } from './scripts/deploy'; +import { deployBA } from './scripts/deployBA'; import { setBaseURI } from './scripts/setBaseURI'; import { mint } from './scripts/mint'; import { ownerMint } from './scripts/ownerMint'; @@ -66,11 +66,6 @@ task('setStages', 'Set stages for ERC721M') .addParam('stages', 'stages json file') .setAction(setStages); -task('setActiveStage', 'Set active stage for ERC721M') - .addParam('contract', 'contract address') - .addParam('stage', 'stage index to set to active') - .setAction(setActiveStage); - task('setMintable', 'Set mintable state for ERC721M') .addParam('contract', 'contract address') .addParam('mintable', 'mintable state', 'true', types.boolean) @@ -102,6 +97,7 @@ task('setCrossmintAddress', 'Set crossmint address') task('mint', 'Mint token(s)') .addParam('contract', 'contract address') .addParam('qty', 'quantity to mint', '1') + .addParam('minttime', 'time of the mint') .setAction(mint); task('ownerMint', 'Mint token(s) as owner') @@ -119,4 +115,24 @@ task('setMaxMintableSupply', 'set max mintable supply') .addParam('contract', 'contract address') .addParam('supply', 'new supply') .setAction(setMaxMintableSupply); + +task('deployBA', 'Deploy BucketAuction') + .addParam('name', 'name') + .addParam('symbol', 'symbol') + .addParam('maxsupply', 'max supply') + .addParam('tokenurisuffix', 'token uri suffix', '.json') + .addParam('globalwalletlimit', 'global wallet limit') + .addOptionalParam( + 'cosigner', + 'cosigner address (0x00...000 if not using cosign)', + '0x0000000000000000000000000000000000000000', + ) + .addParam( + 'mincontributioninwei', + 'The minimum contribution in wei required only for the AcutionBucket', + ) + .addParam('auctionstarttime', 'The start time of the bucket auction') + .addParam('auctionendtime', 'The end time of the bucket auction') + .setAction(deployBA); + export default config; diff --git a/package-lock.json b/package-lock.json index a6eb7f2..53b358c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@magiceden-oss/erc721m", - "version": "0.0.8", + "version": "0.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@magiceden-oss/erc721m", - "version": "0.0.8", + "version": "0.0.5", "dependencies": { "@openzeppelin/contracts": "^4.7.3", "erc721a": "^4.2.3" diff --git a/package.json b/package.json index 254c544..07b6359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@magiceden-oss/erc721m", - "version": "0.0.8", + "version": "0.0.7", "description": "erc721m contract for Solidity", "files": [ "/contracts/**/*.sol", diff --git a/scripts/common/constants.ts b/scripts/common/constants.ts new file mode 100644 index 0000000..81e3041 --- /dev/null +++ b/scripts/common/constants.ts @@ -0,0 +1,6 @@ +// This module wraps up the constants which can be used by any script + +export const ContractDetails = { + ERC721M: { name: 'ERC721M' }, // The contract of direct sales + BucketAuction: { name: 'BucketAuction' }, // The contract of bucket auctions +}; diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 880e9b4..a9f5fa6 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -4,6 +4,7 @@ // When running the script with `npx hardhat run