Skip to content

Commit cf13f16

Browse files
committed
feat: 🎸 Implemented retailer flow
1 parent bff822f commit cf13f16

File tree

3 files changed

+160
-30
lines changed

3 files changed

+160
-30
lines changed

contracts/DealsRegistry.sol

+129-17
Original file line numberDiff line numberDiff line change
@@ -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();

contracts/Market.sol

+29-12
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,40 @@ contract Market is Ownable, Pausable, DealsRegistry, ERC721Token {
2323

2424
/**
2525
* @dev Constructor that initializes the Market contract with the given arguments
26-
* @param owner The owner of the contract
27-
* @param name The name of the contract
28-
* @param version The version of the contract
29-
* @param asset The address of the asset
30-
* @param minDeposit The minimum deposit required for the contract
26+
* @param _owner The owner of the contract
27+
* @param _name The name of the contract
28+
* @param _version The version of the contract
29+
* @param _claimPeriod The default time period, in seconds, allowed for the supplier to claim the deal.
30+
* @param _protocolFee Protocol's fee in percents
31+
* @param _retailerFee Retailer's fee in percents
32+
* @param _feeRecipient he recipient of the protocol fee
33+
* @param _asset The address of the asset
34+
* @param _minDeposit The minimum deposit required for the contract
3135
*/
3236
constructor(
33-
address owner,
34-
string memory name,
35-
string memory version,
36-
address asset,
37-
uint256 minDeposit
37+
address _owner,
38+
string memory _name,
39+
string memory _version,
40+
uint256 _claimPeriod,
41+
uint256 _protocolFee,
42+
uint256 _retailerFee,
43+
address _feeRecipient,
44+
address _asset,
45+
uint256 _minDeposit
3846
)
39-
DealsRegistry(name, version, asset, minDeposit)
47+
DealsRegistry(
48+
_name,
49+
_version,
50+
_claimPeriod,
51+
_protocolFee,
52+
_retailerFee,
53+
_feeRecipient,
54+
_asset,
55+
_minDeposit
56+
)
4057
ERC721Token("DealToken", "DEAL")
4158
{
42-
transferOwnership(owner);
59+
transferOwnership(_owner);
4360
}
4461

4562
/// Getters

contracts/SuppliersRegistry.sol

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ abstract contract SuppliersRegistry is Context, Configurable, Pausable {
8585
constructor(address _asset, uint256 _minDeposit) {
8686
// Supplier's deposit asset
8787
config("asset", _asset);
88+
8889
// Minimum deposit value
8990
config("min_deposit", _minDeposit);
9091
}
@@ -104,7 +105,7 @@ abstract contract SuppliersRegistry is Context, Configurable, Pausable {
104105
* @param id The supplier Id
105106
*/
106107
function isSupplierEnabled(bytes32 id) external view returns (bool) {
107-
return suppliers[id].enabled;
108+
return suppliers[id].enabled && deposits[id] > 0;
108109
}
109110

110111
/// Features

0 commit comments

Comments
 (0)