Skip to content

Commit

Permalink
Make timestamp expiry configurable (me-foundation#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
mi-yu authored Oct 29, 2022
1 parent a6c94a4 commit b7df814
Show file tree
Hide file tree
Showing 25 changed files with 300 additions and 751 deletions.
54 changes: 15 additions & 39 deletions contracts/BucketAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ 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(
Expand All @@ -26,25 +24,21 @@ contract BucketAuction is IBucketAuction, ERC721M {
uint256 maxMintableSupply,
uint256 globalWalletLimit,
address cosigner,
uint256 minimumContributionInWei,
uint64 startTimeUnixSeconds,
uint64 endTimeUnixSeconds

uint256 minimumContributionInWei
)
ERC721M(
collectionName,
collectionSymbol,
tokenURISuffix,
maxMintableSupply,
globalWalletLimit,
cosigner
cosigner,
/* timestampExpirySeconds= */
300
)
{
_claimable = false;
_minimumContributionInWei = minimumContributionInWei;
_startTimeUnixSeconds = startTimeUnixSeconds;
_endTimeUnixSeconds = endTimeUnixSeconds ;
_firstTokenSent = false;
}

modifier isClaimable() {
Expand All @@ -53,12 +47,12 @@ contract BucketAuction is IBucketAuction, ERC721M {
}

modifier isAuctionActive() {
if (_startTimeUnixSeconds > block.timestamp || _endTimeUnixSeconds <= block.timestamp) revert BucketAuctionNotActive();
if (!_auctionActive) revert BucketAuctionNotActive();
_;
}

modifier isAuctionInactive() {
if (_startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds) revert BucketAuctionActive();
if (_auctionActive) revert BucketAuctionActive();
_;
}

Expand All @@ -70,16 +64,8 @@ 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 _startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds;
return _auctionActive;
}

function getUserData(address user) external view returns (User memory) {
Expand All @@ -96,30 +82,21 @@ contract BucketAuction is IBucketAuction, ERC721M {
}

/**
* @notice set the start and end times in unix seconds for the bucket auction.
* @notice begin the auction.
* @dev cannot be reactivated after price has been set.
* @param startTime set to unix timestamp for the auction start time.
* @param endTime set to unix timestamp for the auction end time.
* @param b set 'true' to start auction, set 'false' to stop auction.
*/
function setStartAndEndTimeUnixSeconds(uint64 startTime, uint64 endTime) external onlyOwner {
function setAuctionActive(bool b) external onlyOwner cannotMint {
if (_price != 0) revert PriceHasBeenSet();
if (endTime <= startTime) revert InvalidStartAndEndTimestamp();

_startTimeUnixSeconds = startTime;
_endTimeUnixSeconds = endTime;
_auctionActive = b;
}

/**
* @notice place a bid in ETH or add to your existing bid. Calling this
* multiple times will increase your bid amount. All bids placed are final
* and cannot be reversed.
*/
function bid()
external
payable
isAuctionActive
nonReentrant
{
function bid() external payable isAuctionActive nonReentrant cannotMint {
User storage bidder = _userData[msg.sender]; // get user's current bid total
uint256 contribution_ = bidder.contribution; // bidder.contribution is uint216
unchecked {
Expand Down Expand Up @@ -152,11 +129,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);
Expand All @@ -172,7 +149,6 @@ contract BucketAuction is IBucketAuction, ERC721M {
hasSupply(numberOfTokens)
{
_safeMint(to, numberOfTokens);
if (!_firstTokenSent && numberOfTokens > 0) _firstTokenSent = true;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion contracts/DutchAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ contract DutchAuction is IDutchAuction, ERC721M {
tokenURISuffix,
maxMintableSupply,
globalWalletLimit,
cosigner
cosigner,
/* timestampExpirySeconds= */
300
)
{
_refundable = refundable;
Expand Down
58 changes: 38 additions & 20 deletions contracts/ERC721M.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ 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;
string private _currentBaseURI;
uint256 private _maxMintableSupply;
uint256 private _globalWalletLimit;
string private _tokenURISuffix;
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;
uint256 private _maxMintableSupply;
uint256 private _globalWalletLimit;
string private _currentBaseURI;
string private _tokenURISuffix;

MintStageInfo[] private _mintStages;

// Need this because struct cannot have nested mapping
mapping(uint256 => mapping(address => uint32))
private _stageMintedCountsPerWallet;
mapping(uint256 => uint256) private _stageMintedCounts;
Expand All @@ -37,7 +36,8 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
string memory tokenURISuffix,
uint256 maxMintableSupply,
uint256 globalWalletLimit,
address cosigner
address cosigner,
uint64 timestampExpirySeconds
) ERC721A(collectionName, collectionSymbol) {
if (globalWalletLimit > maxMintableSupply)
revert GlobalWalletLimitOverflow();
Expand All @@ -47,6 +47,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
_globalWalletLimit = globalWalletLimit;
_tokenURISuffix = tokenURISuffix;
_cosigner = cosigner; // ethers.constants.AddressZero for no cosigning
_timestampExpirySeconds = timestampExpirySeconds;
}

modifier canMint() {
Expand Down Expand Up @@ -77,6 +78,11 @@ 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;
}
Expand All @@ -92,12 +98,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 +
MIN_STAGE_INTERVAL_SECONDS
newStages[i - 1].endTimeUnixSeconds + timestampExpirySeconds
) {
revert InsufficientStageTimeGap();
}
Expand Down Expand Up @@ -170,6 +176,16 @@ 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
Expand Down Expand Up @@ -211,7 +227,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
if (
startTimeUnixSeconds <
_mintStages[index - 1].endTimeUnixSeconds +
MIN_STAGE_INTERVAL_SECONDS
getTimestampExpirySeconds()
) {
revert InsufficientStageTimeGap();
}
Expand Down Expand Up @@ -269,17 +285,17 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
uint64 timestamp,
bytes calldata signature
) internal canMint hasSupply(qty) {
uint64 stageTimestamp = uint64(block.timestamp);
uint256 activeStage = _activeStage;

if (activeStage >= _mintStages.length) revert InvalidStage();

MintStageInfo memory stage;
if (_cosigner != address(0)) {
assertValidCosign(msg.sender, qty, timestamp, signature);
_assertValidTimestamp(timestamp);
stageTimestamp = timestamp;
activeStage = getActiveStageFromTimestamp(timestamp);
}

uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp);

stage = _mintStages[activeStage];

// Check value
Expand Down Expand Up @@ -432,11 +448,13 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
revert InvalidStage();
}

function getTimestampExpirySeconds() public view override returns (uint64) {
return _timestampExpirySeconds;
}

function _assertValidTimestamp(uint64 timestamp) internal view {
uint64 threshold = msg.sender == _crossmintAddress
? CROSSMINT_TIMESTAMP_EXPIRY_SECONDS
: MIN_STAGE_INTERVAL_SECONDS;
if (timestamp < block.timestamp - threshold) revert TimestampExpired();
if (timestamp < block.timestamp - getTimestampExpirySeconds())
revert TimestampExpired();
}

function _assertValidStartAndEndTimestamp(uint64 start, uint64 end)
Expand Down
6 changes: 4 additions & 2 deletions contracts/ERC721MCallback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ contract ERC721MCallback is ERC721M, IERC721MCallback {
string memory tokenURISuffix,
uint256 maxMintableSupply,
uint256 globalWalletLimit,
address cosigner
address cosigner,
uint64 timestampExpirySeconds
)
ERC721M(
collectionName,
collectionSymbol,
tokenURISuffix,
maxMintableSupply,
globalWalletLimit,
cosigner
cosigner,
timestampExpirySeconds
)
{}

Expand Down
1 change: 0 additions & 1 deletion contracts/IBucketAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ interface IBucketAuction {
error BucketAuctionNotActive();
error CannotSendMoreThanUserPurchased();
error CannotSetPriceIfClaimable();
error CannotSetPriceIfFirstTokenSent();
error LowerThanMinBidAmount();
error NotClaimable();
error PriceHasBeenSet();
Expand Down
5 changes: 5 additions & 0 deletions contracts/IERC721M.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ 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);

Expand Down Expand Up @@ -80,6 +81,10 @@ interface IERC721M is IERC721AQueryable {
uint256
);

function getActiveStage() external view returns (uint256);

function getTimestampExpirySeconds() external view returns (uint64);

function getActiveStageFromTimestamp(uint64 timestamp)
external
view
Expand Down
28 changes: 6 additions & 22 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -66,6 +66,11 @@ 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)
Expand Down Expand Up @@ -97,7 +102,6 @@ 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')
Expand All @@ -115,24 +119,4 @@ 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;
Loading

0 comments on commit b7df814

Please sign in to comment.