Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .ghost/events.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
interface Events {
event Create(address indexed deal, address indexed sponsor, address arbitrator, address escrowToken, string name, string symbol, string image, string description);

event Init(string social, string website, uint256 multiple, uint256 closingDelay, uint256 unstakingFee, uint256 closingTime, uint256 dealMinimum, uint256 dealMaximum, uint256 deliveryType, bool active, bool transferable);
event Setup(address escrowToken, uint256 closingDelay, uint256 unstakingFee, string website, string social, string image, string description, uint256 deliveryType);
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);
event Setup(address escrowToken, uint256 closingDelay, uint256 unstakingFee, uint256 dealMinimum, uint256 dealMaximum, string website, string social, string image, string description, uint256 deliveryType);

event Configure(string description, string social, string website, uint256 closingTime, uint256 dealMinimum, uint256 dealMaximum, uint256 multiple);
event StateUpdated(uint8 state);
event Transferable(bool transferable);
Expand All @@ -11,4 +12,7 @@ interface Events {
event Stake(address indexed staker, address tokenBoundAccount, uint256 tokenId, uint256 amount);
event Unstake(address indexed staker, address tokenBoundAccount, uint256 tokenId, uint256 amount);
event Recover(address indexed staker, address tokenBoundAccount, uint256 tokenId, uint256 amount);

event SetStakersWhitelist(address whitelist);
event SetClaimsWhitelist(address whitelist);
}
28 changes: 22 additions & 6 deletions .ghost/indexer.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

Expand All @@ -9,6 +8,7 @@ import "./gen_helpers.sol";

interface IDeal {
function totalStaked() external view returns (uint256);
function state() external view returns (uint8);
}

interface IERC20 {
Expand All @@ -23,8 +23,8 @@ contract MyIndex is GhostGraph {
using StringHelpers for address;

function registerHandles() external {
graph.registerFactory(0xA831B98cD0190bC973cAfd0551BfCfed0Ff3f510, GhostEventName.Create, "deal");
graph.registerHandle(0xA831B98cD0190bC973cAfd0551BfCfed0Ff3f510);
graph.registerFactory(0xB59392C43F454D505CB2ead541a5BFeF3858b1E7, GhostEventName.Create, "deal");
graph.registerHandle(0xB59392C43F454D505CB2ead541a5BFeF3858b1E7);
}

function onCreate(EventDetails memory details, CreateEvent memory ev) external {
Expand Down Expand Up @@ -61,8 +61,9 @@ contract MyIndex is GhostGraph {
deal.dealMaximum = ev.dealMaximum;
deal.deliveryType = ev.deliveryType;
deal.transferable = ev.transferable;
deal.timeBasedClosing = ev.timeBasedClosing;

if(ev.active){
if (ev.active) {
deal.state = 1;
}

Expand All @@ -83,6 +84,8 @@ contract MyIndex is GhostGraph {
deal.image = ev.image;
deal.description = ev.description;
deal.deliveryType = ev.deliveryType;
deal.dealMinimum = ev.dealMinimum;
deal.dealMaximum = ev.dealMaximum;

graph.saveDeal(deal);
}
Expand Down Expand Up @@ -117,14 +120,15 @@ contract MyIndex is GhostGraph {
Deal memory deal = graph.getDeal(details.emitter);
deal.state = ev.state;
graph.saveDeal(deal);
}
}

function onStake(EventDetails memory details, StakeEvent memory ev) external {
// DEAL
Deal memory deal = graph.getDeal(details.emitter);
deal.totalStaked = IDeal(details.emitter).totalStaked();
deal.lastStakeTime = details.timestamp;
deal.lastStakeTxHash = details.transactionHash;
deal.state = IDeal(details.emitter).state();
graph.saveDeal(deal);

// STAKE
Expand Down Expand Up @@ -167,8 +171,20 @@ contract MyIndex is GhostGraph {
graph.saveStake(stake);
}

function onSetStakersWhitelist(EventDetails memory details, SetStakersWhitelistEvent memory ev) external {
Deal memory deal = graph.getDeal(details.emitter);
deal.stakersWhitelist = ev.whitelist;
graph.saveDeal(deal);
}

function onSetClaimsWhitelist(EventDetails memory details, SetClaimsWhitelistEvent memory ev) external {
Deal memory deal = graph.getDeal(details.emitter);
deal.claimsWhitelist = ev.whitelist;
graph.saveDeal(deal);
}

// HELPER
function getStakeId(address deal, uint256 token) private returns(string memory){
function getStakeId(address deal, uint256 token) private returns (string memory) {
return string(abi.encodePacked(deal.toString(), ":", token.toString()));
}
}
3 changes: 3 additions & 0 deletions .ghost/schema.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ struct Deal {
address sponsor;
address arbitrator;
address escrowToken;
address stakersWhitelist;
address claimsWhitelist;
string escrowSymbol;
uint8 escrowDecimals;
string name;
Expand All @@ -21,6 +23,7 @@ struct Deal {
uint256 dealMaximum;
uint256 deliveryType;
bool transferable;
bool timeBasedClosing;

// calculated
uint8 state;
Expand Down
92 changes: 64 additions & 28 deletions src/DealNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
error OwnerMismatch();

// Events
event Init(string social, string website, uint256 multiple, uint256 closingDelay, uint256 unstakingFee, uint256 closingTime, uint256 dealMinimum, uint256 dealMaximum, uint256 deliveryType, bool active, bool transferable);
event Setup(address escrowToken, uint256 closingDelay, uint256 unstakingFee, string website, string social, string image, string description, uint256 deliveryType);
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);
event Setup(address escrowToken, uint256 closingDelay, uint256 unstakingFee, uint256 dealMinimum, uint256 dealMaximum, string website, string social, string image, string description, uint256 deliveryType);
event Configure(string description, string social, string website, uint256 closingTime, uint256 dealMinimum, uint256 dealMaximum, uint256 multiple);
event StateUpdated(State state);
event Transferable(bool transferable);
Expand Down Expand Up @@ -81,6 +81,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
IWhitelist public stakersWhitelist;
IWhitelist public claimsWhitelist;

uint256 lastStakeTimestamp;
mapping(uint256 tokenId => uint256) public stakedAmount;
mapping(uint256 tokenId => uint256) public claimedAmount;
mapping(address staker => uint256) public stakes;
Expand Down Expand Up @@ -110,6 +111,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
bool active;
bool cancelled;
bool transferable;
bool timeBasedClosing;
}

Configuration private config;
Expand Down Expand Up @@ -142,7 +144,9 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
if(bytes(symbol_).length == 0) revert ZeroDetected();

if(config_.sponsor == ADDRESS_ZERO) revert ZeroDetected();
_validClosingTime(config_.closingTime, config_.closingDelay);
if(config_.timeBasedClosing) {
_validClosingTime(config_.closingTime, config_.closingDelay);
}
if(config_.dealMinimum > config_.dealMaximum) revert BadStakesRange();
if(config_.multiple < 1e18) revert ZeroDetected();

Expand All @@ -168,7 +172,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
config_.dealMaximum,
config_.deliveryType,
config_.active,
config_.transferable
config_.transferable,
config_.timeBasedClosing
);
}

Expand Down Expand Up @@ -207,24 +212,29 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
address escrowToken_,
uint256 closingDelay_,
uint256 unstakingFee_,
uint256 dealMinimum_,
uint256 dealMaximum_,
string memory social_,
string memory website_,
string memory image_,
string memory description_,
uint256 deliveryType_
) external onlySponsor {
if(state() != State.Setup) revert CannotSetup();
if(dealMinimum_ > dealMaximum_) revert BadStakesRange();

config.escrowToken = escrowToken_;
config.closingDelay = closingDelay_;
config.unstakingFee = unstakingFee_;
config.dealMinimum = dealMinimum_;
config.dealMaximum = dealMaximum_;
config.social = social_;
config.website = website_;
config.image = image_;
config.description = description_;
config.deliveryType = deliveryType_;

emit Setup(escrowToken_, closingDelay_, unstakingFee_, website_, social_, image_, description_, deliveryType_);
emit Setup(escrowToken_, closingDelay_, unstakingFee_, dealMaximum_, dealMinimum_, website_, social_, image_, description_, deliveryType_);
}

/**
Expand Down Expand Up @@ -255,17 +265,22 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
uint256 dealMaximum_,
uint256 multiple_
) external onlySponsor {
_canConfigure();
_validClosingTime(closingTime_, config.closingDelay);
if(dealMinimum_ > dealMaximum_) revert BadStakesRange();
_canConfigure();
if(multiple_ < 1e18) revert ZeroDetected();

if(config.timeBasedClosing) {
_validClosingTime(closingTime_, config.closingDelay);
if(dealMinimum_ > dealMaximum_) revert BadStakesRange();

config.dealMinimum = dealMinimum_;
config.dealMaximum = dealMaximum_;
config.closingTime = closingTime_;
}

config.description = description_;
config.social = social_;
config.website = website_;
config.closingTime = closingTime_;
config.dealMinimum = dealMinimum_;
config.dealMaximum = dealMaximum_;

config.multiple = multiple_;

emit Configure(description_, social_, website_, closingTime_, dealMinimum_, dealMaximum_, multiple_);
Expand Down Expand Up @@ -301,9 +316,6 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
* @param transferable_ Boolean indicating if NFTs are transferable
*/
function setTransferable(bool transferable_) external onlyArbitrator {
if(state() == State.Cancelled) revert CannotConfigure();
if(_afterClosed()) revert CannotConfigure();

config.transferable = transferable_;
emit Transferable(transferable_);
}
Expand Down Expand Up @@ -374,7 +386,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
if(state() < State.Claiming) revert CannotRecover();

if(state() == State.Claiming) {
if(_totalStaked(_tokenId) >= config.dealMinimum) revert MinimumReached();
if(_minimumReached()) revert MinimumReached();
}

AccountV3TBD tokenBoundAccount = getTokenBoundAccount(tokenId);
Expand Down Expand Up @@ -449,17 +461,31 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
*/
function state() public view returns (State) {
if(config.cancelled) return State.Cancelled;
if(_isClaimed()) return State.Closed;

if(_beforeClose()) {
if(config.active) return State.Active;
return State.Setup;
}
if(config.timeBasedClosing){
if(_afterClosed(config.closingTime)) {
return State.Cancelled;
}

if(_beforeClose()) {
if(config.active) return State.Active;
return State.Setup;
}

if(_afterClosed()) return State.Closed;
return State.Claiming;
} else {
if(_minimumReached()) {
if(_afterClosed(lastStakeTimestamp)){
return State.Cancelled;
}

if(_isClaimed()) return State.Closed;
return State.Claiming;
}

return State.Claiming;
if(config.active) return State.Active;
return State.Setup;
}
}

/**
Expand Down Expand Up @@ -557,6 +583,13 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
return config;
}

/**
* @notice Check if the minimum has been reached
*/
function _minimumReached() private view returns (bool) {
return config.dealMinimum > 0 && _totalStaked(_tokenId) >= config.dealMinimum;
}

/**
* @notice Check if all tokens have been claimed by the sponsor
*/
Expand All @@ -574,8 +607,8 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
/**
* @notice Check if the current time is after closing time
*/
function _afterClosed() private view returns (bool) {
return config.closingTime > 0 && block.timestamp > (config.closingTime + CLAIMING_PERIOD);
function _afterClosed(uint256 since) private view returns (bool) {
return since > 0 && block.timestamp > (since + CLAIMING_PERIOD);
}

/**
Expand Down Expand Up @@ -625,7 +658,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
function _canClaim() internal view {
if(_claimId == _tokenId) revert TokenOutOfBounds();
if(state() != State.Claiming) revert NotInClaimingState();
if(_totalStaked(_tokenId) < config.dealMinimum) revert MinimumNotReached();
if(!_minimumReached()) revert MinimumNotReached();
}

/**
Expand All @@ -634,14 +667,16 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
function _canConfigure() internal view {
if(state() >= State.Closed) revert CannotConfigure();
if(state() == State.Claiming) {
if(_totalStaked(_tokenId) >= config.dealMinimum) revert MinimumReached();
if(_minimumReached()) revert MinimumReached();
}
}

function _validateActivation() internal view {
if(config.escrowToken == ADDRESS_ZERO) revert ZeroDetected();
if(config.closingDelay <= 0) revert ZeroDetected();
if(config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig();
if(config.timeBasedClosing) {
if(config.closingDelay <= 0) revert ZeroDetected();
if(config.closingDelay > MAX_CLOSING_RANGE) revert ClosingDelayTooBig();
}
if(config.unstakingFee > MAX_FEE) revert ClosingFeeTooBig();
if(bytes(config.website).length == 0) revert ZeroDetected();
if(bytes(config.social).length == 0) revert ZeroDetected();
Expand Down Expand Up @@ -670,6 +705,7 @@ contract DealNFT is ERC721, IDealNFT, ReentrancyGuard {
uint256 newTokenId = _tokenId++;
stakedAmount[newTokenId] = amount;
stakes[staker] = currentStake;
lastStakeTimestamp = block.timestamp;

_safeMint(staker, newTokenId);

Expand Down
1 change: 1 addition & 0 deletions src/Reader.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ contract Reader {
dealMaximum: config.dealMaximum,
deliveryType: config.deliveryType,
transferable: config.transferable,
timeBasedClosing: config.timeBasedClosing,
stakersWhitelist: dealInstance.stakersWhitelist(),
claimsWhitelist: dealInstance.claimsWhitelist(),
deliveryToken: address(dealInstance.deliveryToken()),
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IDeal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface IDeal {
uint8 escrowDecimals;
StakeData[] claimed;
bool transferable;
bool timeBasedClosing;
uint256 deliveryType;
}

Expand Down
Loading