Skip to content

Commit 19bdcf0

Browse files
committed
feat: 🎸 Added refund and checkOut functions
1 parent cbb1a5d commit 19bdcf0

File tree

2 files changed

+135
-18
lines changed

2 files changed

+135
-18
lines changed

contracts/DealsRegistry.sol

Lines changed: 129 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ abstract contract DealsRegistry is
7575
* @param cancelHash The hash of the cancellation option used for the deal
7676
* @param transferable Indicates whether the deal NFT is transferable or not
7777
* @param checkIn The check-in time for the deal (in seconds since the Unix epoch)
78+
* @param checkOut The check-out time for the deal (in seconds since the Unix epoch)
7879
*/
7980
struct Offer {
8081
bytes32 id;
@@ -87,6 +88,7 @@ abstract contract DealsRegistry is
8788
bytes32 cancelHash;
8889
bool transferable;
8990
uint256 checkIn;
91+
uint256 checkOut;
9092
}
9193

9294
/**
@@ -169,6 +171,9 @@ abstract contract DealsRegistry is
169171
/// @dev Thrown when a user attempts to claim the deal in non-created status
170172
error NotAllowedStatus();
171173

174+
/// @dev Thrown when a user attempts to do something that not allowed at a moment
175+
error NotAllowedTime();
176+
172177
/// @dev Thrown when a user attempts to cancel the deal using invalid cancellation options
173178
error InvalidCancelOptions();
174179

@@ -193,6 +198,7 @@ abstract contract DealsRegistry is
193198
allowedStatuses["claim"] = [DealStatus.Created];
194199
allowedStatuses["checkIn"] = [DealStatus.Claimed];
195200
allowedStatuses["checkOut"] = [DealStatus.CheckedIn];
201+
allowedStatuses["dispute"] = [DealStatus.CheckedIn, DealStatus.CheckedOut];
196202
}
197203

198204
/// Modifiers
@@ -385,6 +391,34 @@ abstract contract DealsRegistry is
385391
*/
386392
function _afterReject(bytes32 offerId, bytes32 reason) internal virtual {}
387393

394+
/**
395+
* @dev Hook function that runs before the deal is canceled.
396+
* Allows inheriting smart contracts to perform custom logic.
397+
* @param offerId The offerId of the deal
398+
*/
399+
function _beforeCancel(bytes32 offerId) internal virtual whenNotPaused {}
400+
401+
/**
402+
* @dev Hook function that runs after the deal is canceled.
403+
* Allows inheriting smart contracts to perform custom logic.
404+
* @param offerId The offerId of the deal
405+
*/
406+
function _afterCancel(bytes32 offerId) internal virtual {}
407+
408+
/**
409+
* @dev Hook function that runs before the deal is refunded.
410+
* Allows inheriting smart contracts to perform custom logic.
411+
* @param offerId The offerId of the deal
412+
*/
413+
function _beforeRefund(bytes32 offerId) internal virtual whenNotPaused {}
414+
415+
/**
416+
* @dev Hook function that runs after the deal is refunded.
417+
* Allows inheriting smart contracts to perform custom logic.
418+
* @param offerId The offerId of the deal
419+
*/
420+
function _afterRefund(bytes32 offerId) internal virtual {}
421+
388422
/**
389423
* @dev Hook function that runs before the deal is claimed.
390424
* Allows inheriting smart contracts to perform custom logic.
@@ -427,18 +461,18 @@ abstract contract DealsRegistry is
427461
) internal virtual {}
428462

429463
/**
430-
* @dev Hook function that runs before the deal is canceled.
464+
* @dev Hook function that runs before the deal is checked out.
431465
* Allows inheriting smart contracts to perform custom logic.
432466
* @param offerId The offerId of the deal
433467
*/
434-
function _beforeCancel(bytes32 offerId) internal virtual whenNotPaused {}
468+
function _beforeCheckOut(bytes32 offerId) internal virtual whenNotPaused {}
435469

436470
/**
437-
* @dev Hook function that runs after the deal is canceled.
471+
* @dev Hook function that runs after the deal is checked out.
438472
* Allows inheriting smart contracts to perform custom logic.
439473
* @param offerId The offerId of the deal
440474
*/
441-
function _afterCancel(bytes32 offerId) internal virtual {}
475+
function _afterCheckOut(bytes32 offerId) internal virtual {}
442476

443477
/// Features
444478

@@ -470,6 +504,7 @@ abstract contract DealsRegistry is
470504
address buyer = _msgSender();
471505

472506
/// @dev variable scoping used to avoid stack too deep errors
507+
/// The `supplier` storage variable is required is the frame of this scope only
473508
{
474509
bytes32 offerHash = _hashTypedDataV4(hash(offer));
475510
Supplier storage supplier = suppliers[offer.supplierId];
@@ -563,15 +598,16 @@ abstract contract DealsRegistry is
563598
)
564599
external
565600
dealExists(offerId)
566-
inStatuses(offerId, allowedStatuses["reject"])
567601
onlySigner(offerId)
602+
inStatuses(offerId, allowedStatuses["reject"])
568603
{
569604
Deal storage storedDeal = deals[offerId];
570605

571-
_beforeReject(offerId, reason);
572-
606+
// Moving to the Rejected status before all to avoid reentrancy
573607
storedDeal.status = DealStatus.Rejected;
574608

609+
_beforeReject(offerId, reason);
610+
575611
if (
576612
!IERC20(storedDeal.asset).transfer(storedDeal.buyer, storedDeal.price)
577613
) {
@@ -583,6 +619,42 @@ abstract contract DealsRegistry is
583619
_afterReject(offerId, reason);
584620
}
585621

622+
/**
623+
* @dev Refunds the deal
624+
* @param offerId The deal offer Id
625+
*
626+
* Requirements:
627+
*
628+
* - the deal must exists
629+
* - the deal must be in status DealStatus.CheckedIn
630+
* - must be called by the signer address of the deal offer supplier
631+
*/
632+
function refund(
633+
bytes32 offerId
634+
)
635+
external
636+
dealExists(offerId)
637+
onlySigner(offerId)
638+
inStatuses(offerId, allowedStatuses["refund"])
639+
{
640+
Deal storage storedDeal = deals[offerId];
641+
642+
// Moving to the Refunded status before all to avoid reentrancy
643+
storedDeal.status = DealStatus.Refunded;
644+
645+
_beforeRefund(offerId);
646+
647+
if (
648+
!IERC20(storedDeal.asset).transfer(storedDeal.buyer, storedDeal.price)
649+
) {
650+
revert DealFundsTransferFailed();
651+
}
652+
653+
emit Status(offerId, DealStatus.Refunded, _msgSender());
654+
655+
_afterRefund(offerId);
656+
}
657+
586658
/**
587659
* @dev Cancels the deal
588660
* @param offerId The deal offer Id
@@ -612,22 +684,28 @@ abstract contract DealsRegistry is
612684
revert NotAllowedAuth();
613685
}
614686

615-
_beforeCancel((offerId));
687+
DealStatus callStatus = storedDeal.status;
688+
689+
// Moving to the Cancelled status before all to avoid reentrancy
690+
storedDeal.status = DealStatus.Cancelled;
616691

617-
if (storedDeal.status == DealStatus.Created) {
692+
_beforeCancel(offerId);
693+
694+
if (callStatus == DealStatus.Created) {
618695
// Full refund
619696
if (
620697
!IERC20(storedDeal.asset).transfer(storedDeal.buyer, storedDeal.price)
621698
) {
622699
revert DealFundsTransferFailed();
623700
}
624701
} else if (
625-
storedDeal.status == DealStatus.Claimed &&
702+
callStatus == DealStatus.Claimed &&
626703
block.timestamp < storedDeal.offer.checkIn
627704
) {
628705
if (storedDeal.offer.cancelHash != hash(_cancelOptions)) {
629706
revert InvalidCancelOptions();
630707
}
708+
631709
// Using offer cancellation rules
632710
uint256 selectedTime;
633711
uint256 selectedPenalty;
@@ -675,7 +753,6 @@ abstract contract DealsRegistry is
675753
revert NotAllowedStatus();
676754
}
677755

678-
storedDeal.status = DealStatus.Cancelled;
679756
emit Status(offerId, DealStatus.Cancelled, sender);
680757

681758
_afterCancel(offerId);
@@ -696,8 +773,8 @@ abstract contract DealsRegistry is
696773
)
697774
external
698775
dealExists(offerId)
699-
inStatuses(offerId, allowedStatuses["claim"])
700776
onlySigner(offerId)
777+
inStatuses(offerId, allowedStatuses["claim"])
701778
{
702779
Deal storage storedDeal = deals[offerId];
703780

@@ -794,13 +871,51 @@ abstract contract DealsRegistry is
794871
_afterCheckIn(offerId, signs);
795872
}
796873

874+
/**
875+
* @dev Checks out the deal and sends funds to the supplier
876+
* @param offerId The deal offer Id
877+
*
878+
* Requirements:
879+
*
880+
* - the deal must exists
881+
* - must be called by the supplier's signer only
882+
* - the deal must be in status DealStatus.CheckIn
883+
* - must be called after checkOut time only
884+
*/
797885
function checkOut(
798886
bytes32 offerId
799887
)
800888
external
801889
dealExists(offerId)
802-
inStatuses(offerId, allowedStatuses["checkIn"])
803-
{}
890+
onlySigner(offerId)
891+
inStatuses(offerId, allowedStatuses["checkOut"])
892+
{
893+
Deal storage storedDeal = deals[offerId];
894+
895+
if (block.timestamp < storedDeal.offer.checkOut) {
896+
revert NotAllowedTime();
897+
}
898+
899+
// Moving to CheckedOut status before all to avoid reentrancy
900+
storedDeal.status = DealStatus.CheckedOut;
901+
902+
// Execute before checkOut hook
903+
_beforeCheckOut(offerId);
904+
905+
if (
906+
!IERC20(storedDeal.asset).transfer(
907+
suppliers[storedDeal.offer.supplierId].owner,
908+
storedDeal.price
909+
)
910+
) {
911+
revert DealFundsTransferFailed();
912+
}
913+
914+
emit Status(offerId, DealStatus.CheckedOut, _msgSender());
915+
916+
// Execute after checkOut hook
917+
_afterCheckOut(offerId);
918+
}
804919

805920
uint256[50] private __gap;
806921
}

contracts/Market.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,14 @@ contract Market is Ownable, Pausable, DealsRegistry, ERC721Token {
126126
}
127127

128128
/**
129-
* @dev Executes logic before a deal is canceled
129+
* @dev Executes logic after a deal is canceled
130130
* @param offerId The ID of the offer
131131
*/
132-
function _beforeCancel(bytes32 offerId) internal override(DealsRegistry) {
133-
if (deals[offerId].status != DealStatus.Created) {
134-
uint256 tokenId = offerTokens[offerId];
132+
function _afterCancel(bytes32 offerId) internal override(DealsRegistry) {
133+
uint256 tokenId = offerTokens[offerId];
134+
135+
// If token has been minted we must burn it
136+
if (tokenId != 0) {
135137
safeBurn(tokenId);
136138
delete tokenOffers[tokenId];
137139
delete offerTokens[offerId];

0 commit comments

Comments
 (0)