Skip to content

Commit 878d800

Browse files
committed
feat: 🎸 Added claim feature with tests
1 parent 4985ed7 commit 878d800

File tree

11 files changed

+924
-192
lines changed

11 files changed

+924
-192
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"plugins": ["prettier-plugin-solidity"],
2+
"plugins": ["prettier-plugin-solidity", "prettier-plugin-jsdoc"],
33
"semi": true,
44
"trailingComma": "all",
55
"singleQuote": true,

contracts/Configurable.sol

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.19;
3+
4+
import "@openzeppelin/contracts/access/Ownable.sol";
5+
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
6+
7+
abstract contract Configurable is Ownable {
8+
using EnumerableSet for EnumerableSet.Bytes32Set;
9+
10+
EnumerableSet.Bytes32Set private _variables;
11+
12+
/// @dev Mapping of a named Id to uint256 number
13+
mapping(bytes32 => uint256) public numbers;
14+
15+
/// @dev Mapping of a named Id to address
16+
mapping(bytes32 => address) public addresses;
17+
18+
/**
19+
* @dev Emitted when a variable is created/updated
20+
* @param name Name of variable
21+
* @param value Value of variable
22+
*/
23+
event ConfigNumbers(bytes32 indexed name, uint256 indexed value);
24+
25+
/**
26+
* @dev Emitted when a variable is created/updated
27+
* @param name Name of variable
28+
* @param value Value of variable
29+
*/
30+
event ConfigAddresses(bytes32 indexed name, address indexed value);
31+
32+
/// Features
33+
34+
/**
35+
* @dev Returns a list of registered config variables names
36+
* @return names Array of registered variables names
37+
*/
38+
function variables() external view returns (bytes32[] memory names) {
39+
names = _variables.values();
40+
}
41+
42+
/**
43+
* @dev Changes variable uint256 of value
44+
* @param name Name of variable
45+
* @param value Value of variable
46+
*/
47+
function config(bytes32 name, uint256 value) public onlyOwner {
48+
numbers[name] = value;
49+
_variables.add(name);
50+
emit ConfigNumbers(name, value);
51+
}
52+
53+
/**
54+
* @dev Changes variable uint256 of value
55+
* @param name Name of variable
56+
* @param value Value of variable
57+
*/
58+
function config(bytes32 name, address value) public onlyOwner {
59+
addresses[name] = value;
60+
_variables.add(name);
61+
emit ConfigAddresses(name, value);
62+
}
63+
}

contracts/DealsRegistry.sol

Lines changed: 146 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.19;
33

4-
import "@openzeppelin/contracts/utils/Context.sol";
54
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
5+
import "@openzeppelin/contracts/security/Pausable.sol";
66
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
7+
import "./Configurable.sol";
78
import "./SuppliersRegistry.sol";
89
import "./utils/IERC20.sol";
910
import "./utils/SignatureUtils.sol";
@@ -14,7 +15,12 @@ import "./utils/SignatureUtils.sol";
1415
* The contract stores offers made by suppliers, and allows buyers to create deals based on those offers.
1516
* Each deal specifies the payment and cancellation terms, and can be tracked on-chain using its unique Id.
1617
*/
17-
abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
18+
abstract contract DealsRegistry is
19+
Configurable,
20+
Pausable,
21+
SuppliersRegistry,
22+
EIP712
23+
{
1824
using SignatureChecker for address;
1925
using SignatureUtils for bytes;
2026

@@ -110,16 +116,29 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
110116
/**
111117
* @dev Emitted when a Deal is created by a buyer
112118
* @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
114120
*/
115121
event DealCreated(bytes32 indexed offerId, address indexed buyer);
116122

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+
117130
/// @dev Thrown when a user attempts to create a deal using an offer with an invalid signature
118131
error InvalidOfferSignature();
119132

120133
/// @dev Thrown when a user attempts to create an already existing Deal
121134
error DealExists();
122135

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+
123142
/// @dev Thrown when a user attempts to create a deal providing an invalid payment options
124143
error InvalidPaymentOptions();
125144

@@ -135,6 +154,15 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
135154
/// @dev Thrown when the supplier of the offer is not enabled
136155
error DisabledSupplier();
137156

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+
138166
/**
139167
* @dev DealsRegistry constructor
140168
* @param name EIP712 contract name
@@ -145,7 +173,13 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
145173
string memory version,
146174
address asset,
147175
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
149183

150184
/// @dev Create a has of bytes32 array
151185
function hash(bytes32[] memory hashes) internal pure returns (bytes32) {
@@ -221,21 +255,82 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
221255
);
222256
}
223257

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+
224309
/**
225310
* @dev Creates a Deal on a base of an offer
226311
* @param offer An offer payload
227312
* @param paymentOptions Raw offered payment options array
228313
* @param paymentId Payment option Id
229314
* @param signs Signatures: [0] - offer: ECDSA/ERC1271; [1] - asset permit: ECDSA (optional)
230315
*
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+
*
231326
* NOTE: `permit` signature can be ECDSA of type only
232327
*/
233-
function _deal(
328+
function deal(
234329
Offer memory offer,
235330
PaymentOption[] memory paymentOptions,
236331
bytes32 paymentId,
237332
bytes[] memory signs
238-
) internal virtual {
333+
) external {
239334
address buyer = _msgSender();
240335

241336
bytes32 offerHash = _hashTypedDataV4(hash(offer));
@@ -288,6 +383,11 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
288383

289384
_beforeDealCreated(offer, price, asset, signs);
290385

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+
291391
// Creating the deal before any external call to avoid reentrancy
292392
deals[offer.id] = Deal(offer, buyer, price, asset, DealStatus.Created);
293393

@@ -308,34 +408,48 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
308408
}
309409

310410
/**
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
317419
*/
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];
324422

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+
}
339453

340454
uint256[50] private __gap;
341455
}

contracts/ERC1155Token.sol

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)