diff --git a/.prettierrc b/.prettierrc index 2251d87..5c8c51c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "plugins": ["prettier-plugin-solidity"], + "plugins": ["prettier-plugin-solidity", "prettier-plugin-jsdoc"], "semi": true, "trailingComma": "all", "singleQuote": true, diff --git a/contracts/Configurable.sol b/contracts/Configurable.sol new file mode 100644 index 0000000..d2401ec --- /dev/null +++ b/contracts/Configurable.sol @@ -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); + } +} diff --git a/contracts/DealsRegistry.sol b/contracts/DealsRegistry.sol index 7f23e28..0c5b2f4 100644 --- a/contracts/DealsRegistry.sol +++ b/contracts/DealsRegistry.sol @@ -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"; @@ -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; @@ -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(); @@ -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 @@ -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) { @@ -221,6 +255,57 @@ 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 @@ -228,14 +313,24 @@ abstract contract DealsRegistry is Context, EIP712, SuppliersRegistry { * @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)); @@ -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); @@ -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; } diff --git a/contracts/ERC1155Token.sol b/contracts/ERC1155Token.sol deleted file mode 100644 index d0e851f..0000000 --- a/contracts/ERC1155Token.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; - -abstract contract ERC1155Token is ERC1155(""), Ownable, ERC1155Supply { - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override(ERC1155, ERC1155Supply) { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } - - uint256[50] private __gap; -} diff --git a/contracts/ERC721Token.sol b/contracts/ERC721Token.sol new file mode 100644 index 0000000..9912447 --- /dev/null +++ b/contracts/ERC721Token.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// Import OpenZeppelin ERC721, ERC721Enumerable, Ownable, Pausable contracts +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/security/Pausable.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; + +/** + * @title ERC721Token + * @dev Abstract contract that defines an ERC721 token with additional functionality for enumeration and pausing + */ +abstract contract ERC721Token is ERC721, ERC721Enumerable, Pausable { + using Counters for Counters.Counter; + + /// @dev Internal counter to keep track of tokenIds + Counters.Counter private _tokenIdCounter; + + /** + * @dev Constructor that sets the name and symbol of the token + * @param name The name of the token + * @param symbol The symbol of the token + */ + constructor(string memory name, string memory symbol) ERC721(name, symbol) {} + + /** + * @dev Internal function to safely mint an NFT to an address + * @param to The address that will receive the minted NFT + */ + function safeMint(address to) internal returns (uint256) { + uint256 tokenId = _tokenIdCounter.current(); + _tokenIdCounter.increment(); + _safeMint(to, tokenId); + return tokenId; + } + + /** + * @dev Internal function to safely burn an NFT + * @param tokenId The ID of the NFT to be burnt + */ + function safeBurn(uint256 tokenId) internal { + require( + _isApprovedOrOwner(_msgSender(), tokenId), + "ERC721: caller is not token owner or approved" + ); + _burn(tokenId); + } + + /** + * @dev See {ERC721-_beforeTokenTransfer} + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId, + uint256 batchSize + ) internal virtual override(ERC721, ERC721Enumerable) whenNotPaused { + super._beforeTokenTransfer(from, to, tokenId, batchSize); + } + + /** + * @dev See {ERC721-supportsInterface} + */ + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC721, ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/Market.sol b/contracts/Market.sol index 9341477..5d7dd4c 100644 --- a/contracts/Market.sol +++ b/contracts/Market.sol @@ -1,27 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; -import "./ERC1155Token.sol"; +import "./ERC721Token.sol"; import "./DealsRegistry.sol"; -contract Market is Pausable, DealsRegistry, ERC1155Token { +/** + * @title Market + * @dev This contract enables the creation and management of deals + * @custom:security-contact security@windingtree.com + */ +contract Market is Ownable, Pausable, DealsRegistry, ERC721Token { + /// Mapping of tokenId on offer Id + mapping(uint256 => bytes32) public tokenOffers; + + /// Throws when NFT transfer is not allowed by offer rule + error TokenTransferNotAllowed(); + + /** + * @dev Constructor that initializes the Market contract with the given arguments + * @param owner The owner of the contract + * @param name The name of the contract + * @param version The version of the contract + * @param asset The address of the asset + * @param minDeposit The minimum deposit required for the contract + */ constructor( address owner, string memory name, string memory version, address asset, uint256 minDeposit - ) DealsRegistry(name, version, asset, minDeposit) { + ) + DealsRegistry(name, version, asset, minDeposit) + ERC721Token("DealToken", "DEAL") + { transferOwnership(owner); } + /// Getters + + /** + * @dev Returns offerId linked to the token + * @param tokenId The ID of the token + * @return offerId The ID of the offer linked to the token + */ + function resolveTokenId( + uint256 tokenId + ) external view returns (bytes32 offerId) { + _requireMinted(tokenId); + offerId = tokenOffers[tokenId]; + } + /// Pausable features + /** + * @dev Pauses the contract + */ function pause() public onlyOwner { _pause(); } + /** + * @dev Unpauses the contract + */ function unpause() public onlyOwner { _unpause(); } @@ -29,31 +72,27 @@ contract Market is Pausable, DealsRegistry, ERC1155Token { /// Deals features /** - * @dev See {DealsRegistry-deal}. - */ - function deal( - Offer memory offer, - PaymentOption[] memory paymentOptions, - bytes32 paymentId, - bytes[] memory signs - ) external whenNotPaused { - _deal(offer, paymentOptions, paymentId, signs); - } - - /** - * @dev See {DealsRegistry-_beforeDealCreated}. + * @dev Executes before a deal is created + * @param offer The details of the offer + * @param price The price of the offer + * @param asset The address of the asset + * @param signs The signatures of the offer */ function _beforeDealCreated( Offer memory offer, uint256 price, address asset, bytes[] memory signs - ) internal override(DealsRegistry) { + ) internal override(DealsRegistry) whenNotPaused { super._beforeDealCreated(offer, price, asset, signs); } /** - * @dev See {DealsRegistry-_afterDealCreated}. + * @dev Executes after a deal is created + * @param offer The details of the offer + * @param price The price of the offer + * @param asset The address of the asset + * @param signs The signatures of the offer */ function _afterDealCreated( Offer memory offer, @@ -61,93 +100,85 @@ contract Market is Pausable, DealsRegistry, ERC1155Token { address asset, bytes[] memory signs ) internal override(DealsRegistry) { + // After-deal logic super._afterDealCreated(offer, price, asset, signs); } - /// Suppliers features - - /** - * @dev See {SuppliersRegistry-_register}. - */ - function register(bytes32 salt, address signer) external whenNotPaused { - _register(salt, signer); - } - /** - * @dev See {SuppliersRegistry-_changeSigner}. + * @dev Executes after a deal is claimed + * @param offerId The ID of the offer + * @param buyer The address of the buyer */ - function changeSigner( - bytes32 id, - address signer - ) external onlySupplierOwner(id) { - _changeSigner(id, signer); - } - - /** - * @dev See {SuppliersRegistry-_toggleSupplier}. - */ - function toggleSupplier( - bytes32 id - ) external onlySupplierOwner(id) whenNotPaused { - _toggleSupplier(id); - } - - /** - * @dev See {SuppliersRegistry._addDeposit}. - */ - function addDeposit( - bytes32 id, - uint256 value - ) external onlySupplierOwner(id) whenNotPaused { - _addDeposit(id, value, 0, ""); - } - - /** - * @dev See {SuppliersRegistry-_addDeposit}. - */ - function addDeposit( - bytes32 id, - uint256 value, - uint256 deadline, - bytes memory sign - ) external onlySupplierOwner(id) whenNotPaused { - _addDeposit(id, value, deadline, sign); - } - - /** - * @dev See {SuppliersRegistry-_withdrawDeposit}. - */ - function withdrawDeposit( - bytes32 id, - uint256 value - ) external onlySupplierOwner(id) whenNotPaused { - _withdrawDeposit(id, value); + function _afterDealClaimed( + bytes32 offerId, + address buyer + ) internal override(DealsRegistry) { + // Minting of a token + uint256 tokenId = safeMint(buyer); + // Create a map of tokenId on offer Id + tokenOffers[tokenId] = offerId; + super._afterDealClaimed(offerId, buyer); } - /// ERC1155 features + /// ERC721 features /** - * @dev See {IERC1155MetadataURI-uri}. + * @dev Returns the token URI of the given token ID + * @param tokenId The ID of the token + * @return The token URI of the given token ID */ - function uri( - uint256 id - ) public view override(ERC1155) returns (string memory) { - // Generate data-uri that depends on the id + function tokenURI( + uint256 tokenId + ) public view override(ERC721) returns (string memory) { + _requireMinted(tokenId); + // TODO: Generate data-uri that depends on the id return ""; } /** - * @dev See {ERC1155-_beforeTokenTransfer}. + * @dev Executes before a token transfer + * @param from The address to transfer the token from + * @param to The address to transfer the token to + * @param tokenId The ID of the token being transferred + * @param batchSize The size of the batch being transferred + * + * NOTE: Initially minted token is transferred to his owner without any restrictions + * All other transfers are managed according the following requirements: + * + * - token must be linked to an offerId + * - token can be transferred or not according to the configuration of offer + * - token can not be transferred when the deal status is `Claimed` only */ function _beforeTokenTransfer( - address operator, address from, address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal override(ERC1155Token) { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + uint256 tokenId, + uint256 batchSize + ) internal override(ERC721Token) whenNotPaused { + // Execute the logic when the function called not from `_mint` + if (from != address(0)) { + bytes32 offerId = tokenOffers[tokenId]; + + if (offerId == bytes32(0)) { + revert DealNotFound(); + } + + Deal storage offerDeal = deals[offerId]; + + // Prevent transfer of token when this is not allowed by the offer + // or the deal is in the non-transferrable status + if ( + !offerDeal.offer.transferable || + offerDeal.status != DealStatus.Claimed + ) { + revert TokenTransferNotAllowed(); + } + + // Change the deal buyer to the new token owner + offerDeal.buyer = to; + } + + super._beforeTokenTransfer(from, to, tokenId, batchSize); } uint256[50] private __gap; diff --git a/contracts/SuppliersRegistry.sol b/contracts/SuppliersRegistry.sol index 3e3544e..93c14fd 100644 --- a/contracts/SuppliersRegistry.sol +++ b/contracts/SuppliersRegistry.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/Context.sol"; +import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import "./Configurable.sol"; import "./utils/IERC20.sol"; import "./utils/SignatureUtils.sol"; @@ -10,16 +12,10 @@ import "./utils/SignatureUtils.sol"; * @title SuppliersRegistry * @dev A smart contract for registering and managing suppliers who can participate in deals. */ -abstract contract SuppliersRegistry is Context { +abstract contract SuppliersRegistry is Context, Configurable, Pausable { using SafeMath for uint256; using SignatureUtils for bytes; - /// @dev Supplier's deposit asset - address public asset; - - /// @dev Minimum deposit value - uint256 public minDeposit; - /** * @dev Supplier storage struct * @param id Unique supplier Id @@ -87,8 +83,10 @@ abstract contract SuppliersRegistry is Context { * @dev SuppliersRegistry constructor */ constructor(address _asset, uint256 _minDeposit) { - asset = _asset; - minDeposit = _minDeposit; + // Supplier's deposit asset + config("asset", _asset); + // Minimum deposit value + config("min_deposit", _minDeposit); } /// Getters @@ -109,7 +107,7 @@ abstract contract SuppliersRegistry is Context { return suppliers[id].enabled; } - /// Internal functions + /// Features /** * @dev Registers a new supplier entity @@ -127,7 +125,7 @@ abstract contract SuppliersRegistry is Context { * NOTE: When the supplier is registered its initial `enabled` status is set to `false`. * This means that to start accepting deals the supplier must be enabled */ - function _register(bytes32 salt, address signer) internal { + function register(bytes32 salt, address signer) external whenNotPaused { address supplierOwner = _msgSender(); bytes32 id = keccak256(abi.encodePacked(supplierOwner, salt)); @@ -151,10 +149,10 @@ abstract contract SuppliersRegistry is Context { * * - can be called by the supplier owner only */ - function _changeSigner( + function changeSigner( bytes32 id, address signer - ) internal onlySupplierOwner(id) { + ) external onlySupplierOwner(id) whenNotPaused { address oldSigner = suppliers[id].signer; suppliers[id].signer = signer; emit SignerChanged(id, _msgSender(), oldSigner, signer); @@ -173,12 +171,64 @@ abstract contract SuppliersRegistry is Context { * * - can be called by the supplier owner only */ - function _toggleSupplier(bytes32 id) internal onlySupplierOwner(id) { + function toggleSupplier( + bytes32 id + ) external onlySupplierOwner(id) whenNotPaused { bool enabled = !suppliers[id].enabled; suppliers[id].enabled = enabled; emit ToggleEnabled(id, _msgSender(), enabled); } + /** + * @dev See {SuppliersRegistry._addDeposit}. + */ + function addDeposit(bytes32 id, uint256 value) external whenNotPaused { + _addDeposit(id, value, 0, ""); + } + + /** + * @dev See {SuppliersRegistry-_addDeposit}. + */ + function addDeposit( + bytes32 id, + uint256 value, + uint256 deadline, + bytes memory sign + ) external whenNotPaused { + _addDeposit(id, value, deadline, sign); + } + + /** + * @dev Makes deposit withdrawal of the supplier + * @param id The supplier Id + * @param value Amount of `asset` tokens that must be withdrawn + * + * If the tokens withdrawal is succeeded: + * - the function emits `Withdraw(bytes32 id, address sender, uint256 value)` event + * + * Requirements: + * + * - can be called by the supplier owner only + */ + function withdrawDeposit( + bytes32 id, + uint256 value + ) external onlySupplierOwner(id) whenNotPaused { + if (deposits[id] < value) { + revert DepositNotEnough(); + } + + if (!IERC20(addresses["asset"]).transfer(_msgSender(), value)) { + revert DepositTransferFailed(); + } + + deposits[id] = deposits[id].sub(value); + + emit Withdraw(id, _msgSender(), value); + } + + /// Internal functions + /** * @dev Makes deposit of `asset` tokens with permit * @param id The supplier Id @@ -203,11 +253,12 @@ abstract contract SuppliersRegistry is Context { uint256 deadline, bytes memory sign ) internal onlySupplierOwner(id) { - if (deposits[id].add(value) < minDeposit) { + if (deposits[id].add(value) < numbers["min_deposit"]) { revert DepositTooSmall(); } address supplierOwner = _msgSender(); + address asset = addresses["asset"]; if (sign.length > 0) { // Use permit function to transfer tokens from the sender to the contract @@ -233,34 +284,5 @@ abstract contract SuppliersRegistry is Context { emit Deposit(id, supplierOwner, value); } - /** - * @dev Makes deposit withdrawal of the supplier - * @param id The supplier Id - * @param value Amount of `asset` tokens that must be withdrawn - * - * If the tokens withdrawal is succeeded: - * - the function emits `Withdraw(bytes32 id, address sender, uint256 value)` event - * - * Requirements: - * - * - can be called by the supplier owner only - */ - function _withdrawDeposit( - bytes32 id, - uint256 value - ) internal onlySupplierOwner(id) { - if (deposits[id] < value) { - revert DepositNotEnough(); - } - - if (!IERC20(asset).transfer(_msgSender(), value)) { - revert DepositTransferFailed(); - } - - deposits[id] = deposits[id].sub(value); - - emit Withdraw(id, _msgSender(), value); - } - uint256[50] private __gap; } diff --git a/package.json b/package.json index 100c966..50770ef 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "prettier": "^2.8.7", "prettier-plugin-solidity": "^1.1.3", "solhint-plugin-prettier": "^0.0.5", + "prettier-plugin-jsdoc": "^0.4.2", "husky": "^8.0.3", "git-cz": "^4.9.0", "@commitlint/config-conventional": "^17.6.1", diff --git a/test/contracts/Market.test.ts b/test/contracts/Market.test.ts index 954c2e8..7d9fbd4 100644 --- a/test/contracts/Market.test.ts +++ b/test/contracts/Market.test.ts @@ -116,7 +116,7 @@ describe('Market contract', () => { let erc20: MockERC20Dec18; let lif: MockERC20Dec18Permit; - before(() => { + before(async () => { provider = Provider.getDefaultProvider(); [ deployerWallet, @@ -126,9 +126,6 @@ describe('Market contract', () => { supplierSignerWallet, ] = createWallets(provider); deployer = new Deployer(hre, deployerWallet); - }); - - before(async () => { erc20 = await deployErc20(deployer, deployerWallet.address, 'STABLE'); lif = await deployErc20Permit(deployer, deployerWallet.address, 'LIF'); market = await deployMarket(deployer, deployerWallet.address, lif); @@ -326,6 +323,7 @@ describe('Market contract', () => { }); it('should add deposit', async () => { + const lifBefore = await lif.balanceOf(supplierOwnerWallet.address); expect(await market.balanceOfSupplier(supplierId)).to.eq(minDeposit); const value = BigNumber.from('1'); await ( @@ -337,6 +335,9 @@ describe('Market contract', () => { ['addDeposit(bytes32,uint256)'](supplierId, value) ).wait(); expect(await market.balanceOfSupplier(supplierId)).to.eq(minDeposit.add(value)); + expect(await lif.balanceOf(supplierOwnerWallet.address)).to.eq( + lifBefore.sub(value), + ); }); it.skip('should throw if invalid permit signature provided', async () => { @@ -389,6 +390,7 @@ describe('Market contract', () => { }); it('should withdraw deposit', async () => { + const lifBefore = await lif.balanceOf(supplierOwnerWallet.address); expect(await market.balanceOfSupplier(supplierId)).to.eq(minDeposit); await ( await market @@ -396,6 +398,9 @@ describe('Market contract', () => { .withdrawDeposit(supplierId, minDeposit) ).wait(); expect(await market.balanceOfSupplier(supplierId)).to.eq(constants.Zero); + expect(await lif.balanceOf(supplierOwnerWallet.address)).to.eq( + lifBefore.add(minDeposit), + ); }); }); }); @@ -442,7 +447,7 @@ describe('Market contract', () => { ); }); - describe('#deal(**)', () => { + describe('#deal(Offer,PaymentOption[],bytes32,bytes[])', () => { it('should throw if invalid payment options provided', async () => { await expect( ( @@ -579,5 +584,137 @@ describe('Market contract', () => { ).to.rejected; //revertedWithCustomError(market, 'DisabledSupplier'); }); }); + + describe('#claim(bytes32)', () => { + describe('without deal', () => { + it('should throw if deal not found', async () => { + await expect( + ( + await market.connect(supplierSignerWallet).claim(randomId(), { + gasLimit: 5000000, + }) + ).wait(), + ).to.rejected; + }); + }); + + describe('with deal', () => { + let offer: Offer; + + beforeEach(async () => { + offer = await buildRandomOffer( + supplierId, + supplierSignerWallet, + 'Market', + '1', + ( + await market.provider.getNetwork() + ).chainId, + market.address, + erc20.address, + ); + await ( + await erc20 + .connect(buyerWallet) + .approve(market.address, offer.payment[0].price) + ).wait(); + console.log('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'); + await ( + await market + .connect(buyerWallet) + .deal(offer.payload, offer.payment, offer.payment[0].id, [offer.signature]) + ).wait(); + }); + + it('should throw called not by signer', async () => { + await expect( + ( + await market.connect(buyerWallet).claim(offer.payload.id, { + gasLimit: 5000000, + }) + ).wait(), + ).to.rejected; //revertedWithCustomError(market, 'NotAllowed'); + }); + + it('should claim the deal', async () => { + const tx = await market.connect(supplierSignerWallet).claim(offer.payload.id); + await tx.wait(); + await expect(tx) + .to.emit(market, 'DealClaimed') + .withArgs(offer.payload.id, supplierSignerWallet.address); + await expect(tx) + .to.emit(market, 'Transfer') + .withArgs(constants.AddressZero, buyerWallet.address, 0); + expect(await market.resolveTokenId(0)).to.eq(offer.payload.id); + const { + offer: contractOffer, + buyer, + price, + asset, + status, + } = await market.deals(offer.payload.id); + expect(buyer).to.eq(buyerWallet.address); + structEqual(contractOffer, offer.payload); + expect(price).to.eq(offer.payment[0].price); + expect(asset).to.eq(offer.payment[0].asset); + expect(status).to.eq(1); + }); + + it('should throw id deal "not-created"', async () => { + await ( + await market.connect(supplierSignerWallet).claim(offer.payload.id) + ).wait(); + await expect( + ( + await market.connect(supplierSignerWallet).claim(offer.payload.id, { + gasLimit: 5000000, + }) + ).wait(), + ).to.rejected; //revertedWithCustomError(market, 'DealNotCreated'); + }); + }); + }); + + describe('#transferFrom(address,address,uint256)', () => { + let offer: Offer; + + beforeEach(async () => { + offer = await buildRandomOffer( + supplierId, + supplierSignerWallet, + 'Market', + '1', + ( + await market.provider.getNetwork() + ).chainId, + market.address, + erc20.address, + true, + ); + await ( + await erc20.connect(buyerWallet).approve(market.address, offer.payment[0].price) + ).wait(); + await ( + await market + .connect(buyerWallet) + .deal(offer.payload, offer.payment, offer.payment[0].id, [offer.signature]) + ).wait(); + await (await market.connect(supplierSignerWallet).claim(offer.payload.id)).wait(); + }); + + it('should transfer', async () => { + expect(await market.ownerOf(0)).to.eq(buyerWallet.address); + const { status } = await market.deals(offer.payload.id); + expect(status).to.eq(1); // claimed + const tx = await market + .connect(buyerWallet) + .transferFrom(buyerWallet.address, notOwnerWallet.address, 0); + await tx.wait(); + await expect(tx) + .to.emit(market, 'Transfer') + .withArgs(buyerWallet.address, notOwnerWallet.address, 0); + expect(await market.ownerOf(0)).to.eq(notOwnerWallet.address); + }); + }); }); }); diff --git a/test/contracts/utils.ts b/test/contracts/utils.ts index 8b58f67..342b0a7 100644 --- a/test/contracts/utils.ts +++ b/test/contracts/utils.ts @@ -186,6 +186,7 @@ export const buildRandomOffer = async ( chainId: BigNumberish, verifyingContract: string, erc20address: string, + transferableOverride?: boolean, ): Promise => { const request: Request = { id: randomId(), @@ -221,7 +222,7 @@ export const buildRandomOffer = async ( optionsHash: hashObject({}), paymentHash: hashPaymentOptionArray(payment), cancelHash: hashCancelOptionArray(cancel), - transferable: Math.random() > 0.5, + transferable: transferableOverride ?? Math.random() > 0.5, checkIn: checkInTime, }; diff --git a/yarn.lock b/yarn.lock index 4394af0..a061cfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1573,6 +1573,13 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== +"@types/debug@^4.0.0": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" + "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -1583,6 +1590,13 @@ resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== +"@types/mdast@^3.0.0": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0" + integrity sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw== + dependencies: + "@types/unist" "*" + "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" @@ -1593,6 +1607,11 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== +"@types/ms@*": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + "@types/node@*", "@types/node@^18.15.11": version "18.15.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" @@ -1640,6 +1659,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/unist@*", "@types/unist@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + "@typescript-eslint/eslint-plugin@^5.58.0": version "5.58.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz#b1d4b0ad20243269d020ef9bbb036a40b0849829" @@ -2752,6 +2776,11 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary-searching@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/binary-searching/-/binary-searching-2.0.5.tgz#ab6d08d51cd1b58878ae208ab61988f885b22dd3" + integrity sha512-v4N2l3RxL+m4zDxyxz3Ne2aTmiPn8ZUpKFpdPtO+ItW1NcTCXA7JeHG5GMBSvoKSkQZ9ycS+EouDVxYB9ufKWA== + bl@^1.0.0: version "1.2.3" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" @@ -3194,6 +3223,11 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3525,6 +3559,11 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1" integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== +comment-parser@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" + integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -3861,6 +3900,13 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -3930,6 +3976,11 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -3965,7 +4016,7 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.1.0: +diff@^5.0.0, diff@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== @@ -6303,6 +6354,11 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +kleur@^4.0.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -7030,6 +7086,31 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdast-util-from-markdown@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz#0214124154f26154a2b3f9d401155509be45e894" + integrity sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + decode-named-character-reference "^1.0.0" + mdast-util-to-string "^3.1.0" + micromark "^3.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-decode-string "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + unist-util-stringify-position "^3.0.0" + uvu "^0.5.0" + +mdast-util-to-string@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz#66f7bb6324756741c5f47a53557f0cbf16b6f789" + integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg== + dependencies: + "@types/mdast" "^3.0.0" + meant@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.3.tgz#67769af9de1d158773e928ae82c456114903554c" @@ -7076,6 +7157,201 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micromark-core-commonmark@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad" + integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-factory-destination "^1.0.0" + micromark-factory-label "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-factory-title "^1.0.0" + micromark-factory-whitespace "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-html-tag-name "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + +micromark-factory-destination@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e" + integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-label@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz#6be2551fa8d13542fcbbac478258fb7a20047137" + integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-space@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633" + integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-title@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz#7e09287c3748ff1693930f176e1c4a328382494f" + integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-factory-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c" + integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86" + integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-chunked@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06" + integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-classify-character@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20" + integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-combine-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5" + integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-decode-numeric-character-reference@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946" + integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-decode-string@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz#942252ab7a76dec2dbf089cc32505ee2bc3acf02" + integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-encode@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383" + integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA== + +micromark-util-html-tag-name@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz#eb227118befd51f48858e879b7a419fc0df20497" + integrity sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA== + +micromark-util-normalize-identifier@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828" + integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-resolve-all@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88" + integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-util-sanitize-uri@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz#f12e07a85106b902645e0364feb07cf253a85aee" + integrity sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-subtokenize@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105" + integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + uvu "^0.5.0" + +micromark-util-symbol@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e" + integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ== + +micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20" + integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w== + +micromark@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.1.0.tgz#eeba0fe0ac1c9aaef675157b52c166f125e89f62" + integrity sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + micromark-core-commonmark "^1.0.1" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + uvu "^0.5.0" + micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -7372,6 +7648,11 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8542,6 +8823,15 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" +prettier-plugin-jsdoc@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-0.4.2.tgz#c5668fc622ed10b87d988279476f96af96b058b7" + integrity sha512-w2jnAQm3z0GAG0bhzVJeehzDtrhGMSxJjit5ApCc2oxWfc7+jmLAkbtdOXaSpfwZz3IWkk+PiQPeRrLNpbM+Mw== + dependencies: + binary-searching "^2.0.5" + comment-parser "^1.3.1" + mdast-util-from-markdown "^1.2.0" + prettier-plugin-solidity@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" @@ -9288,6 +9578,13 @@ rxjs@^7.8.0: dependencies: tslib "^2.1.0" +sade@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -10620,6 +10917,13 @@ unique-string@^3.0.0: dependencies: crypto-random-string "^4.0.0" +unist-util-stringify-position@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d" + integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg== + dependencies: + "@types/unist" "^2.0.0" + universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -10744,6 +11048,16 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uvu@^0.5.0: + version "0.5.6" + resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df" + integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA== + dependencies: + dequal "^2.0.0" + diff "^5.0.0" + kleur "^4.0.3" + sade "^1.7.3" + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"