1
1
// SPDX-License-Identifier: MIT
2
2
pragma solidity ^ 0.8.19 ;
3
3
4
- import "@openzeppelin/contracts/utils/Context.sol " ;
5
4
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol " ;
5
+ import "@openzeppelin/contracts/security/Pausable.sol " ;
6
6
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol " ;
7
+ import "./Configurable.sol " ;
7
8
import "./SuppliersRegistry.sol " ;
8
9
import "./utils/IERC20.sol " ;
9
10
import "./utils/SignatureUtils.sol " ;
@@ -14,7 +15,12 @@ import "./utils/SignatureUtils.sol";
14
15
* The contract stores offers made by suppliers, and allows buyers to create deals based on those offers.
15
16
* Each deal specifies the payment and cancellation terms, and can be tracked on-chain using its unique Id.
16
17
*/
17
- abstract contract DealsRegistry is Context , EIP712 , SuppliersRegistry {
18
+ abstract contract DealsRegistry is
19
+ Configurable ,
20
+ Pausable ,
21
+ SuppliersRegistry ,
22
+ EIP712
23
+ {
18
24
using SignatureChecker for address ;
19
25
using SignatureUtils for bytes ;
20
26
@@ -110,16 +116,29 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
110
116
/**
111
117
* @dev Emitted when a Deal is created by a buyer
112
118
* @param offerId The Id of the offer used to create the deal
113
- * @param buyer The address of the buyer who created the deal
119
+ * @param buyer The address of the buyer who is created the deal
114
120
*/
115
121
event DealCreated (bytes32 indexed offerId , address indexed buyer );
116
122
123
+ /**
124
+ * @dev Emitted when a Deal is claimed by a supplier's signer
125
+ * @param offerId The Id of the offer used to create the deal
126
+ * @param signer The address of the supplier's signer who is claimed the deal
127
+ */
128
+ event DealClaimed (bytes32 indexed offerId , address indexed signer );
129
+
117
130
/// @dev Thrown when a user attempts to create a deal using an offer with an invalid signature
118
131
error InvalidOfferSignature ();
119
132
120
133
/// @dev Thrown when a user attempts to create an already existing Deal
121
134
error DealExists ();
122
135
136
+ /// @dev Thrown when Deal was created in the `_beforeDealCreated` hook
137
+ error DealAlreadyCreated ();
138
+
139
+ /// @dev Thrown when the Deal is not found
140
+ error DealNotFound ();
141
+
123
142
/// @dev Thrown when a user attempts to create a deal providing an invalid payment options
124
143
error InvalidPaymentOptions ();
125
144
@@ -135,6 +154,15 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
135
154
/// @dev Thrown when the supplier of the offer is not enabled
136
155
error DisabledSupplier ();
137
156
157
+ /// @dev Thrown when a function call is not allowed
158
+ error NotAllowed ();
159
+
160
+ /// @dev Thrown when a user attempts to claim the deal in non-created status
161
+ error DealNotCreated ();
162
+
163
+ /// @dev Thrown when a user attempts to claim already claimed deal
164
+ error DealAlreadyClaimed ();
165
+
138
166
/**
139
167
* @dev DealsRegistry constructor
140
168
* @param name EIP712 contract name
@@ -145,7 +173,13 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
145
173
string memory version ,
146
174
address asset ,
147
175
uint256 minDeposit
148
- ) EIP712 (name, version) SuppliersRegistry (asset, minDeposit) {}
176
+ ) EIP712 (name, version) SuppliersRegistry (asset, minDeposit) {
177
+ // The default time period, in seconds, allowed for the supplier to claim the deal.
178
+ // The buyer is not able to cancel the deal during this period
179
+ config ("claim_period " , 60 );
180
+ }
181
+
182
+ /// Utilities
149
183
150
184
/// @dev Create a has of bytes32 array
151
185
function hash (bytes32 [] memory hashes ) internal pure returns (bytes32 ) {
@@ -221,21 +255,82 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
221
255
);
222
256
}
223
257
258
+ /// Workflow hooks
259
+
260
+ /**
261
+ * @dev Hook function that runs before a new deal is created.
262
+ * Allows inheriting smart contracts to perform custom logic.
263
+ * @param offer The offer used to create the deal
264
+ * @param price The price of the asset in wei
265
+ * @param asset The address of the ERC20 token used for payment
266
+ * @param signs An array of signatures authorizing the creation of the deal
267
+ */
268
+ function _beforeDealCreated (
269
+ Offer memory offer ,
270
+ uint256 price ,
271
+ address asset ,
272
+ bytes [] memory signs
273
+ ) internal virtual whenNotPaused {}
274
+
275
+ /**
276
+ * @dev Hook function that runs after a new deal is created.
277
+ * Allows inheriting smart contracts to perform custom logic.
278
+ * @param offer The offer used to create the deal
279
+ * @param price The price of the asset in wei
280
+ * @param asset The address of the ERC20 token used for payment
281
+ * @param signs An array of signatures authorizing the creation of the deal
282
+ */
283
+ function _afterDealCreated (
284
+ Offer memory offer ,
285
+ uint256 price ,
286
+ address asset ,
287
+ bytes [] memory signs
288
+ ) internal virtual {}
289
+
290
+ /**
291
+ * @dev Hook function that runs before the deal is claimed.
292
+ * Allows inheriting smart contracts to perform custom logic.
293
+ * @param offerId The offerId of the deal
294
+ */
295
+ function _beforeDealClaimed (
296
+ bytes32 offerId ,
297
+ address buyer
298
+ ) internal virtual whenNotPaused {}
299
+
300
+ /**
301
+ * @dev Hook function that runs after the deal is claimed.
302
+ * Allows inheriting smart contracts to perform custom logic.
303
+ * @param offerId The offerId of the deal
304
+ */
305
+ function _afterDealClaimed (bytes32 offerId , address buyer ) internal virtual {}
306
+
307
+ /// Features
308
+
224
309
/**
225
310
* @dev Creates a Deal on a base of an offer
226
311
* @param offer An offer payload
227
312
* @param paymentOptions Raw offered payment options array
228
313
* @param paymentId Payment option Id
229
314
* @param signs Signatures: [0] - offer: ECDSA/ERC1271; [1] - asset permit: ECDSA (optional)
230
315
*
316
+ * Requirements:
317
+ *
318
+ * - supplier of the offer must be registered
319
+ * - offer must be signed with a proper signer
320
+ * - the deal should not be created before
321
+ * - the deal should not be created inside the _before hook
322
+ * - payment options must be valid (equal to those from the offer)
323
+ * - payment Id must exists in payment options
324
+ * - the contract must be able to make transfer of funds
325
+ *
231
326
* NOTE: `permit` signature can be ECDSA of type only
232
327
*/
233
- function _deal (
328
+ function deal (
234
329
Offer memory offer ,
235
330
PaymentOption[] memory paymentOptions ,
236
331
bytes32 paymentId ,
237
332
bytes [] memory signs
238
- ) internal virtual {
333
+ ) external {
239
334
address buyer = _msgSender ();
240
335
241
336
bytes32 offerHash = _hashTypedDataV4 (hash (offer));
@@ -288,6 +383,11 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
288
383
289
384
_beforeDealCreated (offer, price, asset, signs);
290
385
386
+ // Check that the deal was not created by `_beforeDealCreated` hook
387
+ if (deals[offer.id].offer.id == offer.id) {
388
+ revert DealAlreadyCreated ();
389
+ }
390
+
291
391
// Creating the deal before any external call to avoid reentrancy
292
392
deals[offer.id] = Deal (offer, buyer, price, asset, DealStatus.Created);
293
393
@@ -308,34 +408,48 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
308
408
}
309
409
310
410
/**
311
- * @dev Hook function that runs before a new deal is created.
312
- * Allows inheriting smart contracts to perform custom logic.
313
- * @param offer The offer used to create the deal
314
- * @param price The price of the asset in wei
315
- * @param asset The address of the ERC20 token used for payment
316
- * @param signs An array of signatures authorizing the creation of the deal
411
+ * @dev Claims the deal
412
+ * @param offerId The deal offer Id
413
+ *
414
+ * Requirements:
415
+ *
416
+ * - the deal must exists
417
+ * - the deal must be in status DealStatus.Created
418
+ * - must be called by the signer address of the deal offer supplier
317
419
*/
318
- function _beforeDealCreated (
319
- Offer memory offer ,
320
- uint256 price ,
321
- address asset ,
322
- bytes [] memory signs
323
- ) internal virtual {}
420
+ function claim (bytes32 offerId ) external {
421
+ Deal storage claimingDeal = deals[offerId];
324
422
325
- /**
326
- * @dev Hook function that runs after a new deal is created.
327
- * Allows inheriting smart contracts to perform custom logic.
328
- * @param offer The offer used to create the deal
329
- * @param price The price of the asset in wei
330
- * @param asset The address of the ERC20 token used for payment
331
- * @param signs An array of signatures authorizing the creation of the deal
332
- */
333
- function _afterDealCreated (
334
- Offer memory offer ,
335
- uint256 price ,
336
- address asset ,
337
- bytes [] memory signs
338
- ) internal virtual {}
423
+ // Deal must exists
424
+ if (claimingDeal.offer.id == bytes32 (0 )) {
425
+ revert DealNotFound ();
426
+ }
427
+
428
+ // Deal should not be claimed
429
+ if (claimingDeal.status != DealStatus.Created) {
430
+ revert DealNotCreated ();
431
+ }
432
+
433
+ address signer = _msgSender ();
434
+ Supplier storage supplier = suppliers[claimingDeal.offer.supplierId];
435
+
436
+ // Registered signer of the supplier is allowed to claim the deal
437
+ if (signer != supplier.signer) {
438
+ revert NotAllowed ();
439
+ }
440
+
441
+ _beforeDealClaimed (offerId, claimingDeal.buyer);
442
+
443
+ // Prevent claiming of the deal inside the `_beforeDealClaimed` hook
444
+ if (claimingDeal.status == DealStatus.Claimed) {
445
+ revert DealAlreadyClaimed ();
446
+ }
447
+
448
+ claimingDeal.status = DealStatus.Claimed;
449
+ emit DealClaimed (offerId, signer);
450
+
451
+ _afterDealClaimed (offerId, claimingDeal.buyer);
452
+ }
339
453
340
454
uint256 [50 ] private __gap;
341
455
}
0 commit comments