Skip to content

Commit 2478387

Browse files
committed
adds amount based closing config and functionality
1 parent a2ffbc0 commit 2478387

File tree

7 files changed

+84
-36
lines changed

7 files changed

+84
-36
lines changed

src/DealNFT.sol

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src/Reader.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ contract Reader {
4444
dealMaximum: config.dealMaximum,
4545
deliveryType: config.deliveryType,
4646
transferable: config.transferable,
47+
timeBasedClosing: config.timeBasedClosing,
4748
stakersWhitelist: dealInstance.stakersWhitelist(),
4849
claimsWhitelist: dealInstance.claimsWhitelist(),
4950
deliveryToken: address(dealInstance.deliveryToken()),

src/interfaces/IDeal.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface IDeal {
4444
uint8 escrowDecimals;
4545
StakeData[] claimed;
4646
bool transferable;
47+
bool timeBasedClosing;
4748
uint256 deliveryType;
4849
}
4950

test/DealNFTClaim.t.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ contract DealNFTClaimTest is Test {
8181
deliveryType: 0,
8282
active: false,
8383
cancelled: false,
84-
transferable: false
84+
transferable: false,
85+
timeBasedClosing: true
8586
});
8687

8788
deal = new DealNFT(
@@ -113,7 +114,7 @@ contract DealNFTClaimTest is Test {
113114
escrowToken.approve(address(deal), amount);
114115

115116
vm.startPrank(sponsor);
116-
deal.setup(address(escrowToken), 30 minutes, 50000, "https://social", "https://website", "https://image", "desc", 0);
117+
deal.setup(address(escrowToken), 30 minutes, 50000, 0, 0, "https://social", "https://website", "https://image", "desc", 0);
117118
deal.configure("desc", "https://social", "https://website", block.timestamp + 2 weeks, 0, 2000000, 1e18);
118119
deal.activate();
119120
vm.stopPrank();

test/DealNFTRecover.t.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ contract DealNFTRecoverTest is Test, DealSetup {
6161
assertEq(escrowToken.balanceOf(staker1), amount);
6262
}
6363

64-
function test_RecoverAfterClosed() public {
64+
function test_RecoverAfterExpired() public {
6565
_stake(staker1);
6666

6767
skip(22 days);
68-
assertEq(uint(deal.state()), uint256(DealNFT.State.Closed));
68+
assertEq(uint(deal.state()), uint256(DealNFT.State.Cancelled));
6969

7070
assertEq(escrowToken.balanceOf(address(deal.getTokenBoundAccount(0))), amount);
7171
assertEq(escrowToken.balanceOf(staker1), 0);
@@ -76,4 +76,8 @@ contract DealNFTRecoverTest is Test, DealSetup {
7676
assertEq(escrowToken.balanceOf(address(deal.getTokenBoundAccount(0))), 0);
7777
assertEq(escrowToken.balanceOf(staker1), amount);
7878
}
79+
80+
function test_RecoverAfterClosed() public {
81+
// TODO
82+
}
7983
}

test/DealNFTUnstake.t.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ contract DealNFTUnstakeTest is Test, DealSetup {
6868
deal.unstake(0);
6969
}
7070

71-
function test_RevertWhen_UnstakeAfterClosed() public {
71+
function test_RevertWhen_UnstakeAfterExpired() public {
7272
_stake(staker1);
7373
_stake(staker2);
7474
assertEq(deal.totalStaked(), amount * 2);
@@ -78,11 +78,15 @@ contract DealNFTUnstakeTest is Test, DealSetup {
7878

7979
skip(22 days);
8080
assertEq(deal.totalStaked(), amount);
81-
assertEq(uint(deal.state()), uint256(DealNFT.State.Closed));
81+
assertEq(uint(deal.state()), uint256(DealNFT.State.Cancelled));
8282

8383
vm.expectRevert(DealNFT.CannotUnstake.selector);
8484

8585
vm.prank(staker2);
8686
deal.unstake(1);
8787
}
88+
89+
function test_RevertWhen_UnstakeAfterClosed() public {
90+
// TODO
91+
}
8892
}

test/DealSetup.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ contract DealSetup is Test {
6767
deliveryType: 0,
6868
active: false,
6969
cancelled: false,
70-
transferable: false
70+
transferable: false,
71+
timeBasedClosing: true
7172
});
7273

7374
deal = new DealNFT(
@@ -98,7 +99,7 @@ contract DealSetup is Test {
9899

99100
function _setup() internal {
100101
vm.prank(sponsor);
101-
deal.setup(address(escrowToken), 30 minutes, 50000, "https://social", "https://website", "https://image", "", 1);
102+
deal.setup(address(escrowToken), 30 minutes, 50000, 0, 0, "https://social", "https://website", "https://image", "", 1);
102103
}
103104

104105
function _configure() internal {

0 commit comments

Comments
 (0)