@@ -113,7 +113,9 @@ abstract contract DealsRegistry is
113113 * @param status Current deal status
114114 */
115115 struct Deal {
116+ uint256 created;
116117 Offer offer;
118+ bytes32 retailerId;
117119 address buyer;
118120 uint256 price;
119121 address asset;
@@ -138,6 +140,9 @@ abstract contract DealsRegistry is
138140 address indexed sender
139141 );
140142
143+ /// @dev Thrown when a user attempts to provide an invalid config property value
144+ error InvalidConfig ();
145+
141146 /// @dev Thrown when a user attempts to create a deal using an offer with an invalid signature
142147 error InvalidOfferSignature ();
143148
@@ -162,9 +167,15 @@ abstract contract DealsRegistry is
162167 /// @dev Thrown when the supplier of the offer is not found
163168 error InvalidSupplier ();
164169
170+ /// @dev Thrown when the retailer of the offer is not found
171+ error InvalidRetailer ();
172+
165173 /// @dev Thrown when the supplier of the offer is not enabled
166174 error DisabledSupplier ();
167175
176+ /// @dev Thrown when the retailer is not enabled
177+ error DisabledRetailer ();
178+
168179 /// @dev Thrown when a function call is not allowed for current user
169180 error NotAllowedAuth ();
170181
@@ -177,21 +188,50 @@ abstract contract DealsRegistry is
177188 /// @dev Thrown when a user attempts to cancel the deal using invalid cancellation options
178189 error InvalidCancelOptions ();
179190
191+ /// @dev Thrown when percents value greater than 100
192+ error InvalidPercent ();
193+
180194 /**
181195 * @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
184204 */
185205 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) {
191215 // The default time period, in seconds, allowed for the supplier to claim the deal.
192216 // 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);
194221
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
195235 allowedStatuses["reject " ] = [DealStatus.Created];
196236 allowedStatuses["cancel " ] = [DealStatus.Created, DealStatus.Claimed];
197237 allowedStatuses["refund " ] = [DealStatus.Claimed, DealStatus.CheckedIn];
@@ -340,6 +380,17 @@ abstract contract DealsRegistry is
340380 return keccak256 (abi.encode (CHECK_IN_TYPE_HASH, _id, _signer));
341381 }
342382
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+
343394 /// Workflow hooks
344395
345396 /**
@@ -481,6 +532,7 @@ abstract contract DealsRegistry is
481532 * @param offer An offer payload
482533 * @param paymentOptions Raw offered payment options array
483534 * @param paymentId Payment option Id
535+ * @param retailerId Retailer Id
484536 * @param signs Signatures: [0] - offer: ECDSA/ERC1271; [1] - asset permit: ECDSA (optional)
485537 *
486538 * Requirements:
@@ -499,6 +551,7 @@ abstract contract DealsRegistry is
499551 Offer memory offer ,
500552 PaymentOption[] memory paymentOptions ,
501553 bytes32 paymentId ,
554+ bytes32 retailerId ,
502555 bytes [] memory signs
503556 ) external {
504557 address buyer = _msgSender ();
@@ -521,10 +574,25 @@ abstract contract DealsRegistry is
521574
522575 // Not-enabled suppliers are not allowed to accept deals
523576 // So, we cannot allow to create such a deal
524- if (! supplier.enabled) {
577+ if (! supplier.enabled || deposits[offer.supplierId] == 0 ) {
525578 revert DisabledSupplier ();
526579 }
527580
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+
528596 // Deal can be created only once
529597 if (deals[offer.id].offer.id == offer.id) {
530598 revert DealExists ();
@@ -563,7 +631,15 @@ abstract contract DealsRegistry is
563631 }
564632
565633 // 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+ );
567643
568644 if (signs.length > 1 ) {
569645 // Use permit function to transfer tokens from the sender to the contract
@@ -684,6 +760,12 @@ abstract contract DealsRegistry is
684760 revert NotAllowedAuth ();
685761 }
686762
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+
687769 DealStatus callStatus = storedDeal.status;
688770
689771 // Moving to the Cancelled status before all to avoid reentrancy
@@ -724,12 +806,7 @@ abstract contract DealsRegistry is
724806 selectedPenalty = 100 ;
725807 }
726808
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);
733810
734811 if (
735812 ! IERC20 (storedDeal.asset).transfer (
@@ -902,10 +979,45 @@ abstract contract DealsRegistry is
902979 // Execute before checkOut hook
903980 _beforeCheckOut (offerId);
904981
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+
9051016 if (
1017+ // Sends value to the supplier
9061018 ! IERC20 (storedDeal.asset).transfer (
9071019 suppliers[storedDeal.offer.supplierId].owner,
908- storedDeal.price
1020+ supplierValue
9091021 )
9101022 ) {
9111023 revert DealFundsTransferFailed ();
0 commit comments