Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"plugins": ["prettier-plugin-solidity"],
"plugins": ["prettier-plugin-solidity", "prettier-plugin-jsdoc"],
"semi": true,
"trailingComma": "all",
"singleQuote": true,
Expand Down
63 changes: 63 additions & 0 deletions contracts/Configurable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

abstract contract Configurable is Ownable {
using EnumerableSet for EnumerableSet.Bytes32Set;

EnumerableSet.Bytes32Set private _variables;

/// @dev Mapping of a named Id to uint256 number
mapping(bytes32 => uint256) public numbers;

/// @dev Mapping of a named Id to address
mapping(bytes32 => address) public addresses;

/**
* @dev Emitted when a variable is created/updated
* @param name Name of variable
* @param value Value of variable
*/
event ConfigNumbers(bytes32 indexed name, uint256 indexed value);

/**
* @dev Emitted when a variable is created/updated
* @param name Name of variable
* @param value Value of variable
*/
event ConfigAddresses(bytes32 indexed name, address indexed value);

/// Features

/**
* @dev Returns a list of registered config variables names
* @return names Array of registered variables names
*/
function variables() external view returns (bytes32[] memory names) {
names = _variables.values();
}

/**
* @dev Changes variable uint256 of value
* @param name Name of variable
* @param value Value of variable
*/
function config(bytes32 name, uint256 value) public onlyOwner {
numbers[name] = value;
_variables.add(name);
emit ConfigNumbers(name, value);
}

/**
* @dev Changes variable uint256 of value
* @param name Name of variable
* @param value Value of variable
*/
function config(bytes32 name, address value) public onlyOwner {
addresses[name] = value;
_variables.add(name);
emit ConfigAddresses(name, value);
}
}
178 changes: 146 additions & 32 deletions contracts/DealsRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import "./Configurable.sol";
import "./SuppliersRegistry.sol";
import "./utils/IERC20.sol";
import "./utils/SignatureUtils.sol";
Expand All @@ -14,7 +15,12 @@ import "./utils/SignatureUtils.sol";
* The contract stores offers made by suppliers, and allows buyers to create deals based on those offers.
* Each deal specifies the payment and cancellation terms, and can be tracked on-chain using its unique Id.
*/
abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
abstract contract DealsRegistry is
Configurable,
Pausable,
SuppliersRegistry,
EIP712
{
using SignatureChecker for address;
using SignatureUtils for bytes;

Expand Down Expand Up @@ -110,16 +116,29 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
/**
* @dev Emitted when a Deal is created by a buyer
* @param offerId The Id of the offer used to create the deal
* @param buyer The address of the buyer who created the deal
* @param buyer The address of the buyer who is created the deal
*/
event DealCreated(bytes32 indexed offerId, address indexed buyer);

/**
* @dev Emitted when a Deal is claimed by a supplier's signer
* @param offerId The Id of the offer used to create the deal
* @param signer The address of the supplier's signer who is claimed the deal
*/
event DealClaimed(bytes32 indexed offerId, address indexed signer);

/// @dev Thrown when a user attempts to create a deal using an offer with an invalid signature
error InvalidOfferSignature();

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

/// @dev Thrown when Deal was created in the `_beforeDealCreated` hook
error DealAlreadyCreated();

/// @dev Thrown when the Deal is not found
error DealNotFound();

/// @dev Thrown when a user attempts to create a deal providing an invalid payment options
error InvalidPaymentOptions();

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

/// @dev Thrown when a function call is not allowed
error NotAllowed();

/// @dev Thrown when a user attempts to claim the deal in non-created status
error DealNotCreated();

/// @dev Thrown when a user attempts to claim already claimed deal
error DealAlreadyClaimed();

/**
* @dev DealsRegistry constructor
* @param name EIP712 contract name
Expand All @@ -145,7 +173,13 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry {
string memory version,
address asset,
uint256 minDeposit
) EIP712(name, version) SuppliersRegistry(asset, minDeposit) {}
) EIP712(name, version) SuppliersRegistry(asset, minDeposit) {
// The default time period, in seconds, allowed for the supplier to claim the deal.
// The buyer is not able to cancel the deal during this period
config("claim_period", 60);
}

/// Utilities

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

/// Workflow hooks

/**
* @dev Hook function that runs before a new deal is created.
* Allows inheriting smart contracts to perform custom logic.
* @param offer The offer used to create the deal
* @param price The price of the asset in wei
* @param asset The address of the ERC20 token used for payment
* @param signs An array of signatures authorizing the creation of the deal
*/
function _beforeDealCreated(
Offer memory offer,
uint256 price,
address asset,
bytes[] memory signs
) internal virtual whenNotPaused {}

/**
* @dev Hook function that runs after a new deal is created.
* Allows inheriting smart contracts to perform custom logic.
* @param offer The offer used to create the deal
* @param price The price of the asset in wei
* @param asset The address of the ERC20 token used for payment
* @param signs An array of signatures authorizing the creation of the deal
*/
function _afterDealCreated(
Offer memory offer,
uint256 price,
address asset,
bytes[] memory signs
) internal virtual {}

/**
* @dev Hook function that runs before the deal is claimed.
* Allows inheriting smart contracts to perform custom logic.
* @param offerId The offerId of the deal
*/
function _beforeDealClaimed(
bytes32 offerId,
address buyer
) internal virtual whenNotPaused {}

/**
* @dev Hook function that runs after the deal is claimed.
* Allows inheriting smart contracts to perform custom logic.
* @param offerId The offerId of the deal
*/
function _afterDealClaimed(bytes32 offerId, address buyer) internal virtual {}

/// Features

/**
* @dev Creates a Deal on a base of an offer
* @param offer An offer payload
* @param paymentOptions Raw offered payment options array
* @param paymentId Payment option Id
* @param signs Signatures: [0] - offer: ECDSA/ERC1271; [1] - asset permit: ECDSA (optional)
*
* Requirements:
*
* - supplier of the offer must be registered
* - offer must be signed with a proper signer
* - the deal should not be created before
* - the deal should not be created inside the _before hook
* - payment options must be valid (equal to those from the offer)
* - payment Id must exists in payment options
* - the contract must be able to make transfer of funds
*
* NOTE: `permit` signature can be ECDSA of type only
*/
function _deal(
function deal(
Offer memory offer,
PaymentOption[] memory paymentOptions,
bytes32 paymentId,
bytes[] memory signs
) internal virtual {
) external {
address buyer = _msgSender();

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

_beforeDealCreated(offer, price, asset, signs);

// Check that the deal was not created by `_beforeDealCreated` hook
if (deals[offer.id].offer.id == offer.id) {
revert DealAlreadyCreated();
}

// Creating the deal before any external call to avoid reentrancy
deals[offer.id] = Deal(offer, buyer, price, asset, DealStatus.Created);

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

/**
* @dev Hook function that runs before a new deal is created.
* Allows inheriting smart contracts to perform custom logic.
* @param offer The offer used to create the deal
* @param price The price of the asset in wei
* @param asset The address of the ERC20 token used for payment
* @param signs An array of signatures authorizing the creation of the deal
* @dev Claims the deal
* @param offerId The deal offer Id
*
* Requirements:
*
* - the deal must exists
* - the deal must be in status DealStatus.Created
* - must be called by the signer address of the deal offer supplier
*/
function _beforeDealCreated(
Offer memory offer,
uint256 price,
address asset,
bytes[] memory signs
) internal virtual {}
function claim(bytes32 offerId) external {
Deal storage claimingDeal = deals[offerId];

/**
* @dev Hook function that runs after a new deal is created.
* Allows inheriting smart contracts to perform custom logic.
* @param offer The offer used to create the deal
* @param price The price of the asset in wei
* @param asset The address of the ERC20 token used for payment
* @param signs An array of signatures authorizing the creation of the deal
*/
function _afterDealCreated(
Offer memory offer,
uint256 price,
address asset,
bytes[] memory signs
) internal virtual {}
// Deal must exists
if (claimingDeal.offer.id == bytes32(0)) {
revert DealNotFound();
}

// Deal should not be claimed
if (claimingDeal.status != DealStatus.Created) {
revert DealNotCreated();
}

address signer = _msgSender();
Supplier storage supplier = suppliers[claimingDeal.offer.supplierId];

// Registered signer of the supplier is allowed to claim the deal
if (signer != supplier.signer) {
revert NotAllowed();
}

_beforeDealClaimed(offerId, claimingDeal.buyer);

// Prevent claiming of the deal inside the `_beforeDealClaimed` hook
if (claimingDeal.status == DealStatus.Claimed) {
revert DealAlreadyClaimed();
}

claimingDeal.status = DealStatus.Claimed;
emit DealClaimed(offerId, signer);

_afterDealClaimed(offerId, claimingDeal.buyer);
}

uint256[50] private __gap;
}
21 changes: 0 additions & 21 deletions contracts/ERC1155Token.sol

This file was deleted.

Loading