@@ -113,7 +113,9 @@ abstract contract DealsRegistry is
113
113
* @param status Current deal status
114
114
*/
115
115
struct Deal {
116
+ uint256 created;
116
117
Offer offer;
118
+ bytes32 retailerId;
117
119
address buyer;
118
120
uint256 price;
119
121
address asset;
@@ -138,6 +140,9 @@ abstract contract DealsRegistry is
138
140
address indexed sender
139
141
);
140
142
143
+ /// @dev Thrown when a user attempts to provide an invalid config property value
144
+ error InvalidConfig ();
145
+
141
146
/// @dev Thrown when a user attempts to create a deal using an offer with an invalid signature
142
147
error InvalidOfferSignature ();
143
148
@@ -162,9 +167,15 @@ abstract contract DealsRegistry is
162
167
/// @dev Thrown when the supplier of the offer is not found
163
168
error InvalidSupplier ();
164
169
170
+ /// @dev Thrown when the retailer of the offer is not found
171
+ error InvalidRetailer ();
172
+
165
173
/// @dev Thrown when the supplier of the offer is not enabled
166
174
error DisabledSupplier ();
167
175
176
+ /// @dev Thrown when the retailer is not enabled
177
+ error DisabledRetailer ();
178
+
168
179
/// @dev Thrown when a function call is not allowed for current user
169
180
error NotAllowedAuth ();
170
181
@@ -177,21 +188,50 @@ abstract contract DealsRegistry is
177
188
/// @dev Thrown when a user attempts to cancel the deal using invalid cancellation options
178
189
error InvalidCancelOptions ();
179
190
191
+ /// @dev Thrown when percents value greater than 100
192
+ error InvalidPercent ();
193
+
180
194
/**
181
195
* @dev DealsRegistry constructor
182
- * @param name EIP712 contract name
183
- * @param version EIP712 contract version
196
+ * @param _name The name of the contract
197
+ * @param _version The version of the contract
198
+ * @param _claimPeriod The default time period, in seconds, allowed for the supplier to claim the deal.
199
+ * @param _protocolFee Protocol's fee in percents
200
+ * @param _retailerFee Retailer's fee in percents
201
+ * @param _feeRecipient he recipient of the protocol fee
202
+ * @param _asset The address of the asset
203
+ * @param _minDeposit The minimum deposit required for the contract
184
204
*/
185
205
constructor (
186
- string memory name ,
187
- string memory version ,
188
- address asset ,
189
- uint256 minDeposit
190
- ) EIP712 (name, version) SuppliersRegistry (asset, minDeposit) {
206
+ string memory _name ,
207
+ string memory _version ,
208
+ uint256 _claimPeriod ,
209
+ uint256 _protocolFee ,
210
+ uint256 _retailerFee ,
211
+ address _feeRecipient ,
212
+ address _asset ,
213
+ uint256 _minDeposit
214
+ ) EIP712 (_name, _version) SuppliersRegistry (_asset, _minDeposit) {
191
215
// The default time period, in seconds, allowed for the supplier to claim the deal.
192
216
// The buyer is not able to cancel the deal during this period
193
- config ("claim_period " , 60 );
217
+ config ("claim_period " , _claimPeriod);
218
+
219
+ // The recipient of the protocol fee
220
+ config ("fee_recipient " , _feeRecipient);
194
221
222
+ // In total, all the fees must not be greater than 100.
223
+ // Of course, having 100% of the fees is absurd case.
224
+ if (_protocolFee.add (_retailerFee) > 100 ) {
225
+ revert InvalidConfig ();
226
+ }
227
+
228
+ // Protocol's fee in percents
229
+ config ("protocol_fee " , _protocolFee);
230
+
231
+ // Retailer's fee in percents
232
+ config ("retailer_fee " , _retailerFee);
233
+
234
+ // Allowed statuses for functions execution
195
235
allowedStatuses["reject " ] = [DealStatus.Created];
196
236
allowedStatuses["cancel " ] = [DealStatus.Created, DealStatus.Claimed];
197
237
allowedStatuses["refund " ] = [DealStatus.Claimed, DealStatus.CheckedIn];
@@ -340,6 +380,17 @@ abstract contract DealsRegistry is
340
380
return keccak256 (abi.encode (CHECK_IN_TYPE_HASH, _id, _signer));
341
381
}
342
382
383
+ /// @dev Calculates percentage value
384
+ function _percentage (
385
+ uint256 value ,
386
+ uint256 percent
387
+ ) internal pure returns (uint256 ) {
388
+ if (percent > 100 ) {
389
+ revert InvalidPercent ();
390
+ }
391
+ return value.mul (1000 ).mul (percent).div (100 ).div (1000 );
392
+ }
393
+
343
394
/// Workflow hooks
344
395
345
396
/**
@@ -481,6 +532,7 @@ abstract contract DealsRegistry is
481
532
* @param offer An offer payload
482
533
* @param paymentOptions Raw offered payment options array
483
534
* @param paymentId Payment option Id
535
+ * @param retailerId Retailer Id
484
536
* @param signs Signatures: [0] - offer: ECDSA/ERC1271; [1] - asset permit: ECDSA (optional)
485
537
*
486
538
* Requirements:
@@ -499,6 +551,7 @@ abstract contract DealsRegistry is
499
551
Offer memory offer ,
500
552
PaymentOption[] memory paymentOptions ,
501
553
bytes32 paymentId ,
554
+ bytes32 retailerId ,
502
555
bytes [] memory signs
503
556
) external {
504
557
address buyer = _msgSender ();
@@ -521,10 +574,25 @@ abstract contract DealsRegistry is
521
574
522
575
// Not-enabled suppliers are not allowed to accept deals
523
576
// So, we cannot allow to create such a deal
524
- if (! supplier.enabled) {
577
+ if (! supplier.enabled || deposits[offer.supplierId] == 0 ) {
525
578
revert DisabledSupplier ();
526
579
}
527
580
581
+ // The retailer is optional, so we validate its rules only if retailerId is defined
582
+ if (retailerId != bytes32 (0 )) {
583
+ Supplier storage retailer = suppliers[retailerId];
584
+
585
+ // Retailer must be registered
586
+ if (retailer.owner == address (0 )) {
587
+ revert InvalidRetailer ();
588
+ }
589
+
590
+ // Not-enabled retailer are not allowed
591
+ if (! retailer.enabled || deposits[retailerId] == 0 ) {
592
+ revert DisabledRetailer ();
593
+ }
594
+ }
595
+
528
596
// Deal can be created only once
529
597
if (deals[offer.id].offer.id == offer.id) {
530
598
revert DealExists ();
@@ -563,7 +631,15 @@ abstract contract DealsRegistry is
563
631
}
564
632
565
633
// Creating the deal before any external call to avoid reentrancy
566
- deals[offer.id] = Deal (offer, buyer, price, asset, DealStatus.Created);
634
+ deals[offer.id] = Deal (
635
+ block .timestamp ,
636
+ offer,
637
+ retailerId,
638
+ buyer,
639
+ price,
640
+ asset,
641
+ DealStatus.Created
642
+ );
567
643
568
644
if (signs.length > 1 ) {
569
645
// Use permit function to transfer tokens from the sender to the contract
@@ -684,6 +760,12 @@ abstract contract DealsRegistry is
684
760
revert NotAllowedAuth ();
685
761
}
686
762
763
+ // Buyer is not able to cancel the deal during `claim_period`
764
+ // This time is given to the supplier to claim the deal
765
+ if (block .timestamp < storedDeal.created.add (numbers["claim_period " ])) {
766
+ revert NotAllowedTime ();
767
+ }
768
+
687
769
DealStatus callStatus = storedDeal.status;
688
770
689
771
// Moving to the Cancelled status before all to avoid reentrancy
@@ -724,12 +806,7 @@ abstract contract DealsRegistry is
724
806
selectedPenalty = 100 ;
725
807
}
726
808
727
- uint256 penaltyValue = storedDeal
728
- .price
729
- .mul (1000 )
730
- .mul (selectedPenalty)
731
- .div (100 )
732
- .div (1000 );
809
+ uint256 penaltyValue = _percentage (storedDeal.price, selectedPenalty);
733
810
734
811
if (
735
812
! IERC20 (storedDeal.asset).transfer (
@@ -902,10 +979,45 @@ abstract contract DealsRegistry is
902
979
// Execute before checkOut hook
903
980
_beforeCheckOut (offerId);
904
981
982
+ uint256 protocolFee;
983
+ uint256 retailerFee;
984
+ uint256 supplierValue;
985
+
986
+ protocolFee = _percentage (storedDeal.price, numbers["protocol_fee " ]);
987
+
988
+ if (storedDeal.retailerId != bytes32 (0 )) {
989
+ retailerFee = _percentage (storedDeal.price, numbers["retailer_fee " ]);
990
+ }
991
+
992
+ supplierValue = storedDeal.price.sub (protocolFee).sub (retailerFee);
993
+
994
+ if (
995
+ protocolFee > 0 &&
996
+ // Sends fee to the protocol recipient
997
+ ! IERC20 (storedDeal.asset).transfer (
998
+ addresses["fee_recipient " ],
999
+ protocolFee
1000
+ )
1001
+ ) {
1002
+ revert DealFundsTransferFailed ();
1003
+ }
1004
+
1005
+ if (
1006
+ retailerFee > 0 &&
1007
+ // Send fee to the deal retailer
1008
+ ! IERC20 (storedDeal.asset).transfer (
1009
+ suppliers[storedDeal.retailerId].owner,
1010
+ retailerFee
1011
+ )
1012
+ ) {
1013
+ revert DealFundsTransferFailed ();
1014
+ }
1015
+
905
1016
if (
1017
+ // Sends value to the supplier
906
1018
! IERC20 (storedDeal.asset).transfer (
907
1019
suppliers[storedDeal.offer.supplierId].owner,
908
- storedDeal.price
1020
+ supplierValue
909
1021
)
910
1022
) {
911
1023
revert DealFundsTransferFailed ();
0 commit comments