@@ -43,8 +43,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
4343 error OwnerMismatch ();
4444
4545 // Events
46- event Init (string social , string website , uint256 multiple , uint256 closingDelay , uint256 unstakingFee , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 deliveryType , bool active , bool transferable );
47- event Setup (address escrowToken , uint256 closingDelay , uint256 unstakingFee , string website , string social , string image , string description , uint256 deliveryType );
46+ event Init (string social , string website , uint256 multiple , uint256 closingDelay , uint256 unstakingFee , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 deliveryType , bool active , bool transferable , bool timeBasedClosing );
47+ event Setup (address escrowToken , uint256 closingDelay , uint256 unstakingFee , uint256 dealMinimum , uint256 dealMaximum , string website , string social , string image , string description , uint256 deliveryType );
4848 event Configure (string description , string social , string website , uint256 closingTime , uint256 dealMinimum , uint256 dealMaximum , uint256 multiple );
4949 event StateUpdated (State state );
5050 event Transferable (bool transferable );
@@ -81,6 +81,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
8181 IWhitelist public stakersWhitelist;
8282 IWhitelist public claimsWhitelist;
8383
84+ uint256 lastStakeTimestamp;
8485 mapping (uint256 tokenId = > uint256 ) public stakedAmount;
8586 mapping (uint256 tokenId = > uint256 ) public claimedAmount;
8687 mapping (address staker = > uint256 ) public stakes;
@@ -110,6 +111,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
110111 bool active;
111112 bool cancelled;
112113 bool transferable;
114+ bool timeBasedClosing;
113115 }
114116
115117 Configuration private config;
@@ -142,7 +144,9 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
142144 if (bytes (symbol_).length == 0 ) revert ZeroDetected ();
143145
144146 if (config_.sponsor == ADDRESS_ZERO) revert ZeroDetected ();
145- _validClosingTime (config_.closingTime, config_.closingDelay);
147+ if (config_.timeBasedClosing) {
148+ _validClosingTime (config_.closingTime, config_.closingDelay);
149+ }
146150 if (config_.dealMinimum > config_.dealMaximum) revert BadStakesRange ();
147151 if (config_.multiple < 1e18 ) revert ZeroDetected ();
148152
@@ -168,7 +172,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
168172 config_.dealMaximum,
169173 config_.deliveryType,
170174 config_.active,
171- config_.transferable
175+ config_.transferable,
176+ config_.timeBasedClosing
172177 );
173178 }
174179
@@ -207,24 +212,29 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
207212 address escrowToken_ ,
208213 uint256 closingDelay_ ,
209214 uint256 unstakingFee_ ,
215+ uint256 dealMinimum_ ,
216+ uint256 dealMaximum_ ,
210217 string memory social_ ,
211218 string memory website_ ,
212219 string memory image_ ,
213220 string memory description_ ,
214221 uint256 deliveryType_
215222 ) external onlySponsor {
216223 if (state () != State.Setup) revert CannotSetup ();
224+ if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
217225
218226 config.escrowToken = escrowToken_;
219227 config.closingDelay = closingDelay_;
220228 config.unstakingFee = unstakingFee_;
229+ config.dealMinimum = dealMinimum_;
230+ config.dealMaximum = dealMaximum_;
221231 config.social = social_;
222232 config.website = website_;
223233 config.image = image_;
224234 config.description = description_;
225235 config.deliveryType = deliveryType_;
226236
227- emit Setup (escrowToken_, closingDelay_, unstakingFee_, website_, social_, image_, description_, deliveryType_);
237+ emit Setup (escrowToken_, closingDelay_, unstakingFee_, dealMaximum_, dealMinimum_, website_, social_, image_, description_, deliveryType_);
228238 }
229239
230240 /**
@@ -255,17 +265,22 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
255265 uint256 dealMaximum_ ,
256266 uint256 multiple_
257267 ) external onlySponsor {
258- _canConfigure ();
259- _validClosingTime (closingTime_, config.closingDelay);
260- if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
268+ _canConfigure ();
261269 if (multiple_ < 1e18 ) revert ZeroDetected ();
262270
271+ if (config.timeBasedClosing) {
272+ _validClosingTime (closingTime_, config.closingDelay);
273+ if (dealMinimum_ > dealMaximum_) revert BadStakesRange ();
274+
275+ config.dealMinimum = dealMinimum_;
276+ config.dealMaximum = dealMaximum_;
277+ config.closingTime = closingTime_;
278+ }
279+
263280 config.description = description_;
264281 config.social = social_;
265282 config.website = website_;
266- config.closingTime = closingTime_;
267- config.dealMinimum = dealMinimum_;
268- config.dealMaximum = dealMaximum_;
283+
269284 config.multiple = multiple_;
270285
271286 emit Configure (description_, social_, website_, closingTime_, dealMinimum_, dealMaximum_, multiple_);
@@ -301,9 +316,6 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
301316 * @param transferable_ Boolean indicating if NFTs are transferable
302317 */
303318 function setTransferable (bool transferable_ ) external onlyArbitrator {
304- if (state () == State.Cancelled) revert CannotConfigure ();
305- if (_afterClosed ()) revert CannotConfigure ();
306-
307319 config.transferable = transferable_;
308320 emit Transferable (transferable_);
309321 }
@@ -374,7 +386,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
374386 if (state () < State.Claiming) revert CannotRecover ();
375387
376388 if (state () == State.Claiming) {
377- if (_totalStaked (_tokenId) >= config.dealMinimum ) revert MinimumReached ();
389+ if (_minimumReached () ) revert MinimumReached ();
378390 }
379391
380392 AccountV3TBD tokenBoundAccount = getTokenBoundAccount (tokenId);
@@ -449,17 +461,31 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
449461 */
450462 function state () public view returns (State) {
451463 if (config.cancelled) return State.Cancelled;
464+ if (_isClaimed ()) return State.Closed;
452465
453- if (_beforeClose ()) {
454- if (config.active) return State.Active;
455- return State.Setup;
456- }
466+ if (config.timeBasedClosing){
467+ if (_afterClosed (config.closingTime)) {
468+ return State.Cancelled;
469+ }
470+
471+ if (_beforeClose ()) {
472+ if (config.active) return State.Active;
473+ return State.Setup;
474+ }
457475
458- if (_afterClosed ()) return State.Closed;
476+ return State.Claiming;
477+ } else {
478+ if (_minimumReached ()) {
479+ if (_afterClosed (lastStakeTimestamp)){
480+ return State.Cancelled;
481+ }
459482
460- if (_isClaimed ()) return State.Closed;
483+ return State.Claiming;
484+ }
461485
462- return State.Claiming;
486+ if (config.active) return State.Active;
487+ return State.Setup;
488+ }
463489 }
464490
465491 /**
@@ -557,6 +583,13 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
557583 return config;
558584 }
559585
586+ /**
587+ * @notice Check if the minimum has been reached
588+ */
589+ function _minimumReached () private view returns (bool ) {
590+ return _totalStaked (_tokenId) >= config.dealMinimum;
591+ }
592+
560593 /**
561594 * @notice Check if all tokens have been claimed by the sponsor
562595 */
@@ -574,8 +607,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
574607 /**
575608 * @notice Check if the current time is after closing time
576609 */
577- function _afterClosed () private view returns (bool ) {
578- return config.closingTime > 0 && block .timestamp > (config.closingTime + CLAIMING_PERIOD);
610+ function _afterClosed (uint256 since ) private view returns (bool ) {
611+ return since > 0 && block .timestamp > (since + CLAIMING_PERIOD);
579612 }
580613
581614 /**
@@ -625,7 +658,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
625658 function _canClaim () internal view {
626659 if (_claimId == _tokenId) revert TokenOutOfBounds ();
627660 if (state () != State.Claiming) revert NotInClaimingState ();
628- if (_totalStaked (_tokenId) < config.dealMinimum ) revert MinimumNotReached ();
661+ if (! _minimumReached () ) revert MinimumNotReached ();
629662 }
630663
631664 /**
@@ -634,14 +667,16 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
634667 function _canConfigure () internal view {
635668 if (state () >= State.Closed) revert CannotConfigure ();
636669 if (state () == State.Claiming) {
637- if (_totalStaked (_tokenId) >= config.dealMinimum ) revert MinimumReached ();
670+ if (_minimumReached () ) revert MinimumReached ();
638671 }
639672 }
640673
641674 function _validateActivation () internal view {
642675 if (config.escrowToken == ADDRESS_ZERO) revert ZeroDetected ();
643- if (config.closingDelay <= 0 ) revert ZeroDetected ();
644- if (config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig ();
676+ if (config.timeBasedClosing) {
677+ if (config.closingDelay <= 0 ) revert ZeroDetected ();
678+ if (config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig ();
679+ }
645680 if (config.unstakingFee > MAX_FEE) revert ClosingFeeTooBig ();
646681 if (bytes (config.website).length == 0 ) revert ZeroDetected ();
647682 if (bytes (config.social).length == 0 ) revert ZeroDetected ();
@@ -670,6 +705,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
670705 uint256 newTokenId = _tokenId++ ;
671706 stakedAmount[newTokenId] = amount;
672707 stakes[staker] = currentStake;
708+ lastStakeTimestamp = block .timestamp ;
673709
674710 _safeMint (staker, newTokenId);
675711
0 commit comments