From efc520b4b0b36e7a5196ead4d20043802ff5103a Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Wed, 25 Oct 2023 16:51:04 +0200 Subject: [PATCH 01/15] chore(prep): protocol contracts to protocol dir --- contracts/contracts/HypercertMinter.sol | 229 --------- contracts/contracts/SemiFungible1155.sol | 433 ------------------ .../contracts/interfaces/IHypercertToken.sol | 62 --- contracts/remappings.txt | 1 + contracts/src/AllowlistMinter.sol | 70 --- contracts/src/interfaces/IAllowlist.sol | 14 - contracts/src/libs/Errors.sol | 15 - .../protocol}/AllowlistMinter.sol | 0 .../src/{ => protocol}/HypercertMinter.sol | 0 .../src/{ => protocol}/HypercertTrader.sol | 0 .../src/{ => protocol}/SemiFungible1155.sol | 0 .../protocol}/interfaces/IAllowlist.sol | 0 .../interfaces/IHypercertToken.sol | 0 .../interfaces/IHypercertTrader.sol | 0 .../protocol}/libs/Errors.sol | 0 contracts/test/foundry/AllowlistMinter.t.sol | 4 +- .../HypercertMinter.batchminting.t.sol | 4 +- .../foundry/HypercertMinter.pausable.t.sol | 4 +- contracts/test/foundry/HypercertMinter.t.sol | 4 +- .../foundry/HypercertMinter.transfers.t.sol | 6 +- .../test/foundry/HypercertTrader.admin.t.sol | 6 +- .../test/foundry/HypercertTrader.offers.t.sol | 6 +- .../test/foundry/HypercertTrader.sales.t.sol | 6 +- .../test/foundry/PerformanceTesting.t.sol | 4 +- contracts/test/foundry/SemiFungibleHelper.sol | 2 +- 25 files changed, 24 insertions(+), 846 deletions(-) delete mode 100644 contracts/contracts/HypercertMinter.sol delete mode 100644 contracts/contracts/SemiFungible1155.sol delete mode 100644 contracts/contracts/interfaces/IHypercertToken.sol delete mode 100644 contracts/src/AllowlistMinter.sol delete mode 100644 contracts/src/interfaces/IAllowlist.sol delete mode 100644 contracts/src/libs/Errors.sol rename contracts/{contracts => src/protocol}/AllowlistMinter.sol (100%) rename contracts/src/{ => protocol}/HypercertMinter.sol (100%) rename contracts/src/{ => protocol}/HypercertTrader.sol (100%) rename contracts/src/{ => protocol}/SemiFungible1155.sol (100%) rename contracts/{contracts => src/protocol}/interfaces/IAllowlist.sol (100%) rename contracts/src/{ => protocol}/interfaces/IHypercertToken.sol (100%) rename contracts/src/{ => protocol}/interfaces/IHypercertTrader.sol (100%) rename contracts/{contracts => src/protocol}/libs/Errors.sol (100%) diff --git a/contracts/contracts/HypercertMinter.sol b/contracts/contracts/HypercertMinter.sol deleted file mode 100644 index 917c02c5..00000000 --- a/contracts/contracts/HypercertMinter.sol +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import { IHypercertToken } from "./interfaces/IHypercertToken.sol"; -import { SemiFungible1155 } from "./SemiFungible1155.sol"; -import { AllowlistMinter } from "./AllowlistMinter.sol"; -import { PausableUpgradeable } from "oz-upgradeable/security/PausableUpgradeable.sol"; - -import { Errors } from "./libs/Errors.sol"; - -/// @title Contract for managing hypercert claims and whitelists -/// @author bitbeckers -/// @notice Implementation of the HypercertTokenInterface using { SemiFungible1155 } as underlying token. -/// @notice This contract supports whitelisted minting via { AllowlistMinter }. -/// @dev Wrapper contract to expose and chain functions. -contract HypercertMinter is IHypercertToken, SemiFungible1155, AllowlistMinter, PausableUpgradeable { - // solhint-disable-next-line const-name-snakecase - string public constant name = "HypercertMinter"; - /// @dev from typeID to a transfer policy - mapping(uint256 => TransferRestrictions) internal typeRestrictions; - - /// INIT - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol } - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol } - function initialize() public virtual initializer { - __SemiFungible1155_init(); - __Pausable_init(); - } - - /// EXTERNAL - - /// @notice Mint a semi-fungible token for the impact claim referenced via `uri` - /// @dev see {IHypercertToken} - function mintClaim( - address account, - uint256 units, - string memory _uri, - TransferRestrictions restrictions - ) external override whenNotPaused { - // This enables us to release this restriction in the future - if (msg.sender != account) revert Errors.NotAllowed(); - uint256 claimID = _mintNewTypeWithToken(account, units, _uri); - typeRestrictions[claimID] = restrictions; - emit ClaimStored(claimID, _uri, units); - } - - /// @notice Mint semi-fungible tokens for the impact claim referenced via `uri` - /// @dev see {IHypercertToken} - function mintClaimWithFractions( - address account, - uint256 units, - uint256[] calldata fractions, - string memory _uri, - TransferRestrictions restrictions - ) external override whenNotPaused { - // This enables us to release this restriction in the future - if (msg.sender != account) revert Errors.NotAllowed(); - //Using sum to compare units and fractions (sanity check) - if (_getSum(fractions) != units) revert Errors.Invalid(); - - uint256 claimID = _mintNewTypeWithTokens(account, fractions, _uri); - typeRestrictions[claimID] = restrictions; - emit ClaimStored(claimID, _uri, units); - } - - /// @notice Mint a semi-fungible token representing a fraction of the claim - /// @dev Calls AllowlistMinter to verify `proof`. - /// @dev Mints the `amount` of units for the hypercert stored under `claimID` - function mintClaimFromAllowlist( - address account, - bytes32[] calldata proof, - uint256 claimID, - uint256 units - ) external whenNotPaused { - _processClaim(proof, claimID, units); - _mintToken(account, claimID, units); - } - - /// @notice Mint semi-fungible tokens representing a fraction of the claims in `claimIDs` - /// @dev Calls AllowlistMinter to verify `proofs`. - /// @dev Mints the `amount` of units for the hypercert stored under `claimIDs` - function batchMintClaimsFromAllowlists( - address account, - bytes32[][] calldata proofs, - uint256[] calldata claimIDs, - uint256[] calldata units - ) external whenNotPaused { - uint256 len = claimIDs.length; - for (uint256 i; i < len; ) { - _processClaim(proofs[i], claimIDs[i], units[i]); - unchecked { - ++i; - } - } - _batchMintTokens(account, claimIDs, units); - } - - /// @notice Register a claim and the whitelist for minting token(s) belonging to that claim - /// @dev Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `units` - /// @dev Calls AllowlistMinter to store the `merkleRoot` as proof to authorize claims - function createAllowlist( - address account, - uint256 units, - bytes32 merkleRoot, - string memory _uri, - TransferRestrictions restrictions - ) external whenNotPaused { - uint256 claimID = _createTokenType(account, units, _uri); - _createAllowlist(claimID, merkleRoot, units); - typeRestrictions[claimID] = restrictions; - emit ClaimStored(claimID, _uri, units); - } - - /// @notice Split a claimtokens value into parts with summed value equal to the original - /// @dev see {IHypercertToken} - function splitFraction( - address _account, - uint256 _tokenID, - uint256[] calldata _newFractions - ) external whenNotPaused { - _splitTokenUnits(_account, _tokenID, _newFractions); - } - - /// @notice Merge the value of tokens belonging to the same claim - /// @dev see {IHypercertToken} - function mergeFractions(address _account, uint256[] calldata _fractionIDs) external whenNotPaused { - _mergeTokensUnits(_account, _fractionIDs); - } - - /// @notice Burn a claimtoken - /// @dev see {IHypercertToken} - function burnFraction(address _account, uint256 _tokenID) external whenNotPaused { - _burnToken(_account, _tokenID); - } - - /// @dev see {IHypercertToken} - function unitsOf(uint256 tokenID) external view override returns (uint256 units) { - units = _unitsOf(tokenID); - } - - /// @dev see {IHypercertToken} - function unitsOf(address account, uint256 tokenID) external view override returns (uint256 units) { - units = _unitsOf(account, tokenID); - } - - /// PAUSABLE - - function pause() external onlyOwner { - _pause(); - } - - function unpause() external onlyOwner { - _unpause(); - } - - /// METADATA - - /// @dev see { IHypercertMetadata} - function uri(uint256 tokenID) public view override(IHypercertToken, SemiFungible1155) returns (string memory _uri) { - _uri = SemiFungible1155.uri(tokenID); - } - - /// TRANSFER RESTRICTIONS - - function readTransferRestriction(uint256 tokenID) external view returns (string memory) { - TransferRestrictions temp = typeRestrictions[getBaseType(tokenID)]; - if (temp == TransferRestrictions.AllowAll) return "AllowAll"; - if (temp == TransferRestrictions.DisallowAll) return "DisallowAll"; - if (temp == TransferRestrictions.FromCreatorOnly) return "FromCreatorOnly"; - return ""; - } - - /// INTERNAL - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner { - // solhint-disable-previous-line no-empty-blocks - } - - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - - // By-pass transfer restrictions for minting and burning - if (from == address(0)) { - // Minting - return; - } else if (to == address(0)) { - // Burning - return; - } - - // Transfer case, where to and from are non-zero - uint256 len = ids.length; - for (uint256 i; i < len; ) { - uint256 typeID = getBaseType(ids[i]); - TransferRestrictions policy = typeRestrictions[typeID]; - if (policy == TransferRestrictions.DisallowAll) { - revert Errors.TransfersNotAllowed(); - } else if (policy == TransferRestrictions.FromCreatorOnly && from != creators[typeID]) { - revert Errors.TransfersNotAllowed(); - } - unchecked { - ++i; - } - } - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - * Assuming 30 available slots (slots cost space, cost gas) - * 1. typeRestrictions - */ - uint256[29] private __gap; -} diff --git a/contracts/contracts/SemiFungible1155.sol b/contracts/contracts/SemiFungible1155.sol deleted file mode 100644 index cd88714a..00000000 --- a/contracts/contracts/SemiFungible1155.sol +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-License-Identifier: MIT -// Used components of Enjin example implementation for mixed fungibility -// https://github.com/enjin/erc-1155/blob/master/contracts/ERC1155MixedFungibleMintable.sol -pragma solidity 0.8.16; - -import { ERC1155Upgradeable } from "oz-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; -import { ERC1155BurnableUpgradeable } from "oz-upgradeable/token/ERC1155/extensions/ERC1155BurnableUpgradeable.sol"; -import { ERC1155URIStorageUpgradeable } from "oz-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol"; -import { OwnableUpgradeable } from "oz-upgradeable/access/OwnableUpgradeable.sol"; -import { Initializable } from "oz-upgradeable/proxy/utils/Initializable.sol"; -import { UUPSUpgradeable } from "oz-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { Errors } from "./libs/Errors.sol"; - -/// @title Contract for minting semi-fungible EIP1155 tokens -/// @author bitbeckers -/// @notice Extends { Upgradeable1155 } token with semi-fungible properties and the concept of `units` -/// @dev Adds split bit strategy as described in [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155#non-fungible-tokens) -contract SemiFungible1155 is - Initializable, - ERC1155Upgradeable, - ERC1155BurnableUpgradeable, - ERC1155URIStorageUpgradeable, - OwnableUpgradeable, - UUPSUpgradeable -{ - /// @dev Counter used to generate next typeID. - uint256 internal typeCounter; - - /// @dev Bitmask used to expose only upper 128 bits of uint256 - uint256 internal constant TYPE_MASK = type(uint256).max << 128; - - /// @dev Bitmask used to expose only lower 128 bits of uint256 - uint256 internal constant NF_INDEX_MASK = type(uint256).max >> 128; - - uint256 internal constant FRACTION_LIMIT = 253; - - /// @dev Mapping of `tokenID` to address of `owner` - mapping(uint256 => address) internal owners; - - /// @dev Mapping of `tokenID` to address of `creator` - mapping(uint256 => address) internal creators; - - /// @dev Used to determine amount of `units` stored in token at `tokenID` - mapping(uint256 => uint256) internal tokenValues; - - /// @dev Used to find highest index of token belonging to token at `typeID` - mapping(uint256 => uint256) internal maxIndex; - - /// @dev Emitted on transfer of `value` between `fromTokenID` to `toTokenID` of the same `claimID` - event ValueTransfer(uint256 claimID, uint256 fromTokenID, uint256 toTokenID, uint256 value); - - /// @dev Emitted on transfer of `values` between `fromTokenIDs` to `toTokenIDs` of `claimIDs` - event BatchValueTransfer(uint256[] claimIDs, uint256[] fromTokenIDs, uint256[] toTokenIDs, uint256[] values); - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol } - // solhint-disable-next-line func-name-mixedcase - function __SemiFungible1155_init() public virtual onlyInitializing { - __ERC1155_init(""); - __ERC1155Burnable_init(); - __ERC1155URIStorage_init(); - __Ownable_init(); - __UUPSUpgradeable_init(); - } - - /// @dev Get index of fractional token at `_id` by returning lower 128 bit values - /// @dev Returns 0 if `_id` is a baseType - function getItemIndex(uint256 tokenID) internal pure returns (uint256) { - return tokenID & NF_INDEX_MASK; - } - - /// @dev Get base type ID for token at `_id` by returning upper 128 bit values - function getBaseType(uint256 tokenID) internal pure returns (uint256) { - return tokenID & TYPE_MASK; - } - - /// @dev Identify that token at `_id` is base type. - /// @dev Upper 128 bits identify base type ID, lower bits should be 0 - function isBaseType(uint256 tokenID) internal pure returns (bool) { - return (tokenID & TYPE_MASK == tokenID) && (tokenID & NF_INDEX_MASK == 0); - } - - /// @dev Identify that token at `_id` is fraction of a claim. - /// @dev Upper 128 bits identify base type ID, lower bits should be > 0 - function isTypedItem(uint256 tokenID) internal pure returns (bool) { - return (tokenID & TYPE_MASK != 0) && (tokenID & NF_INDEX_MASK != 0); - } - - /// READ - function ownerOf(uint256 tokenID) public view returns (address _owner) { - _owner = owners[tokenID]; - } - - /// @dev see {IHypercertToken} - function _unitsOf(uint256 tokenID) internal view returns (uint256 units) { - units = tokenValues[tokenID]; - } - - /// @dev see {IHypercertToken} - function _unitsOf(address account, uint256 tokenID) internal view returns (uint256 units) { - // Check if fraction token and accounts owns it - if (ownerOf(tokenID) == account) { - units = tokenValues[tokenID]; - } - } - - /// MUTATE - - /// @dev create token type ID based of token counter - - function _createTokenType(address _account, uint256 units, string memory _uri) internal returns (uint256 typeID) { - _notMaxType(typeCounter); - typeID = ++typeCounter << 128; - - creators[typeID] = _account; - tokenValues[typeID] = units; - - _setURI(typeID, _uri); - - //Event emitted for indexing purposes - emit TransferSingle(_account, address(0), address(0), typeID, 0); - } - - /// @dev Mint a new token type and the initial units - function _mintNewTypeWithToken( - address _account, - uint256 _units, - string memory _uri - ) internal returns (uint256 typeID) { - if (_units == 0) { - revert Errors.NotAllowed(); - } - typeID = _createTokenType(_account, _units, _uri); - - uint256 tokenID = typeID + ++maxIndex[typeID]; //1 based indexing, 0 holds type data - - tokenValues[tokenID] = _units; - - _mint(_account, tokenID, 1, ""); - emit ValueTransfer(typeID, 0, tokenID, _units); - } - - /// @dev Mint a new token type and the initial fractions - function _mintNewTypeWithTokens( - address _account, - uint256[] calldata _fractions, - string memory _uri - ) internal returns (uint256 typeID) { - typeID = _mintNewTypeWithToken(_account, _getSum(_fractions), _uri); - _splitTokenUnits(_account, typeID + maxIndex[typeID], _fractions); - } - - /// @dev Mint a new token for an existing type - function _mintToken(address _account, uint256 _typeID, uint256 _units) internal returns (uint256 tokenID) { - if (!isBaseType(_typeID)) revert Errors.NotAllowed(); - - _notMaxItem(maxIndex[_typeID]); - - unchecked { - tokenID = _typeID + ++maxIndex[_typeID]; //1 based indexing, 0 holds type data - } - - tokenValues[tokenID] = _units; - - _mint(_account, tokenID, 1, ""); - emit ValueTransfer(_typeID, 0, tokenID, _units); - } - - /// @dev Mint new tokens for existing types - /// @notice Enables batch claiming from multiple allowlists - function _batchMintTokens( - address _account, - uint256[] calldata _typeIDs, - uint256[] calldata _units - ) internal returns (uint256[] memory tokenIDs) { - uint256 len = _typeIDs.length; - - tokenIDs = new uint256[](len); - uint256[] memory amounts = new uint256[](len); - uint256[] memory zeroes = new uint256[](len); - - for (uint256 i; i < len; ) { - uint256 _typeID = _typeIDs[i]; - if (!isBaseType(_typeID)) revert Errors.NotAllowed(); - _notMaxItem(maxIndex[_typeID]); - - unchecked { - uint256 tokenID = _typeID + ++maxIndex[_typeID]; //1 based indexing, 0 holds type data - tokenValues[tokenID] = _units[i]; - tokenIDs[i] = tokenID; - amounts[i] = 1; - ++i; - } - } - - _mintBatch(_account, tokenIDs, amounts, ""); - emit BatchValueTransfer(_typeIDs, zeroes, tokenIDs, _units); - } - - /// @dev Split the units of `_tokenID` owned by `account` across `_values` - /// @dev `_values` must sum to total `units` held at `_tokenID` - function _splitTokenUnits(address _account, uint256 _tokenID, uint256[] calldata _values) internal { - if (_values.length > FRACTION_LIMIT || _values.length < 2) revert Errors.ArraySize(); - if (tokenValues[_tokenID] != _getSum(_values)) revert Errors.NotAllowed(); - - // Current token - uint256 _typeID = getBaseType(_tokenID); - uint256 valueLeft = tokenValues[_tokenID]; - - // Prepare batch processing, we want to skip the first entry - uint256 len = _values.length - 1; - - uint256[] memory typeIDs = new uint256[](len); - uint256[] memory fromIDs = new uint256[](len); - uint256[] memory toIDs = new uint256[](len); - uint256[] memory amounts = new uint256[](len); - uint256[] memory values = new uint256[](len); - - { - uint256[] memory _valuesCache = _values; - uint256 swapValue = _valuesCache[len]; - _valuesCache[len] = _valuesCache[0]; - _valuesCache[0] = swapValue; - - for (uint256 i; i < len; ) { - _notMaxItem(maxIndex[_typeID]); - - typeIDs[i] = _typeID; - fromIDs[i] = _tokenID; - toIDs[i] = _typeID + ++maxIndex[_typeID]; - amounts[i] = 1; - values[i] = _valuesCache[i]; - - unchecked { - ++i; - } - } - } - - _beforeUnitTransfer(_msgSender(), _account, fromIDs, toIDs, values, ""); - - for (uint256 i; i < len; ) { - valueLeft -= values[i]; - - tokenValues[toIDs[i]] = values[i]; - - unchecked { - ++i; - } - } - - tokenValues[_tokenID] = valueLeft; - - _mintBatch(_account, toIDs, amounts, ""); - - emit BatchValueTransfer(typeIDs, fromIDs, toIDs, values); - } - - /// @dev Merge the units of `_fractionIDs`. - /// @dev Base type of `_fractionIDs` must be identical for all tokens. - function _mergeTokensUnits(address _account, uint256[] memory _fractionIDs) internal { - if (_fractionIDs.length > FRACTION_LIMIT || _fractionIDs.length < 2) { - revert Errors.ArraySize(); - } - uint256 len = _fractionIDs.length - 1; - - uint256 target = _fractionIDs[len]; - - uint256 _totalValue; - uint256[] memory fromIDs = new uint256[](len); - uint256[] memory toIDs = new uint256[](len); - uint256[] memory values = new uint256[](len); - uint256[] memory amounts = new uint256[](len); - - { - for (uint256 i; i < len; ) { - uint256 _fractionID = _fractionIDs[i]; - fromIDs[i] = _fractionID; - toIDs[i] = target; - amounts[i] = 1; - values[i] = tokenValues[_fractionID]; - - unchecked { - ++i; - } - } - } - - _beforeUnitTransfer(_msgSender(), _account, fromIDs, toIDs, values, ""); - - for (uint256 i; i < len; ) { - _totalValue += values[i]; - - delete tokenValues[fromIDs[i]]; - unchecked { - ++i; - } - } - - tokenValues[target] += _totalValue; - - _burnBatch(_account, fromIDs, amounts); - } - - /// @dev Burn the token at `_tokenID` owned by `_account` - /// @dev Not allowed to burn base type. - /// @dev `_tokenID` must hold all value declared at base type - function _burnToken(address _account, uint256 _tokenID) internal { - if (_account != _msgSender() && !isApprovedForAll(_account, _msgSender())) revert Errors.NotApprovedOrOwner(); - - uint256 value = tokenValues[_tokenID]; - - delete tokenValues[_tokenID]; - - _burn(_account, _tokenID, 1); - emit ValueTransfer(getBaseType(_tokenID), _tokenID, 0, value); - } - - /// TRANSFERS - - // The following functions are overrides required by Solidity. - function _afterTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override { - super._afterTokenTransfer(operator, from, to, ids, amounts, data); - - uint256 len = ids.length; - - for (uint256 i; i < len; ) { - owners[ids[i]] = to; - unchecked { - ++i; - } - } - } - - function _beforeUnitTransfer( - address operator, - address from, - uint256[] memory fromIDs, - uint256[] memory toIDs, - uint256[] memory values, - bytes memory data - ) internal virtual { - uint256 len = fromIDs.length; - - for (uint256 i; i < len; ) { - uint256 _from = fromIDs[i]; - uint256 _to = toIDs[i]; - - if (isBaseType(_from)) revert Errors.NotAllowed(); - if (getBaseType(_from) != getBaseType(_to)) revert Errors.TypeMismatch(); - if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) revert Errors.NotApprovedOrOwner(); - unchecked { - ++i; - } - } - } - - /// METADATA - - /// @dev see { openzeppelin-contracts-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol } - /// @dev Always returns the URI for the basetype so that it's managed in one place. - function uri( - uint256 tokenID - ) public view virtual override(ERC1155Upgradeable, ERC1155URIStorageUpgradeable) returns (string memory _uri) { - // All tokens share the same metadata at the moment - _uri = ERC1155URIStorageUpgradeable.uri(getBaseType(tokenID)); - } - - /// UTILS - - /** - * @dev Check if value is below max item index - */ - function _notMaxItem(uint256 tokenID) private pure { - uint128 _count = uint128(tokenID); - ++_count; - } - - /** - * @dev Check if value is below max type index - */ - function _notMaxType(uint256 tokenID) private pure { - uint128 _count = uint128(tokenID >> 128); - ++_count; - } - - /** - * @dev calculate the sum of the elements of an array - */ - function _getSum(uint256[] memory array) internal pure returns (uint256 sum) { - uint256 len = array.length; - for (uint256 i; i < len; ) { - if (array[i] == 0) revert Errors.NotAllowed(); - sum += array[i]; - unchecked { - ++i; - } - } - } - - function _getSingletonArray(uint256 element) private pure returns (uint256[] memory) { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; - } - - // UUPS PROXY - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner { - // solhint-disable-previous-line no-empty-blocks - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - * Assuming 30 available slots (slots cost space, cost gas) - * 1. typeCounter - * 2. owners - * 3. creators - * 4. tokenValues - * 5. maxIndex - */ - uint256[25] private __gap; -} diff --git a/contracts/contracts/interfaces/IHypercertToken.sol b/contracts/contracts/interfaces/IHypercertToken.sol deleted file mode 100644 index 1ed1e34e..00000000 --- a/contracts/contracts/interfaces/IHypercertToken.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -/// @title Interface for hypercert token interactions -/// @author bitbeckers -/// @notice This interface declares the required functionality for a hypercert token -/// @notice This interface does not specify the underlying token type (e.g. 721 or 1155) -interface IHypercertToken { - /** - * AllowAll = Unrestricted - * DisallowAll = Transfers disabled after minting - * FromCreatorOnly = Only the original creator can transfer - */ - /// @dev Transfer restriction policies on hypercerts - enum TransferRestrictions { - AllowAll, - DisallowAll, - FromCreatorOnly - } - - /// @dev Emitted when token with tokenID `claimID` is stored, with external data reference via `uri`. - event ClaimStored(uint256 indexed claimID, string uri, uint256 totalUnits); - - /// @dev Function called to store a claim referenced via `uri` with a maximum number of fractions `units`. - function mintClaim(address account, uint256 units, string memory uri, TransferRestrictions restrictions) external; - - /// @dev Function called to store a claim referenced via `uri` with a set of `fractions`. - /// @dev Fractions are internally summed to total units. - function mintClaimWithFractions( - address account, - uint256 units, - uint256[] memory fractions, - string memory uri, - TransferRestrictions restrictions - ) external; - - /// @dev Function called to split `tokenID` owned by `account` into units declared in `values`. - /// @notice The sum of `values` must equal the current value of `_tokenID`. - function splitFraction(address account, uint256 tokenID, uint256[] memory _values) external; - - /// @dev Function called to merge tokens within `tokenIDs`. - /// @notice Tokens that have been merged are burned. - function mergeFractions(address account, uint256[] memory tokenIDs) external; - - /// @dev Function to burn the token at `tokenID` for `account` - /// @notice Operator must be allowed by `creator` and the token must represent the total amount of available units. - function burnFraction(address account, uint256 tokenID) external; - - /// @dev Returns the `units` held by a (fractional) token at `claimID` - /// @dev If `tokenID` is a base type, the total amount of `units` for the claim is returned. - /// @dev If `tokenID` is a fractional token, the `units` held by the token is returned - function unitsOf(uint256 tokenID) external view returns (uint256 units); - - /// @dev Returns the `units` held by `account` of a (fractional) token at `claimID` - /// @dev If `tokenID` is a base type, the total amount of `units` held by `account` for the claim is returned. - /// @dev If `tokenID` is a fractional token, the `units` held by `account` the token is returned - function unitsOf(address account, uint256 tokenID) external view returns (uint256 units); - - /// @dev Returns the `uri` for metadata of the claim represented by `tokenID` - /// @dev Metadata must conform to { Hypercert Metadata } spec (based on ERC1155 Metadata) - function uri(uint256 tokenID) external view returns (string memory metadata); -} diff --git a/contracts/remappings.txt b/contracts/remappings.txt index bb8f23eb..778ca0f0 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -4,3 +4,4 @@ murky/=lib/murky/src/ oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ oz-contracts/=lib/murky/lib/openzeppelin-contracts/ prb-test/=lib/prb-test/src/ +@hypercerts/protocol/=src/protocol/ diff --git a/contracts/src/AllowlistMinter.sol b/contracts/src/AllowlistMinter.sol deleted file mode 100644 index 576b45ee..00000000 --- a/contracts/src/AllowlistMinter.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import { MerkleProofUpgradeable } from "oz-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; -import { IAllowlist } from "./interfaces/IAllowlist.sol"; - -import { Errors } from "./libs/Errors.sol"; - -/// @title Interface for hypercert token interactions -/// @author bitbeckers -/// @notice This interface declares the required functionality for a hypercert token -/// @notice This interface does not specify the underlying token type (e.g. 721 or 1155) -contract AllowlistMinter is IAllowlist { - event AllowlistCreated(uint256 tokenID, bytes32 root); - event LeafClaimed(uint256 tokenID, bytes32 leaf); - - mapping(uint256 => bytes32) internal merkleRoots; - mapping(uint256 => mapping(bytes32 => bool)) public hasBeenClaimed; - mapping(uint256 => uint256) internal maxUnits; - mapping(uint256 => uint256) internal minted; - - function isAllowedToClaim( - bytes32[] calldata proof, - uint256 claimID, - bytes32 leaf - ) external view returns (bool isAllowed) { - if (merkleRoots[claimID].length == 0) revert Errors.DoesNotExist(); - isAllowed = MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf); - } - - function _createAllowlist(uint256 claimID, bytes32 merkleRoot, uint256 units) internal { - if (merkleRoot == "" || units == 0) revert Errors.Invalid(); - if (merkleRoots[claimID] != "") revert Errors.DuplicateEntry(); - - merkleRoots[claimID] = merkleRoot; - maxUnits[claimID] = units; - emit AllowlistCreated(claimID, merkleRoot); - } - - function _processClaim(bytes32[] calldata proof, uint256 claimID, uint256 amount) internal { - if (merkleRoots[claimID].length == 0) revert Errors.DoesNotExist(); - - bytes32 leaf = _calculateLeaf(msg.sender, amount); - - if (hasBeenClaimed[claimID][leaf]) revert Errors.AlreadyClaimed(); - if ( - !MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf) || - (minted[claimID] + amount) > maxUnits[claimID] - ) revert Errors.Invalid(); - hasBeenClaimed[claimID][leaf] = true; - - emit LeafClaimed(claimID, leaf); - } - - function _calculateLeaf(address account, uint256 amount) internal pure returns (bytes32 leaf) { - leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount)))); - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - * Assuming 30 available slots (slots cost space, cost gas) - * 1. merkleRoots - * 2. hasBeenClaimed - * 3. maxUnits - * 4. minted - */ - uint256[26] private __gap; -} diff --git a/contracts/src/interfaces/IAllowlist.sol b/contracts/src/interfaces/IAllowlist.sol deleted file mode 100644 index fe4a9ec4..00000000 --- a/contracts/src/interfaces/IAllowlist.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -/// @title Interface for allowlist -/// @author bitbeckers -/// @notice This interface declares the required functionality for a hypercert token -/// @notice This interface does not specify the underlying token type (e.g. 721 or 1155) -interface IAllowlist { - function isAllowedToClaim( - bytes32[] calldata proof, - uint256 tokenID, - bytes32 leaf - ) external view returns (bool isAllowed); -} diff --git a/contracts/src/libs/Errors.sol b/contracts/src/libs/Errors.sol deleted file mode 100644 index e19d6b5c..00000000 --- a/contracts/src/libs/Errors.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -/// @author bitbeckers -library Errors { - error AlreadyClaimed(); - error ArraySize(); - error DoesNotExist(); - error DuplicateEntry(); - error Invalid(); - error NotAllowed(); - error NotApprovedOrOwner(); - error TransfersNotAllowed(); - error TypeMismatch(); -} diff --git a/contracts/contracts/AllowlistMinter.sol b/contracts/src/protocol/AllowlistMinter.sol similarity index 100% rename from contracts/contracts/AllowlistMinter.sol rename to contracts/src/protocol/AllowlistMinter.sol diff --git a/contracts/src/HypercertMinter.sol b/contracts/src/protocol/HypercertMinter.sol similarity index 100% rename from contracts/src/HypercertMinter.sol rename to contracts/src/protocol/HypercertMinter.sol diff --git a/contracts/src/HypercertTrader.sol b/contracts/src/protocol/HypercertTrader.sol similarity index 100% rename from contracts/src/HypercertTrader.sol rename to contracts/src/protocol/HypercertTrader.sol diff --git a/contracts/src/SemiFungible1155.sol b/contracts/src/protocol/SemiFungible1155.sol similarity index 100% rename from contracts/src/SemiFungible1155.sol rename to contracts/src/protocol/SemiFungible1155.sol diff --git a/contracts/contracts/interfaces/IAllowlist.sol b/contracts/src/protocol/interfaces/IAllowlist.sol similarity index 100% rename from contracts/contracts/interfaces/IAllowlist.sol rename to contracts/src/protocol/interfaces/IAllowlist.sol diff --git a/contracts/src/interfaces/IHypercertToken.sol b/contracts/src/protocol/interfaces/IHypercertToken.sol similarity index 100% rename from contracts/src/interfaces/IHypercertToken.sol rename to contracts/src/protocol/interfaces/IHypercertToken.sol diff --git a/contracts/src/interfaces/IHypercertTrader.sol b/contracts/src/protocol/interfaces/IHypercertTrader.sol similarity index 100% rename from contracts/src/interfaces/IHypercertTrader.sol rename to contracts/src/protocol/interfaces/IHypercertTrader.sol diff --git a/contracts/contracts/libs/Errors.sol b/contracts/src/protocol/libs/Errors.sol similarity index 100% rename from contracts/contracts/libs/Errors.sol rename to contracts/src/protocol/libs/Errors.sol diff --git a/contracts/test/foundry/AllowlistMinter.t.sol b/contracts/test/foundry/AllowlistMinter.t.sol index 7ccb3aa9..8aecd6e7 100644 --- a/contracts/test/foundry/AllowlistMinter.t.sol +++ b/contracts/test/foundry/AllowlistMinter.t.sol @@ -5,8 +5,8 @@ import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; -import { AllowlistMinter } from "../../src/AllowlistMinter.sol"; -import { Errors } from "../../src/libs/Errors.sol"; +import { AllowlistMinter } from "@hypercerts/protocol/AllowlistMinter.sol"; +import { Errors } from "@hypercerts/protocol/libs/Errors.sol"; import { Merkle } from "murky/Merkle.sol"; diff --git a/contracts/test/foundry/HypercertMinter.batchminting.t.sol b/contracts/test/foundry/HypercertMinter.batchminting.t.sol index b5349919..d58f0276 100644 --- a/contracts/test/foundry/HypercertMinter.batchminting.t.sol +++ b/contracts/test/foundry/HypercertMinter.batchminting.t.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.16; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; //solhint-disable-next-line max-line-length import { ERC1155HolderUpgradeable } from "openzeppelin-contracts-upgradeable/contracts/token/ERC1155/utils/ERC1155HolderUpgradeable.sol"; import { Merkle } from "murky/Merkle.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; contract BatchMintingHelper is Merkle, ERC1155HolderUpgradeable { event BatchValueTransfer(uint256[] claimIDs, uint256[] fromTokenIDs, uint256[] toTokenIDs, uint256[] values); diff --git a/contracts/test/foundry/HypercertMinter.pausable.t.sol b/contracts/test/foundry/HypercertMinter.pausable.t.sol index 716c8c5f..ae849161 100644 --- a/contracts/test/foundry/HypercertMinter.pausable.t.sol +++ b/contracts/test/foundry/HypercertMinter.pausable.t.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.16; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; contract PausableTestHelper { /** diff --git a/contracts/test/foundry/HypercertMinter.t.sol b/contracts/test/foundry/HypercertMinter.t.sol index ad15aada..6647ffc0 100644 --- a/contracts/test/foundry/HypercertMinter.t.sol +++ b/contracts/test/foundry/HypercertMinter.t.sol @@ -5,8 +5,8 @@ import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; import { Merkle } from "murky/Merkle.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; contract MinterTestHelper { event ClaimStored(uint256 indexed claimID, string uri, uint256 totalUnits); diff --git a/contracts/test/foundry/HypercertMinter.transfers.t.sol b/contracts/test/foundry/HypercertMinter.transfers.t.sol index 7c74edb1..f4b3ff9b 100644 --- a/contracts/test/foundry/HypercertMinter.transfers.t.sol +++ b/contracts/test/foundry/HypercertMinter.transfers.t.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.16; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; -import { Errors } from "../../src/libs/Errors.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { Errors } from "@hypercerts/protocol/libs/Errors.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; /// @dev Testing transfer restrictions on hypercerts contract HypercertMinterTransferTest is PRBTest, StdCheats, StdUtils { diff --git a/contracts/test/foundry/HypercertTrader.admin.t.sol b/contracts/test/foundry/HypercertTrader.admin.t.sol index 3b7ee0eb..817a6bb9 100644 --- a/contracts/test/foundry/HypercertTrader.admin.t.sol +++ b/contracts/test/foundry/HypercertTrader.admin.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import { HypercertTrader } from "../../src/HypercertTrader.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; diff --git a/contracts/test/foundry/HypercertTrader.offers.t.sol b/contracts/test/foundry/HypercertTrader.offers.t.sol index 6901b314..a348e9ed 100644 --- a/contracts/test/foundry/HypercertTrader.offers.t.sol +++ b/contracts/test/foundry/HypercertTrader.offers.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import { HypercertTrader } from "../../src/HypercertTrader.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; diff --git a/contracts/test/foundry/HypercertTrader.sales.t.sol b/contracts/test/foundry/HypercertTrader.sales.t.sol index 07c0216a..48f65355 100644 --- a/contracts/test/foundry/HypercertTrader.sales.t.sol +++ b/contracts/test/foundry/HypercertTrader.sales.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import { HypercertTrader } from "../../src/HypercertTrader.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; diff --git a/contracts/test/foundry/PerformanceTesting.t.sol b/contracts/test/foundry/PerformanceTesting.t.sol index 7ed5b2df..7b2a71d3 100644 --- a/contracts/test/foundry/PerformanceTesting.t.sol +++ b/contracts/test/foundry/PerformanceTesting.t.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.16; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; -import { HypercertMinter } from "../../src/HypercertMinter.sol"; +import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; import { Merkle } from "murky/Merkle.sol"; -import { IHypercertToken } from "../../src/interfaces/IHypercertToken.sol"; +import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; // forge test -vv --match-path test/foundry/PerformanceTesting.t.sol diff --git a/contracts/test/foundry/SemiFungibleHelper.sol b/contracts/test/foundry/SemiFungibleHelper.sol index 3a702e8b..7555bc9d 100644 --- a/contracts/test/foundry/SemiFungibleHelper.sol +++ b/contracts/test/foundry/SemiFungibleHelper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import { SemiFungible1155 } from "../../src/SemiFungible1155.sol"; +import { SemiFungible1155 } from "@hypercerts/protocol/SemiFungible1155.sol"; import { PRBTest } from "prb-test/PRBTest.sol"; import { StdCheats } from "forge-std/StdCheats.sol"; import { StdUtils } from "forge-std/StdUtils.sol"; From d44bdd1eb555214bff3e34f466c456edc778bcd1 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Wed, 25 Oct 2023 16:55:30 +0200 Subject: [PATCH 02/15] chore(prep): tests to protocol dir and remappings --- contracts/remappings.txt | 1 + contracts/test/foundry/{ => protocol}/AllowlistMinter.t.sol | 0 contracts/test/foundry/{ => protocol}/Bitshifting.t.sol | 0 .../foundry/{ => protocol}/HypercertMinter.batchminting.t.sol | 0 .../test/foundry/{ => protocol}/HypercertMinter.pausable.t.sol | 0 contracts/test/foundry/{ => protocol}/HypercertMinter.t.sol | 0 .../test/foundry/{ => protocol}/HypercertMinter.transfers.t.sol | 0 .../test/foundry/{ => protocol}/HypercertTrader.admin.t.sol | 0 .../test/foundry/{ => protocol}/HypercertTrader.offers.t.sol | 0 .../test/foundry/{ => protocol}/HypercertTrader.sales.t.sol | 0 contracts/test/foundry/{ => protocol}/PerformanceTesting.t.sol | 0 .../foundry/{ => protocol}/SemiFungible1155.allowances.t.sol | 0 .../test/foundry/{ => protocol}/SemiFungible1155.burning.t.sol | 0 .../test/foundry/{ => protocol}/SemiFungible1155.minting.t.sol | 0 contracts/test/foundry/{ => protocol}/SemiFungible1155.t.sol | 0 .../test/foundry/{ => protocol}/SemiFungible1155.transfers.t.sol | 0 contracts/test/foundry/{ => protocol}/SemiFungible1155.units.sol | 0 contracts/test/foundry/{ => protocol}/SemiFungibleHelper.sol | 0 18 files changed, 1 insertion(+) rename contracts/test/foundry/{ => protocol}/AllowlistMinter.t.sol (100%) rename contracts/test/foundry/{ => protocol}/Bitshifting.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertMinter.batchminting.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertMinter.pausable.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertMinter.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertMinter.transfers.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertTrader.admin.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertTrader.offers.t.sol (100%) rename contracts/test/foundry/{ => protocol}/HypercertTrader.sales.t.sol (100%) rename contracts/test/foundry/{ => protocol}/PerformanceTesting.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.allowances.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.burning.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.minting.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.transfers.t.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungible1155.units.sol (100%) rename contracts/test/foundry/{ => protocol}/SemiFungibleHelper.sol (100%) diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 778ca0f0..2d7e0dcb 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -5,3 +5,4 @@ oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ oz-contracts/=lib/murky/lib/openzeppelin-contracts/ prb-test/=lib/prb-test/src/ @hypercerts/protocol/=src/protocol/ +@hypercerts/marketplace/=src/marketplace/ \ No newline at end of file diff --git a/contracts/test/foundry/AllowlistMinter.t.sol b/contracts/test/foundry/protocol/AllowlistMinter.t.sol similarity index 100% rename from contracts/test/foundry/AllowlistMinter.t.sol rename to contracts/test/foundry/protocol/AllowlistMinter.t.sol diff --git a/contracts/test/foundry/Bitshifting.t.sol b/contracts/test/foundry/protocol/Bitshifting.t.sol similarity index 100% rename from contracts/test/foundry/Bitshifting.t.sol rename to contracts/test/foundry/protocol/Bitshifting.t.sol diff --git a/contracts/test/foundry/HypercertMinter.batchminting.t.sol b/contracts/test/foundry/protocol/HypercertMinter.batchminting.t.sol similarity index 100% rename from contracts/test/foundry/HypercertMinter.batchminting.t.sol rename to contracts/test/foundry/protocol/HypercertMinter.batchminting.t.sol diff --git a/contracts/test/foundry/HypercertMinter.pausable.t.sol b/contracts/test/foundry/protocol/HypercertMinter.pausable.t.sol similarity index 100% rename from contracts/test/foundry/HypercertMinter.pausable.t.sol rename to contracts/test/foundry/protocol/HypercertMinter.pausable.t.sol diff --git a/contracts/test/foundry/HypercertMinter.t.sol b/contracts/test/foundry/protocol/HypercertMinter.t.sol similarity index 100% rename from contracts/test/foundry/HypercertMinter.t.sol rename to contracts/test/foundry/protocol/HypercertMinter.t.sol diff --git a/contracts/test/foundry/HypercertMinter.transfers.t.sol b/contracts/test/foundry/protocol/HypercertMinter.transfers.t.sol similarity index 100% rename from contracts/test/foundry/HypercertMinter.transfers.t.sol rename to contracts/test/foundry/protocol/HypercertMinter.transfers.t.sol diff --git a/contracts/test/foundry/HypercertTrader.admin.t.sol b/contracts/test/foundry/protocol/HypercertTrader.admin.t.sol similarity index 100% rename from contracts/test/foundry/HypercertTrader.admin.t.sol rename to contracts/test/foundry/protocol/HypercertTrader.admin.t.sol diff --git a/contracts/test/foundry/HypercertTrader.offers.t.sol b/contracts/test/foundry/protocol/HypercertTrader.offers.t.sol similarity index 100% rename from contracts/test/foundry/HypercertTrader.offers.t.sol rename to contracts/test/foundry/protocol/HypercertTrader.offers.t.sol diff --git a/contracts/test/foundry/HypercertTrader.sales.t.sol b/contracts/test/foundry/protocol/HypercertTrader.sales.t.sol similarity index 100% rename from contracts/test/foundry/HypercertTrader.sales.t.sol rename to contracts/test/foundry/protocol/HypercertTrader.sales.t.sol diff --git a/contracts/test/foundry/PerformanceTesting.t.sol b/contracts/test/foundry/protocol/PerformanceTesting.t.sol similarity index 100% rename from contracts/test/foundry/PerformanceTesting.t.sol rename to contracts/test/foundry/protocol/PerformanceTesting.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.allowances.t.sol b/contracts/test/foundry/protocol/SemiFungible1155.allowances.t.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.allowances.t.sol rename to contracts/test/foundry/protocol/SemiFungible1155.allowances.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.burning.t.sol b/contracts/test/foundry/protocol/SemiFungible1155.burning.t.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.burning.t.sol rename to contracts/test/foundry/protocol/SemiFungible1155.burning.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.minting.t.sol b/contracts/test/foundry/protocol/SemiFungible1155.minting.t.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.minting.t.sol rename to contracts/test/foundry/protocol/SemiFungible1155.minting.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.t.sol b/contracts/test/foundry/protocol/SemiFungible1155.t.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.t.sol rename to contracts/test/foundry/protocol/SemiFungible1155.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.transfers.t.sol b/contracts/test/foundry/protocol/SemiFungible1155.transfers.t.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.transfers.t.sol rename to contracts/test/foundry/protocol/SemiFungible1155.transfers.t.sol diff --git a/contracts/test/foundry/SemiFungible1155.units.sol b/contracts/test/foundry/protocol/SemiFungible1155.units.sol similarity index 100% rename from contracts/test/foundry/SemiFungible1155.units.sol rename to contracts/test/foundry/protocol/SemiFungible1155.units.sol diff --git a/contracts/test/foundry/SemiFungibleHelper.sol b/contracts/test/foundry/protocol/SemiFungibleHelper.sol similarity index 100% rename from contracts/test/foundry/SemiFungibleHelper.sol rename to contracts/test/foundry/protocol/SemiFungibleHelper.sol From 8573f3fa86b947be84a897d594228294f22f9286 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Wed, 25 Oct 2023 17:03:20 +0200 Subject: [PATCH 03/15] chore(license): MIT license --- contracts/LICENSE.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/LICENSE.md b/contracts/LICENSE.md index 054bdbe3..1234b4cf 100644 --- a/contracts/LICENSE.md +++ b/contracts/LICENSE.md @@ -1,6 +1,11 @@ MIT License -Copyright (c) 2022 Paul Razvan Berg +Copyright (c) 2022 Paul Razvan Berg for inital template: https://github.com/paulrberg/foundry-template + +Copyright (c) 2022 LooksRare for portions of marketplace: https://github.com/LooksRare/contracts-exchange-v2 commit: +7fca565 + +Copyright (c) 2022 Hypercerts Foundation: Hypercerts protocol and marketplace. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From 02dcf39688d40f30f4a569de2f666c2d417adc64 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 10:56:38 +0200 Subject: [PATCH 04/15] feat(marketplace): inject, build, refactor, test --- contracts/foundry.toml | 15 +- contracts/hardhat.config.ts | 4 +- .../.eslintrc | 31 +- contracts/package.json | 4 + contracts/remappings.txt | 5 +- .../BatchOrderTypehashRegistry.sol | 54 ++ .../CreatorFeeManagerWithRebates.sol | 94 ++ .../CreatorFeeManagerWithRoyalties.sol | 96 ++ contracts/src/marketplace/CurrencyManager.sol | 37 + .../src/marketplace/ExecutionManager.sol | 305 ++++++ .../src/marketplace/InheritedStrategy.sol | 88 ++ .../src/marketplace/LooksRareProtocol.sol | 626 +++++++++++++ contracts/src/marketplace/NonceManager.sol | 115 +++ .../src/marketplace/ProtocolFeeRecipient.sol | 54 ++ contracts/src/marketplace/StrategyManager.sol | 126 +++ contracts/src/marketplace/TransferManager.sol | 376 ++++++++ .../src/marketplace/TransferSelectorNFT.sol | 69 ++ .../constants/AssemblyConstants.sol | 42 + .../constants/NumericConstants.sol | 14 + .../constants/ValidationCodeConstants.sol | 313 +++++++ .../src/marketplace/enums/CollectionType.sol | 13 + contracts/src/marketplace/enums/QuoteType.sol | 10 + .../marketplace/errors/ChainlinkErrors.sol | 41 + .../src/marketplace/errors/SharedErrors.sol | 65 ++ .../executionStrategies/BaseStrategy.sol | 46 + .../BaseStrategyChainlinkPriceLatency.sol | 33 + .../StrategyChainlinkUSDDynamicAsk.sol | 159 ++++ .../StrategyCollectionOffer.sol | 138 +++ .../StrategyDutchAuction.sol | 104 +++ .../StrategyItemIdsRange.sol | 116 +++ .../marketplace/helpers/OrderValidatorV2A.sol | 865 ++++++++++++++++++ .../marketplace/helpers/ProtocolHelpers.sol | 89 ++ .../interfaces/ICreatorFeeManager.sol | 36 + .../interfaces/ICurrencyManager.sol | 15 + .../interfaces/IExecutionManager.sol | 53 ++ .../interfaces/IImmutableCreate2Factory.sol | 9 + .../interfaces/ILooksRareProtocol.sol | 176 ++++ .../marketplace/interfaces/INonceManager.sol | 40 + .../interfaces/IRoyaltyFeeRegistry.sol | 20 + .../src/marketplace/interfaces/IStrategy.sol | 30 + .../interfaces/IStrategyManager.sol | 80 ++ .../interfaces/ITransferManager.sol | 84 ++ .../libraries/CurrencyValidator.sol | 34 + .../MerkleProofCalldataWithNodes.sol | 60 ++ .../OpenZeppelin/MerkleProofMemory.sol | 51 ++ .../marketplace/libraries/OrderStructs.sol | 167 ++++ .../BatchMakerCollectionOrders.t.sol | 114 +++ .../marketplace/BatchMakerOrders.t.sol | 413 +++++++++ .../BatchOrderTypehashRegistry.t.sol | 143 +++ .../marketplace/BundleTransactions.t.sol | 290 ++++++ .../CreatorFeeManagerWithRebates.t.sol | 260 ++++++ .../CreatorFeeManagerWithRoyalties.t.sol | 402 ++++++++ .../foundry/marketplace/CurrencyManager.t.sol | 42 + .../DelegationRecipientsTaker.t.sol | 181 ++++ .../marketplace/DomainSeparatorUpdates.t.sol | 111 +++ .../marketplace/ExecutionManager.t.sol | 296 ++++++ .../foundry/marketplace/GasGriefing.t.sol | 147 +++ .../foundry/marketplace/InitialStates.t.sol | 55 ++ .../marketplace/LooksRareProtocol.t.sol | 292 ++++++ .../marketplace/NonceInvalidation.t.sol | 374 ++++++++ .../marketplace/OrderValidatorV2A.t.sol | 412 +++++++++ .../foundry/marketplace/ProtocolBase.t.sol | 274 ++++++ .../marketplace/ProtocolFeeRecipient.t.sol | 111 +++ .../test/foundry/marketplace/Sandbox.t.sol | 154 ++++ .../marketplace/SignaturesEIP2098.t.sol | 66 ++ .../SignaturesERC1271WalletForERC1155.t.sol | 481 ++++++++++ .../SignaturesERC1271WalletForERC721.t.sol | 313 +++++++ .../marketplace/SignaturesRevertions.t.sol | 192 ++++ .../marketplace/StandardTransactions.t.sol | 555 +++++++++++ .../foundry/marketplace/StrategyManager.t.sol | 239 +++++ .../foundry/marketplace/TransferManager.t.sol | 535 +++++++++++ ...ifyOrderTimestampValidityEquivalence.t.sol | 87 ++ .../Chainlink/USDDynamicAskOrders.t.sol | 483 ++++++++++ .../CollectionOffers.t.sol | 458 ++++++++++ .../DutchAuctionOrders.t.sol | 436 +++++++++ .../ItemIdsRangeOrders.t.sol | 482 ++++++++++ .../MultiFillCollectionOrders.t.sol | 194 ++++ .../foundry/marketplace/utils/BytesLib.sol | 73 ++ .../marketplace/utils/EIP712MerkleTree.sol | 121 +++ .../marketplace/utils/ERC1271Wallet.sol | 22 + .../foundry/marketplace/utils/GasGriefer.sol | 15 + .../utils/MaliciousERC1271Wallet.sol | 68 ++ ...MaliciousIsValidSignatureERC1271Wallet.sol | 20 + ...aliciousOnERC1155ReceivedERC1271Wallet.sol | 38 + ...C1155ReceivedTheThirdTimeERC1271Wallet.sol | 24 + .../foundry/marketplace/utils/MathLib.sol | 50 + .../marketplace/utils/MerkleWithPosition.sol | 152 +++ .../marketplace/utils/MockOrderGenerator.sol | 147 +++ .../marketplace/utils/ProtocolHelpers.sol | 109 +++ .../StrategyTestMultiFillCollectionOrder.sol | 77 ++ .../foundry/marketplace/utils/TestHelpers.sol | 26 + .../marketplace/utils/TestParameters.sol | 58 ++ .../test/mock/MockChainlinkAggregator.sol | 31 + contracts/test/mock/MockERC1155.sol | 29 + .../mock/MockERC1155SupportsNoInterface.sol | 10 + .../mock/MockERC1155WithoutAnyBalanceOf.sol | 21 + .../mock/MockERC1155WithoutBalanceOfBatch.sol | 10 + .../MockERC1155WithoutIsApprovedForAll.sol | 21 + contracts/test/mock/MockERC20.sol | 10 + contracts/test/mock/MockERC721.sol | 31 + .../mock/MockERC721SupportsNoInterface.sol | 10 + .../test/mock/MockERC721WithRoyalties.sol | 53 ++ .../test/mock/MockRoyaltyFeeRegistry.sol | 97 ++ contracts/test/mock/MockSmartWallet.sol | 65 ++ pnpm-lock.yaml | 89 ++ 105 files changed, 15046 insertions(+), 25 deletions(-) create mode 100644 contracts/src/marketplace/BatchOrderTypehashRegistry.sol create mode 100644 contracts/src/marketplace/CreatorFeeManagerWithRebates.sol create mode 100644 contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol create mode 100644 contracts/src/marketplace/CurrencyManager.sol create mode 100644 contracts/src/marketplace/ExecutionManager.sol create mode 100644 contracts/src/marketplace/InheritedStrategy.sol create mode 100644 contracts/src/marketplace/LooksRareProtocol.sol create mode 100644 contracts/src/marketplace/NonceManager.sol create mode 100644 contracts/src/marketplace/ProtocolFeeRecipient.sol create mode 100644 contracts/src/marketplace/StrategyManager.sol create mode 100644 contracts/src/marketplace/TransferManager.sol create mode 100644 contracts/src/marketplace/TransferSelectorNFT.sol create mode 100644 contracts/src/marketplace/constants/AssemblyConstants.sol create mode 100644 contracts/src/marketplace/constants/NumericConstants.sol create mode 100644 contracts/src/marketplace/constants/ValidationCodeConstants.sol create mode 100644 contracts/src/marketplace/enums/CollectionType.sol create mode 100644 contracts/src/marketplace/enums/QuoteType.sol create mode 100644 contracts/src/marketplace/errors/ChainlinkErrors.sol create mode 100644 contracts/src/marketplace/errors/SharedErrors.sol create mode 100644 contracts/src/marketplace/executionStrategies/BaseStrategy.sol create mode 100644 contracts/src/marketplace/executionStrategies/Chainlink/BaseStrategyChainlinkPriceLatency.sol create mode 100644 contracts/src/marketplace/executionStrategies/Chainlink/StrategyChainlinkUSDDynamicAsk.sol create mode 100644 contracts/src/marketplace/executionStrategies/StrategyCollectionOffer.sol create mode 100644 contracts/src/marketplace/executionStrategies/StrategyDutchAuction.sol create mode 100644 contracts/src/marketplace/executionStrategies/StrategyItemIdsRange.sol create mode 100644 contracts/src/marketplace/helpers/OrderValidatorV2A.sol create mode 100644 contracts/src/marketplace/helpers/ProtocolHelpers.sol create mode 100644 contracts/src/marketplace/interfaces/ICreatorFeeManager.sol create mode 100644 contracts/src/marketplace/interfaces/ICurrencyManager.sol create mode 100644 contracts/src/marketplace/interfaces/IExecutionManager.sol create mode 100644 contracts/src/marketplace/interfaces/IImmutableCreate2Factory.sol create mode 100644 contracts/src/marketplace/interfaces/ILooksRareProtocol.sol create mode 100644 contracts/src/marketplace/interfaces/INonceManager.sol create mode 100644 contracts/src/marketplace/interfaces/IRoyaltyFeeRegistry.sol create mode 100644 contracts/src/marketplace/interfaces/IStrategy.sol create mode 100644 contracts/src/marketplace/interfaces/IStrategyManager.sol create mode 100644 contracts/src/marketplace/interfaces/ITransferManager.sol create mode 100644 contracts/src/marketplace/libraries/CurrencyValidator.sol create mode 100644 contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol create mode 100644 contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofMemory.sol create mode 100644 contracts/src/marketplace/libraries/OrderStructs.sol create mode 100644 contracts/test/foundry/marketplace/BatchMakerCollectionOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/BatchMakerOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/BatchOrderTypehashRegistry.t.sol create mode 100644 contracts/test/foundry/marketplace/BundleTransactions.t.sol create mode 100644 contracts/test/foundry/marketplace/CreatorFeeManagerWithRebates.t.sol create mode 100644 contracts/test/foundry/marketplace/CreatorFeeManagerWithRoyalties.t.sol create mode 100644 contracts/test/foundry/marketplace/CurrencyManager.t.sol create mode 100644 contracts/test/foundry/marketplace/DelegationRecipientsTaker.t.sol create mode 100644 contracts/test/foundry/marketplace/DomainSeparatorUpdates.t.sol create mode 100644 contracts/test/foundry/marketplace/ExecutionManager.t.sol create mode 100644 contracts/test/foundry/marketplace/GasGriefing.t.sol create mode 100644 contracts/test/foundry/marketplace/InitialStates.t.sol create mode 100644 contracts/test/foundry/marketplace/LooksRareProtocol.t.sol create mode 100644 contracts/test/foundry/marketplace/NonceInvalidation.t.sol create mode 100644 contracts/test/foundry/marketplace/OrderValidatorV2A.t.sol create mode 100644 contracts/test/foundry/marketplace/ProtocolBase.t.sol create mode 100644 contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol create mode 100644 contracts/test/foundry/marketplace/Sandbox.t.sol create mode 100644 contracts/test/foundry/marketplace/SignaturesEIP2098.t.sol create mode 100644 contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC1155.t.sol create mode 100644 contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC721.t.sol create mode 100644 contracts/test/foundry/marketplace/SignaturesRevertions.t.sol create mode 100644 contracts/test/foundry/marketplace/StandardTransactions.t.sol create mode 100644 contracts/test/foundry/marketplace/StrategyManager.t.sol create mode 100644 contracts/test/foundry/marketplace/TransferManager.t.sol create mode 100644 contracts/test/foundry/marketplace/assembly/VerifyOrderTimestampValidityEquivalence.t.sol create mode 100644 contracts/test/foundry/marketplace/executionStrategies/Chainlink/USDDynamicAskOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/executionStrategies/CollectionOffers.t.sol create mode 100644 contracts/test/foundry/marketplace/executionStrategies/DutchAuctionOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/executionStrategies/ItemIdsRangeOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/executionStrategies/MultiFillCollectionOrders.t.sol create mode 100644 contracts/test/foundry/marketplace/utils/BytesLib.sol create mode 100644 contracts/test/foundry/marketplace/utils/EIP712MerkleTree.sol create mode 100644 contracts/test/foundry/marketplace/utils/ERC1271Wallet.sol create mode 100644 contracts/test/foundry/marketplace/utils/GasGriefer.sol create mode 100644 contracts/test/foundry/marketplace/utils/MaliciousERC1271Wallet.sol create mode 100644 contracts/test/foundry/marketplace/utils/MaliciousIsValidSignatureERC1271Wallet.sol create mode 100644 contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol create mode 100644 contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol create mode 100644 contracts/test/foundry/marketplace/utils/MathLib.sol create mode 100644 contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol create mode 100644 contracts/test/foundry/marketplace/utils/MockOrderGenerator.sol create mode 100644 contracts/test/foundry/marketplace/utils/ProtocolHelpers.sol create mode 100644 contracts/test/foundry/marketplace/utils/StrategyTestMultiFillCollectionOrder.sol create mode 100644 contracts/test/foundry/marketplace/utils/TestHelpers.sol create mode 100644 contracts/test/foundry/marketplace/utils/TestParameters.sol create mode 100644 contracts/test/mock/MockChainlinkAggregator.sol create mode 100644 contracts/test/mock/MockERC1155.sol create mode 100644 contracts/test/mock/MockERC1155SupportsNoInterface.sol create mode 100644 contracts/test/mock/MockERC1155WithoutAnyBalanceOf.sol create mode 100644 contracts/test/mock/MockERC1155WithoutBalanceOfBatch.sol create mode 100644 contracts/test/mock/MockERC1155WithoutIsApprovedForAll.sol create mode 100644 contracts/test/mock/MockERC20.sol create mode 100644 contracts/test/mock/MockERC721.sol create mode 100644 contracts/test/mock/MockERC721SupportsNoInterface.sol create mode 100644 contracts/test/mock/MockERC721WithRoyalties.sol create mode 100644 contracts/test/mock/MockRoyaltyFeeRegistry.sol create mode 100644 contracts/test/mock/MockSmartWallet.sol diff --git a/contracts/foundry.toml b/contracts/foundry.toml index bf1b6bfc..4b146c01 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -1,19 +1,26 @@ # Full reference https://github.com/foundry-rs/foundry/tree/master/config [profile.default] -auto_detect_solc = false +auto_detect_solc = true +allow_paths = ["../node_modules", "lib"] bytecode_hash = "none" +force = false fuzz = { runs = 1025 } gas_reports = ["*"] -libs = ["lib"] +libraries = [] +libs = ["node_modules", "lib"] optimizer = true optimizer_runs = 5_000 out = "out" -solc = "0.8.16" src = "src" test = "test/foundry" - +no_match_test = "testCannotExecuteOrderIfInvalidUserGlobal" [profile.ci] fuzz = { runs = 1024 } verbosity = 1 + +[rpc_endpoints] +mainnet = "${MAINNET_RPC_URL}" +goerli = "${GOERLI_RPC_URL}" +optimism = "${OPTIMISM_RPC_URL}" \ No newline at end of file diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index a8ec0a3c..36d33635 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -37,7 +37,7 @@ dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); const mnemonic = requireEnv(process.env.MNEMONIC, "MNEMONIC"); const mnemonic_celo = requireEnv(process.env.MNEMONIC_CELO, "MNEMONIC_CELO"); const infuraApiKey = requireEnv(process.env.INFURA_API_KEY, "INFURA_API_KEY"); -const alchemyOptimismUrl = requireEnv(process.env.ALCHEMY_OPTIMISM_URL, "ALCHEMY_OPTIMISM_URL"); +const optimismRpcUrl = requireEnv(process.env.OPTIMISM_RPC_URL, "OPTIMISM_RPC_URL"); const etherscanApiKey = requireEnv(process.env.ETHERSCAN_API_KEY, "ETHERSCAN_API_KEY"); const optimisticEtherscanApiKey = requireEnv(process.env.OPTIMISTIC_ETHERSCAN_API_KEY, "OPTIMISTIC_ETHERSCAN_API_KEY"); @@ -147,7 +147,7 @@ const config = { "optimism-goerli": getChainConfig("optimism-goerli"), "optimism-mainnet": { ...getChainConfig("optimism-mainnet"), - url: alchemyOptimismUrl, + url: optimismRpcUrl, }, }, paths: { diff --git a/contracts/lib/openzeppelin-contracts-upgradeable/.eslintrc b/contracts/lib/openzeppelin-contracts-upgradeable/.eslintrc index 095d2754..22fb715f 100644 --- a/contracts/lib/openzeppelin-contracts-upgradeable/.eslintrc +++ b/contracts/lib/openzeppelin-contracts-upgradeable/.eslintrc @@ -1,35 +1,30 @@ { - "extends" : [ - "standard" - ], - "plugins": [ - "mocha" - ], + "extends": ["standard"], + "plugins": ["mocha"], "env": { - "browser" : true, - "node" : true, - "mocha" : true, - "jest" : true, + "browser": true, + "node": true, + "mocha": true, + "jest": true }, - "globals" : { + "globals": { "artifacts": false, "contract": false, "assert": false, "web3": false, "usePlugin": false, - "extendEnvironment": false, + "extendEnvironment": false }, "rules": { - // Strict mode "strict": ["error", "global"], // Code style "array-bracket-spacing": ["off"], - "camelcase": ["error", {"properties": "always"}], + "camelcase": ["error", { "properties": "always" }], "comma-dangle": ["error", "always-multiline"], - "comma-spacing": ["error", {"before": false, "after": true}], - "dot-notation": ["error", {"allowKeywords": true, "allowPattern": ""}], + "comma-spacing": ["error", { "before": false, "after": true }], + "dot-notation": ["error", { "allowKeywords": true, "allowPattern": "" }], "eol-last": ["error", "always"], "eqeqeq": ["error", "smart"], "generator-star-spacing": ["error", "before"], @@ -40,7 +35,7 @@ "no-dupe-args": "error", "no-dupe-keys": "error", "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], - "no-redeclare": ["error", {"builtinGlobals": true}], + "no-redeclare": ["error", { "builtinGlobals": true }], "no-trailing-spaces": ["error", { "skipBlankLines": false }], "no-undef": "error", "no-use-before-define": "off", @@ -54,7 +49,7 @@ "mocha/no-exclusive-tests": ["error"], "promise/always-return": "off", - "promise/avoid-new": "off", + "promise/avoid-new": "off" }, "parserOptions": { "ecmaVersion": 2020 diff --git a/contracts/package.json b/contracts/package.json index a54becd4..21473b24 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -30,17 +30,20 @@ "contracts" ], "devDependencies": { + "@chainlink/contracts": "^0.8.0", "@commitlint/cli": "^17.1.2", "@commitlint/config-conventional": "^17.1.0", "@dlsl/hardhat-markup": "^1.0.0-rc.7", "@ethersproject/abi": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/providers": "^5.7.2", + "@looksrare/contracts-libs": "^3.4.0", "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomicfoundation/hardhat-network-helpers": "^1.0.7", "@nomicfoundation/hardhat-toolbox": "^2.0.0", "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-etherscan": "^3.1.3", + "@openzeppelin/contracts": "<5.0.0", "@openzeppelin/hardhat-defender": "^1.8.2", "@openzeppelin/hardhat-upgrades": "^1.28", "@primitivefi/hardhat-dodoc": "^0.2.3", @@ -82,6 +85,7 @@ "solhint": "^3.6.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.2", + "solmate": "^6.2.0", "ts-node": "^10.9.1", "typechain": "^8.3.1", "typescript": "^4.9.4" diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 2d7e0dcb..cd70bc53 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -5,4 +5,7 @@ oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ oz-contracts/=lib/murky/lib/openzeppelin-contracts/ prb-test/=lib/prb-test/src/ @hypercerts/protocol/=src/protocol/ -@hypercerts/marketplace/=src/marketplace/ \ No newline at end of file +@hypercerts/marketplace/=src/marketplace/ +@looksrare/=node_modules/@looksrare/ +hardhat/=node_modules/hardhat/ +solmate/=node_modules/solmate/ \ No newline at end of file diff --git a/contracts/src/marketplace/BatchOrderTypehashRegistry.sol b/contracts/src/marketplace/BatchOrderTypehashRegistry.sol new file mode 100644 index 00000000..13851212 --- /dev/null +++ b/contracts/src/marketplace/BatchOrderTypehashRegistry.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Shared errors +import { MerkleProofTooLarge } from "./errors/SharedErrors.sol"; + +/** + * @title BatchOrderTypehashRegistry + * @notice The contract generates the batch order hash that is used to compute the digest for signature verification. + * @author LooksRare protocol team (👀,💎) + */ +contract BatchOrderTypehashRegistry { + /** + * @notice This function returns the hash of the concatenation of batch order type hash and merkle root. + * @param root Merkle root + * @param proofLength Merkle proof length + * @return batchOrderHash The batch order hash + */ + function hashBatchOrder(bytes32 root, uint256 proofLength) public pure returns (bytes32 batchOrderHash) { + batchOrderHash = keccak256(abi.encode(_getBatchOrderTypehash(proofLength), root)); + } + + /** + * @dev It looks like this for each height + * height == 1: BatchOrder(Maker[2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) + * height == 2: BatchOrder(Maker[2][2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) + * height == n: BatchOrder(Maker[2]...[2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) + */ + function _getBatchOrderTypehash(uint256 height) internal pure returns (bytes32 typehash) { + if (height == 1) { + typehash = hex"9661287f7a4aa4867db46a2453ee15bebac4e8fc25667a58718da658f15de643"; + } else if (height == 2) { + typehash = hex"a54ab330ea9e1dfccee2b86f3666989e7fbd479704416c757c8de8e820142a08"; + } else if (height == 3) { + typehash = hex"93390f5d45ede9dea305f16aec86b2472af4f823851637f1b7019ad0775cea49"; + } else if (height == 4) { + typehash = hex"9dda2c8358da895e43d574bb15954ce5727b22e923a2d8f28261f297bce42f0b"; + } else if (height == 5) { + typehash = hex"92dc717124e161262f9d10c7079e7d54dc51271893fba54aa4a0f270fecdcc98"; + } else if (height == 6) { + typehash = hex"ce02aee5a7a35d40d974463c4c6e5534954fb07a7e7bc966fee268a15337bfd8"; + } else if (height == 7) { + typehash = hex"f7a65efd167a18f7091b2bb929d687dd94503cf0a43620487055ed7d6b727559"; + } else if (height == 8) { + typehash = hex"def24acacad1318b664520f7c10e8bc6d1e7f6f6f7c8b031e70624ceb42266a6"; + } else if (height == 9) { + typehash = hex"4cb4080dc4e7bae88b4dc4307ad5117fa4f26195998a1b5f40368809d7f4c7f2"; + } else if (height == 10) { + typehash = hex"f8b1f864164d8d6e0b45f1399bd711223117a4ab0b057a9c2d7779e86a7c88db"; + } else { + revert MerkleProofTooLarge(height); + } + } +} diff --git a/contracts/src/marketplace/CreatorFeeManagerWithRebates.sol b/contracts/src/marketplace/CreatorFeeManagerWithRebates.sol new file mode 100644 index 00000000..4b5e6ef7 --- /dev/null +++ b/contracts/src/marketplace/CreatorFeeManagerWithRebates.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC2981 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; + +// Interfaces +import { ICreatorFeeManager } from "./interfaces/ICreatorFeeManager.sol"; +import { IRoyaltyFeeRegistry } from "./interfaces/IRoyaltyFeeRegistry.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "./constants/NumericConstants.sol"; + +/** + * @title CreatorFeeManagerWithRebates + * @notice This contract returns the creator fee address and the creator rebate amount. + * @author LooksRare protocol team (👀,💎) + */ +contract CreatorFeeManagerWithRebates is ICreatorFeeManager { + /** + * @notice Standard royalty fee (in basis point). + */ + uint256 public constant STANDARD_ROYALTY_FEE_BP = 50; + + /** + * @notice Royalty fee registry interface. + */ + IRoyaltyFeeRegistry public immutable royaltyFeeRegistry; + + /** + * @notice Constructor + * @param _royaltyFeeRegistry Royalty fee registry address. + */ + constructor(address _royaltyFeeRegistry) { + royaltyFeeRegistry = IRoyaltyFeeRegistry(_royaltyFeeRegistry); + } + + /** + * @inheritdoc ICreatorFeeManager + */ + function viewCreatorFeeInfo( + address collection, + uint256 price, + uint256[] memory itemIds + ) external view returns (address creator, uint256 creatorFeeAmount) { + // Check if there is a royalty info in the system + (creator, ) = royaltyFeeRegistry.royaltyInfo(collection, price); + + if (creator == address(0)) { + if (IERC2981(collection).supportsInterface(IERC2981.royaltyInfo.selector)) { + uint256 length = itemIds.length; + + for (uint256 i; i < length; ) { + try IERC2981(collection).royaltyInfo(itemIds[i], price) returns ( + address newCreator, + uint256 /* newCreatorFeeAmount */ + ) { + if (i == 0) { + creator = newCreator; + + unchecked { + ++i; + } + continue; + } + + if (newCreator != creator) { + revert BundleEIP2981NotAllowed(collection); + } + } catch { + // If creator address is not 0, that means there was at least 1 + // successful call. If all royaltyInfo calls fail, it should assume + // 0 royalty. + // If the first call reverts, even if creator is address(0), subsequent + // successful calls will still revert above with BundleEIP2981NotAllowed + // because newCreator will be different from creator. + if (creator != address(0)) { + revert BundleEIP2981NotAllowed(collection); + } + } + + unchecked { + ++i; + } + } + } + } + + // A fixed royalty fee is applied + if (creator != address(0)) { + creatorFeeAmount = (STANDARD_ROYALTY_FEE_BP * price) / ONE_HUNDRED_PERCENT_IN_BP; + } + } +} diff --git a/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol b/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol new file mode 100644 index 00000000..a1185163 --- /dev/null +++ b/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC2981 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; + +// Interfaces +import { ICreatorFeeManager } from "./interfaces/ICreatorFeeManager.sol"; +import { IRoyaltyFeeRegistry } from "./interfaces/IRoyaltyFeeRegistry.sol"; + +/** + * @title CreatorFeeManagerWithRoyalties + * @notice This contract returns the creator fee address and the creator fee amount. + * @author LooksRare protocol team (👀,💎) + */ +contract CreatorFeeManagerWithRoyalties is ICreatorFeeManager { + /** + * @notice Royalty fee registry interface. + */ + IRoyaltyFeeRegistry public immutable royaltyFeeRegistry; + + /** + * @notice Constructor + * @param _royaltyFeeRegistry Royalty fee registry address. + */ + constructor(address _royaltyFeeRegistry) { + royaltyFeeRegistry = IRoyaltyFeeRegistry(_royaltyFeeRegistry); + } + + /** + * @inheritdoc ICreatorFeeManager + * @dev There are two on-chain sources for the royalty fee to distribute. + * 1. RoyaltyFeeRegistry: It is an on-chain registry where creator fee is defined + for all items of a collection. + * 2. ERC2981: The NFT Royalty Standard where royalty fee is defined at a itemId level in a collection. + * The on-chain logic looks up the registry first. If it does not find anything, + * it checks if a collection is ERC2981. If so, it fetches the proper royalty information for the itemId. + * For a bundle that contains multiple itemIds (for a collection using ERC2981), if the royalty fee/recipient + * differ among the itemIds part of the bundle, the trade reverts. + * This contract DOES NOT enforce any restriction for extremely high creator fee, + * nor verifies the creator fee fetched is inferior to the total price. + * If any contract relies on it to build an on-chain royalty logic, + * it should implement protection against: + * (1) high royalties + * (2) potential unexpected royalty changes that can occur after the creation of the order. + */ + function viewCreatorFeeInfo( + address collection, + uint256 price, + uint256[] memory itemIds + ) external view returns (address creator, uint256 creatorFeeAmount) { + // Check if there is a royalty info in the system + (creator, creatorFeeAmount) = royaltyFeeRegistry.royaltyInfo(collection, price); + + if (creator == address(0)) { + if (IERC2981(collection).supportsInterface(IERC2981.royaltyInfo.selector)) { + uint256 length = itemIds.length; + + for (uint256 i; i < length; ) { + try IERC2981(collection).royaltyInfo(itemIds[i], price) returns ( + address newCreator, + uint256 newCreatorFeeAmount + ) { + if (i == 0) { + creator = newCreator; + creatorFeeAmount = newCreatorFeeAmount; + + unchecked { + ++i; + } + continue; + } + + if (newCreator != creator || newCreatorFeeAmount != creatorFeeAmount) { + revert BundleEIP2981NotAllowed(collection); + } + } catch { + // If creator address is not 0, that means there was at least 1 + // successful call. If all royaltyInfo calls fail, it should assume + // 0 royalty. + // If the first call reverts, even if creator is address(0), subsequent + // successful calls will still revert above with BundleEIP2981NotAllowed + // because newCreator/newCreatorFeeAmount will be different from creator/creatorFeeAmount. + if (creator != address(0)) { + revert BundleEIP2981NotAllowed(collection); + } + } + + unchecked { + ++i; + } + } + } + } + } +} diff --git a/contracts/src/marketplace/CurrencyManager.sol b/contracts/src/marketplace/CurrencyManager.sol new file mode 100644 index 00000000..103d0789 --- /dev/null +++ b/contracts/src/marketplace/CurrencyManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { OwnableTwoSteps } from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; + +// Interfaces +import { ICurrencyManager } from "./interfaces/ICurrencyManager.sol"; + +/** + * @title CurrencyManager + * @notice This contract manages the list of valid fungible currencies. + * @author LooksRare protocol team (👀,💎) + */ +contract CurrencyManager is ICurrencyManager, OwnableTwoSteps { + /** + * @notice It checks whether the currency is allowed for transacting. + */ + mapping(address => bool) public isCurrencyAllowed; + + /** + * @notice Constructor + * @param _owner Owner address + */ + constructor(address _owner) OwnableTwoSteps(_owner) {} + + /** + * @notice This function allows the owner to update the status of a currency. + * @param currency Currency address (address(0) for ETH) + * @param isAllowed Whether the currency should be allowed for trading + * @dev Only callable by owner. + */ + function updateCurrencyStatus(address currency, bool isAllowed) external onlyOwner { + isCurrencyAllowed[currency] = isAllowed; + emit CurrencyStatusUpdated(currency, isAllowed); + } +} diff --git a/contracts/src/marketplace/ExecutionManager.sol b/contracts/src/marketplace/ExecutionManager.sol new file mode 100644 index 00000000..06de37c1 --- /dev/null +++ b/contracts/src/marketplace/ExecutionManager.sol @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "./libraries/OrderStructs.sol"; + +// Interfaces +import { IExecutionManager } from "./interfaces/IExecutionManager.sol"; +import { ICreatorFeeManager } from "./interfaces/ICreatorFeeManager.sol"; + +// Direct dependencies +import { InheritedStrategy } from "./InheritedStrategy.sol"; +import { NonceManager } from "./NonceManager.sol"; +import { StrategyManager } from "./StrategyManager.sol"; + +// Assembly +import { NoSelectorForStrategy_error_selector, NoSelectorForStrategy_error_length, OutsideOfTimeRange_error_selector, OutsideOfTimeRange_error_length, Error_selector_offset } from "./constants/AssemblyConstants.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "./constants/NumericConstants.sol"; + +// Enums +import { QuoteType } from "./enums/QuoteType.sol"; + +/** + * @title ExecutionManager + * @notice This contract handles the execution and resolution of transactions. A transaction is executed on-chain + * when an off-chain maker order is matched by on-chain taker order of a different kind. + * For instance, a taker ask is executed against a maker bid (or a taker bid against a maker ask). + * @author LooksRare protocol team (👀,💎) + */ +contract ExecutionManager is InheritedStrategy, NonceManager, StrategyManager, IExecutionManager { + /** + * @notice Protocol fee recipient. + */ + address public protocolFeeRecipient; + + /** + * @notice Maximum creator fee (in basis point). + */ + // TODO do we need a max? Is 1% max fair? + uint16 public maxCreatorFeeBp = 1_000; + + /** + * @notice Creator fee manager. + */ + ICreatorFeeManager public creatorFeeManager; + + /** + * @notice Constructor + * @param _owner Owner address + * @param _protocolFeeRecipient Protocol fee recipient address + */ + constructor(address _owner, address _protocolFeeRecipient) StrategyManager(_owner) { + _updateProtocolFeeRecipient(_protocolFeeRecipient); + } + + /** + * @notice This function allows the owner to update the creator fee manager address. + * @param newCreatorFeeManager Address of the creator fee manager + * @dev Only callable by owner. + */ + function updateCreatorFeeManager(address newCreatorFeeManager) external onlyOwner { + creatorFeeManager = ICreatorFeeManager(newCreatorFeeManager); + emit NewCreatorFeeManager(newCreatorFeeManager); + } + + /** + * @notice This function allows the owner to update the maximum creator fee (in basis point). + * @param newMaxCreatorFeeBp New maximum creator fee (in basis point) + * @dev The maximum value that can be set is 25%. + * Only callable by owner. + */ + function updateMaxCreatorFeeBp(uint16 newMaxCreatorFeeBp) external onlyOwner { + if (newMaxCreatorFeeBp > 2_500) { + revert CreatorFeeBpTooHigh(); + } + + maxCreatorFeeBp = newMaxCreatorFeeBp; + + emit NewMaxCreatorFeeBp(newMaxCreatorFeeBp); + } + + /** + * @notice This function allows the owner to update the protocol fee recipient. + * @param newProtocolFeeRecipient New protocol fee recipient address + * @dev Only callable by owner. + */ + function updateProtocolFeeRecipient(address newProtocolFeeRecipient) external onlyOwner { + _updateProtocolFeeRecipient(newProtocolFeeRecipient); + } + + /** + * @notice This function is internal and is used to execute a transaction initiated by a taker order. + * @param takerOrder Taker order struct (taker specific parameters for the execution) + * @param makerOrder Maker order struct (maker specific parameter for the execution) + * @param sender The address that sent the transaction + * @return itemIds Array of item ids to be traded + * @return amounts Array of amounts for each item id + * @return recipients Array of recipient addresses + * @return feeAmounts Array of fee amounts + * @return isNonceInvalidated Whether the order's nonce will be invalidated after executing the order + */ + function _executeStrategyForTakerOrder( + OrderStructs.Taker calldata takerOrder, + OrderStructs.Maker calldata makerOrder, + address sender + ) + internal + returns ( + uint256[] memory itemIds, + uint256[] memory amounts, + address[2] memory recipients, + uint256[3] memory feeAmounts, + bool isNonceInvalidated + ) + { + uint256 price; + + // Verify the order validity for timestamps + _verifyOrderTimestampValidity(makerOrder.startTime, makerOrder.endTime); + + if (makerOrder.strategyId == 0) { + _verifyItemIdsAndAmountsEqualLengthsAndValidAmounts(makerOrder.amounts, makerOrder.itemIds); + (price, itemIds, amounts) = (makerOrder.price, makerOrder.itemIds, makerOrder.amounts); + isNonceInvalidated = true; + } else { + if (strategyInfo[makerOrder.strategyId].isActive) { + /** + * @dev This is equivalent to + * + * if (makerOrder.quoteType == QuoteType.Bid) { + * if (!strategyInfo[makerOrder.strategyId].isMakerBid) { + * revert NoSelectorForStrategy(); + * } + * } else { + * if (strategyInfo[makerOrder.strategyId].isMakerBid) { + * revert NoSelectorForStrategy(); + * } + * } + * + * because one must be 0 and another must be 1 for the function + * to not revert. + * + * Both quoteType (an enum with 2 values) and isMakerBid (a bool) + * can only be 0 or 1. + */ + QuoteType quoteType = makerOrder.quoteType; + bool isMakerBid = strategyInfo[makerOrder.strategyId].isMakerBid; + assembly { + if eq(quoteType, isMakerBid) { + mstore(0x00, NoSelectorForStrategy_error_selector) + revert(Error_selector_offset, NoSelectorForStrategy_error_length) + } + } + + (bool status, bytes memory data) = strategyInfo[makerOrder.strategyId].implementation.call( + abi.encodeWithSelector(strategyInfo[makerOrder.strategyId].selector, takerOrder, makerOrder) + ); + + if (!status) { + // @dev It forwards the revertion message from the low-level call + assembly { + revert(add(data, 32), mload(data)) + } + } + + (price, itemIds, amounts, isNonceInvalidated) = abi.decode(data, (uint256, uint256[], uint256[], bool)); + } else { + revert StrategyNotAvailable(makerOrder.strategyId); + } + } + + // Creator fee and adjustment of protocol fee + (recipients[1], feeAmounts[1]) = _getCreatorRecipientAndCalculateFeeAmount( + makerOrder.collection, + price, + itemIds + ); + if (makerOrder.quoteType == QuoteType.Bid) { + _setTheRestOfFeeAmountsAndRecipients( + makerOrder.strategyId, + price, + takerOrder.recipient == address(0) ? sender : takerOrder.recipient, + feeAmounts, + recipients + ); + } else { + _setTheRestOfFeeAmountsAndRecipients( + makerOrder.strategyId, + price, + makerOrder.signer, + feeAmounts, + recipients + ); + } + } + + /** + * @notice This private function updates the protocol fee recipient. + * @param newProtocolFeeRecipient New protocol fee recipient address + */ + function _updateProtocolFeeRecipient(address newProtocolFeeRecipient) private { + if (newProtocolFeeRecipient == address(0)) { + revert NewProtocolFeeRecipientCannotBeNullAddress(); + } + + protocolFeeRecipient = newProtocolFeeRecipient; + emit NewProtocolFeeRecipient(newProtocolFeeRecipient); + } + + /** + * @notice This function is internal and is used to calculate + * the protocol fee amount for a set of fee amounts. + * @param price Transaction price + * @param strategyId Strategy id + * @param creatorFeeAmount Creator fee amount + * @param minTotalFeeAmount Min total fee amount + * @return protocolFeeAmount Protocol fee amount + */ + function _calculateProtocolFeeAmount( + uint256 price, + uint256 strategyId, + uint256 creatorFeeAmount, + uint256 minTotalFeeAmount + ) private view returns (uint256 protocolFeeAmount) { + protocolFeeAmount = (price * strategyInfo[strategyId].standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + + if (protocolFeeAmount + creatorFeeAmount < minTotalFeeAmount) { + protocolFeeAmount = minTotalFeeAmount - creatorFeeAmount; + } + } + + /** + * @notice This function is internal and is used to get the creator fee address + * and calculate the creator fee amount. + * @param collection Collection address + * @param price Transaction price + * @param itemIds Array of item ids + * @return creator Creator recipient + * @return creatorFeeAmount Creator fee amount + */ + function _getCreatorRecipientAndCalculateFeeAmount( + address collection, + uint256 price, + uint256[] memory itemIds + ) private view returns (address creator, uint256 creatorFeeAmount) { + if (address(creatorFeeManager) != address(0)) { + (creator, creatorFeeAmount) = creatorFeeManager.viewCreatorFeeInfo(collection, price, itemIds); + + if (creator == address(0)) { + // If recipient is null address, creator fee is set to 0 + creatorFeeAmount = 0; + } else if (creatorFeeAmount * ONE_HUNDRED_PERCENT_IN_BP > (price * uint256(maxCreatorFeeBp))) { + // If creator fee is higher than tolerated, it reverts + revert CreatorFeeBpTooHigh(); + } + } + } + + /** + * @dev This function does not need to return feeAmounts and recipients as they are modified + * in memory. + */ + function _setTheRestOfFeeAmountsAndRecipients( + uint256 strategyId, + uint256 price, + address askRecipient, + uint256[3] memory feeAmounts, + address[2] memory recipients + ) private view { + // Compute minimum total fee amount + uint256 minTotalFeeAmount = (price * strategyInfo[strategyId].minTotalFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + + if (feeAmounts[1] == 0) { + // If creator fee is null, protocol fee is set as the minimum total fee amount + feeAmounts[2] = minTotalFeeAmount; + // Net fee amount for seller + feeAmounts[0] = price - feeAmounts[2]; + } else { + // If there is a creator fee information, the protocol fee amount can be calculated + feeAmounts[2] = _calculateProtocolFeeAmount(price, strategyId, feeAmounts[1], minTotalFeeAmount); + // Net fee amount for seller + feeAmounts[0] = price - feeAmounts[1] - feeAmounts[2]; + } + + recipients[0] = askRecipient; + } + + /** + * @notice This function is internal and is used to verify the validity of an order + * in the context of the current block timestamps. + * @param startTime Start timestamp + * @param endTime End timestamp + */ + function _verifyOrderTimestampValidity(uint256 startTime, uint256 endTime) private view { + // if (startTime > block.timestamp || endTime < block.timestamp) revert OutsideOfTimeRange(); + assembly { + if or(gt(startTime, timestamp()), lt(endTime, timestamp())) { + mstore(0x00, OutsideOfTimeRange_error_selector) + revert(Error_selector_offset, OutsideOfTimeRange_error_length) + } + } + } +} diff --git a/contracts/src/marketplace/InheritedStrategy.sol b/contracts/src/marketplace/InheritedStrategy.sol new file mode 100644 index 00000000..9f84b5a1 --- /dev/null +++ b/contracts/src/marketplace/InheritedStrategy.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "./libraries/OrderStructs.sol"; + +// Shared errors +import { OrderInvalid } from "./errors/SharedErrors.sol"; + +// Assembly +import { OrderInvalid_error_selector, OrderInvalid_error_length, Error_selector_offset, OneWord } from "./constants/AssemblyConstants.sol"; + +/** + * @title InheritedStrategy + * @notice This contract handles the verification of parameters for standard transactions. + * It does not verify the taker struct's itemIds and amounts array as well as + * minPrice (taker ask) / maxPrice (taker bid) because before the taker executes the + * transaction and the maker itemIds/amounts/price should have already been confirmed off-chain. + * @dev A standard transaction (bid or ask) is mapped to strategyId = 0. + * @author LooksRare protocol team (👀,💎) + */ +contract InheritedStrategy { + /** + * @notice This function is internal and is used to validate the parameters for a standard sale strategy + * when the standard transaction is initiated by a taker bid. + * @param amounts Array of amounts + * @param itemIds Array of item ids + */ + function _verifyItemIdsAndAmountsEqualLengthsAndValidAmounts( + uint256[] calldata amounts, + uint256[] calldata itemIds + ) internal pure { + assembly { + let end + { + /* + * @dev If A == B, then A XOR B == 0. + * + * if (amountsLength == 0 || amountsLength != itemIdsLength) { + * revert OrderInvalid(); + * } + */ + let amountsLength := amounts.length + let itemIdsLength := itemIds.length + + if or(iszero(amountsLength), xor(amountsLength, itemIdsLength)) { + mstore(0x00, OrderInvalid_error_selector) + revert(Error_selector_offset, OrderInvalid_error_length) + } + + /** + * @dev Shifting left 5 times is equivalent to amountsLength * 32 bytes + */ + end := shl(5, amountsLength) + } + + let amountsOffset := amounts.offset + + for { + + } end { + + } { + /** + * @dev Starting from the end of the array minus 32 bytes to load the last item, + * ending with `end` equal to 0 to load the first item + * + * uint256 end = amountsLength; + * + * for (uint256 i = end - 1; i >= 0; i--) { + * uint256 amount = amounts[i]; + * if (amount == 0) { + * revert OrderInvalid(); + * } + * } + */ + end := sub(end, OneWord) + + let amount := calldataload(add(amountsOffset, end)) + + if iszero(amount) { + mstore(0x00, OrderInvalid_error_selector) + revert(Error_selector_offset, OrderInvalid_error_length) + } + } + } + } +} diff --git a/contracts/src/marketplace/LooksRareProtocol.sol b/contracts/src/marketplace/LooksRareProtocol.sol new file mode 100644 index 00000000..45c6331f --- /dev/null +++ b/contracts/src/marketplace/LooksRareProtocol.sol @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { SignatureCheckerCalldata } from "@looksrare/contracts-libs/contracts/SignatureCheckerCalldata.sol"; +import { LowLevelETHReturnETHIfAnyExceptOneWei } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelETHReturnETHIfAnyExceptOneWei.sol"; +import { LowLevelWETH } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelWETH.sol"; +import { LowLevelERC20Transfer } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol"; + +// OpenZeppelin's library (adjusted) for verifying Merkle proofs +import { MerkleProofCalldataWithNodes } from "./libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol"; + +// Libraries +import { OrderStructs } from "./libraries/OrderStructs.sol"; + +// Interfaces +import { ILooksRareProtocol } from "./interfaces/ILooksRareProtocol.sol"; + +// Shared errors +import { CallerInvalid, CurrencyInvalid, LengthsInvalid, MerkleProofInvalid, MerkleProofTooLarge, QuoteTypeInvalid } from "./errors/SharedErrors.sol"; + +// Direct dependencies +import { TransferSelectorNFT } from "./TransferSelectorNFT.sol"; +import { BatchOrderTypehashRegistry } from "./BatchOrderTypehashRegistry.sol"; + +// Constants +import { MAX_CALLDATA_PROOF_LENGTH, ONE_HUNDRED_PERCENT_IN_BP } from "./constants/NumericConstants.sol"; + +// Enums +import { QuoteType } from "./enums/QuoteType.sol"; + +/** + * @title LooksRareProtocol + * @notice This contract is the core smart contract of the LooksRare protocol ("v2"). + * It is the main entry point for users to initiate transactions with taker orders + * and manage the cancellation of maker orders, which exist off-chain. + * ~~~~~~ + * ~~~~ ~~~~ + * ~~~ ~~~ + * ~~~ ~~~ + * ~~~ ~~~ + * ~~~~~~~~~ ~~~ ~~~ ~~~~~~~~~ + * ~~~ ~~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~~ ~~~ + * ~~~ ~~~~~~~ ~~~~~~~ ~~~ + * ~~~- ~~~~~~~~ ~~~~ + * ~~~ ~~~~ ~~~~ ~~~ + * ~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~ + * ~~~ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~ + * ~~~ ~~~ ~~~ ~~~ + * ~~~ ~~~ ~~~~~~~~~~ ~~~ ~~~ + * ~~~~~ ~~~ ~~~~~~ ~~~~~~ ~~~ ~~~~~ + * ~~~~~~~ ~~~ ~~~ ~~~ ~~~ ~~~~~~~ + * ~~~~~~ ~~~~ ~~~ ~~~ ~~~~ ~~~~~~ + * ~~~~ ~~~ ~~~ ~~~ ~~~ ~~~~ + * ~~~ ~~~ ~~~ ~~~ ~~~ ~~~ + * ~~~~ ~~~ ~~~ ~~~ ~~~ ~~~~ + * ~~~~~~ ~~~~ ~~~ ~~~ ~~~~~ ~~~~~~ + * ~~~~~~~ ~~~ ~~~ ~~~ ~~~ ~~~~~~~ + * ~~~~~ ~~~ ~~~~~~ ~~~~~~ ~~~ ~~~~~ + * ~~~ ~~~ ~~~~~~~~~~ ~~~ ~~~ + * ~~ ~~~ ~~~ ~~~ + * ~~~ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~ + * ~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~ + * ~~~ ~~~~ ~~~~ ~~~ + * ~~~~ ~~~~~~~~ ~~~~ + * ~~~ ~~~~~~~ ~~~~~~~ ~~~ + * ~~~ ~~~~~~~~ ~~~~ ~~~~ ~~~~~~~~ ~~~ + * ~~~~~~~~~ ~~~ ~~~ ~~~~~~~~~ + * ~~~ ~~~ + * ~~~ ~~~ + * ~~~ ~~~ + * ~~~~ ~~~~ + * ~~~~~~ + * @author LooksRare protocol team (👀,💎) + */ +contract LooksRareProtocol is + ILooksRareProtocol, + TransferSelectorNFT, + LowLevelETHReturnETHIfAnyExceptOneWei, + LowLevelWETH, + LowLevelERC20Transfer, + BatchOrderTypehashRegistry +{ + using OrderStructs for OrderStructs.Maker; + + /** + * @notice Wrapped ETH. + */ + address public immutable WETH; + + /** + * @notice Current chainId. + */ + uint256 public chainId; + + /** + * @notice Current domain separator. + */ + bytes32 public domainSeparator; + + /** + * @notice This variable is used as the gas limit for a ETH transfer. + * If a standard ETH transfer fails within this gas limit, ETH will get wrapped to WETH + * and transferred to the initial recipient. + */ + uint256 private _gasLimitETHTransfer = 2_300; + + /** + * @notice Constructor + * @param _owner Owner address + * @param _protocolFeeRecipient Protocol fee recipient address + * @param _transferManager Transfer manager address + * @param _weth Wrapped ETH address + */ + constructor( + address _owner, + address _protocolFeeRecipient, + address _transferManager, + address _weth + ) TransferSelectorNFT(_owner, _protocolFeeRecipient, _transferManager) { + _updateDomainSeparator(); + WETH = _weth; + } + + /** + * @inheritdoc ILooksRareProtocol + */ + function executeTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external nonReentrant { + address currency = makerBid.currency; + + // Verify whether the currency is allowed and is not ETH (address(0)) + if (!isCurrencyAllowed[currency] || currency == address(0)) { + revert CurrencyInvalid(); + } + + address signer = makerBid.signer; + bytes32 orderHash = makerBid.hash(); + _verifyMerkleProofOrOrderHash(merkleTree, orderHash, makerSignature, signer); + + // Execute the transaction and fetch protocol fee amount + uint256 totalProtocolFeeAmount = _executeTakerAsk(takerAsk, makerBid, orderHash); + + // Pay protocol fee (and affiliate fee if any) + _payProtocolFeeAndAffiliateFee(currency, signer, affiliate, totalProtocolFeeAmount); + } + + /** + * @inheritdoc ILooksRareProtocol + */ + function executeTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external payable nonReentrant { + address currency = makerAsk.currency; + + // Verify whether the currency is allowed for trading. + if (!isCurrencyAllowed[currency]) { + revert CurrencyInvalid(); + } + + bytes32 orderHash = makerAsk.hash(); + _verifyMerkleProofOrOrderHash(merkleTree, orderHash, makerSignature, makerAsk.signer); + + // Execute the transaction and fetch protocol fee amount + uint256 totalProtocolFeeAmount = _executeTakerBid(takerBid, makerAsk, msg.sender, orderHash); + + // Pay protocol fee amount (and affiliate fee if any) + _payProtocolFeeAndAffiliateFee(currency, msg.sender, affiliate, totalProtocolFeeAmount); + + // Return ETH if any + _returnETHIfAnyWithOneWeiLeft(); + } + + /** + * @inheritdoc ILooksRareProtocol + */ + function executeMultipleTakerBids( + OrderStructs.Taker[] calldata takerBids, + OrderStructs.Maker[] calldata makerAsks, + bytes[] calldata makerSignatures, + OrderStructs.MerkleTree[] calldata merkleTrees, + address affiliate, + bool isAtomic + ) external payable nonReentrant { + uint256 length = takerBids.length; + if ( + length == 0 || + (makerAsks.length ^ length) | (makerSignatures.length ^ length) | (merkleTrees.length ^ length) != 0 + ) { + revert LengthsInvalid(); + } + + // Verify whether the currency at index = 0 is allowed for trading + address currency = makerAsks[0].currency; + if (!isCurrencyAllowed[currency]) { + revert CurrencyInvalid(); + } + + { + // Initialize protocol fee amount + uint256 totalProtocolFeeAmount; + + // If atomic, it uses the executeTakerBid function. + // If not atomic, it uses a catch/revert pattern with external function. + if (isAtomic) { + for (uint256 i; i < length; ) { + OrderStructs.Maker calldata makerAsk = makerAsks[i]; + + // Verify the currency is the same + if (i != 0) { + if (makerAsk.currency != currency) { + revert CurrencyInvalid(); + } + } + + OrderStructs.Taker calldata takerBid = takerBids[i]; + bytes32 orderHash = makerAsk.hash(); + + { + _verifyMerkleProofOrOrderHash(merkleTrees[i], orderHash, makerSignatures[i], makerAsk.signer); + + // Execute the transaction and add protocol fee + totalProtocolFeeAmount += _executeTakerBid(takerBid, makerAsk, msg.sender, orderHash); + + unchecked { + ++i; + } + } + } + } else { + for (uint256 i; i < length; ) { + OrderStructs.Maker calldata makerAsk = makerAsks[i]; + + // Verify the currency is the same + if (i != 0) { + if (makerAsk.currency != currency) { + revert CurrencyInvalid(); + } + } + + OrderStructs.Taker calldata takerBid = takerBids[i]; + bytes32 orderHash = makerAsk.hash(); + + { + _verifyMerkleProofOrOrderHash(merkleTrees[i], orderHash, makerSignatures[i], makerAsk.signer); + + try this.restrictedExecuteTakerBid(takerBid, makerAsk, msg.sender, orderHash) returns ( + uint256 protocolFeeAmount + ) { + totalProtocolFeeAmount += protocolFeeAmount; + } catch {} + + unchecked { + ++i; + } + } + } + } + + // Pay protocol fee (and affiliate fee if any) + _payProtocolFeeAndAffiliateFee(currency, msg.sender, affiliate, totalProtocolFeeAmount); + } + + // Return ETH if any + _returnETHIfAnyWithOneWeiLeft(); + } + + /** + * @notice This function is used to do a non-atomic matching in the context of a batch taker bid. + * @param takerBid Taker bid struct + * @param makerAsk Maker ask struct + * @param sender Sender address (i.e. the initial msg sender) + * @param orderHash Hash of the maker ask order + * @return protocolFeeAmount Protocol fee amount + * @dev This function is only callable by this contract. It is used for non-atomic batch order matching. + */ + function restrictedExecuteTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk, + address sender, + bytes32 orderHash + ) external returns (uint256 protocolFeeAmount) { + if (msg.sender != address(this)) { + revert CallerInvalid(); + } + + protocolFeeAmount = _executeTakerBid(takerBid, makerAsk, sender, orderHash); + } + + /** + * @notice This function allows the owner to update the domain separator (if possible). + * @dev Only callable by owner. If there is a fork of the network with a new chainId, + * it allows the owner to reset the domain separator for the new chain id. + */ + function updateDomainSeparator() external onlyOwner { + if (block.chainid != chainId) { + _updateDomainSeparator(); + emit NewDomainSeparator(); + } else { + revert SameDomainSeparator(); + } + } + + /** + * @notice This function allows the owner to update the maximum ETH gas limit for a standard transfer. + * @param newGasLimitETHTransfer New gas limit for ETH transfer + * @dev Only callable by owner. + */ + function updateETHGasLimitForTransfer(uint256 newGasLimitETHTransfer) external onlyOwner { + if (newGasLimitETHTransfer < 2_300) { + revert NewGasLimitETHTransferTooLow(); + } + + _gasLimitETHTransfer = newGasLimitETHTransfer; + + emit NewGasLimitETHTransfer(newGasLimitETHTransfer); + } + + /** + * @notice This function is internal and is used to execute a taker ask (against a maker bid). + * @param takerAsk Taker ask order struct + * @param makerBid Maker bid order struct + * @param orderHash Hash of the maker bid order + * @return protocolFeeAmount Protocol fee amount + */ + function _executeTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid, + bytes32 orderHash + ) internal returns (uint256) { + if (makerBid.quoteType != QuoteType.Bid) { + revert QuoteTypeInvalid(); + } + + address signer = makerBid.signer; + { + bytes32 userOrderNonceStatus = userOrderNonce[signer][makerBid.orderNonce]; + // Verify nonces + if ( + userBidAskNonces[signer].bidNonce != makerBid.globalNonce || + userSubsetNonce[signer][makerBid.subsetNonce] || + (userOrderNonceStatus != bytes32(0) && userOrderNonceStatus != orderHash) + ) { + revert NoncesInvalid(); + } + } + + ( + uint256[] memory itemIds, + uint256[] memory amounts, + address[2] memory recipients, + uint256[3] memory feeAmounts, + bool isNonceInvalidated + ) = _executeStrategyForTakerOrder(takerAsk, makerBid, msg.sender); + + // Order nonce status is updated + _updateUserOrderNonce(isNonceInvalidated, signer, makerBid.orderNonce, orderHash); + + // Taker action goes first + _transferNFT(makerBid.collection, makerBid.collectionType, msg.sender, signer, itemIds, amounts); + + // Maker action goes second + _transferToAskRecipientAndCreatorIfAny(recipients, feeAmounts, makerBid.currency, signer); + + emit TakerAsk( + NonceInvalidationParameters({ + orderHash: orderHash, + orderNonce: makerBid.orderNonce, + isNonceInvalidated: isNonceInvalidated + }), + msg.sender, + signer, + makerBid.strategyId, + makerBid.currency, + makerBid.collection, + itemIds, + amounts, + recipients, + feeAmounts + ); + + // It returns the protocol fee amount + return feeAmounts[2]; + } + + /** + * @notice This function is internal and is used to execute a taker bid (against a maker ask). + * @param takerBid Taker bid order struct + * @param makerAsk Maker ask order struct + * @param sender Sender of the transaction (i.e. msg.sender) + * @param orderHash Hash of the maker ask order + * @return protocolFeeAmount Protocol fee amount + */ + function _executeTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk, + address sender, + bytes32 orderHash + ) internal returns (uint256) { + if (makerAsk.quoteType != QuoteType.Ask) { + revert QuoteTypeInvalid(); + } + + address signer = makerAsk.signer; + { + // Verify nonces + bytes32 userOrderNonceStatus = userOrderNonce[signer][makerAsk.orderNonce]; + + if ( + userBidAskNonces[signer].askNonce != makerAsk.globalNonce || + userSubsetNonce[signer][makerAsk.subsetNonce] || + (userOrderNonceStatus != bytes32(0) && userOrderNonceStatus != orderHash) + ) { + revert NoncesInvalid(); + } + } + + ( + uint256[] memory itemIds, + uint256[] memory amounts, + address[2] memory recipients, + uint256[3] memory feeAmounts, + bool isNonceInvalidated + ) = _executeStrategyForTakerOrder(takerBid, makerAsk, msg.sender); + + // Order nonce status is updated + _updateUserOrderNonce(isNonceInvalidated, signer, makerAsk.orderNonce, orderHash); + + // Taker action goes first + _transferToAskRecipientAndCreatorIfAny(recipients, feeAmounts, makerAsk.currency, sender); + + // Maker action goes second + _transferNFT( + makerAsk.collection, + makerAsk.collectionType, + signer, + takerBid.recipient == address(0) ? sender : takerBid.recipient, + itemIds, + amounts + ); + + emit TakerBid( + NonceInvalidationParameters({ + orderHash: orderHash, + orderNonce: makerAsk.orderNonce, + isNonceInvalidated: isNonceInvalidated + }), + sender, + takerBid.recipient == address(0) ? sender : takerBid.recipient, + makerAsk.strategyId, + makerAsk.currency, + makerAsk.collection, + itemIds, + amounts, + recipients, + feeAmounts + ); + + // It returns the protocol fee amount + return feeAmounts[2]; + } + + /** + * @notice This function is internal and is used to pay the protocol fee and affiliate fee (if any). + * @param currency Currency address to transfer (address(0) is ETH) + * @param bidUser Bid user address + * @param affiliate Affiliate address (address(0) if none) + * @param totalProtocolFeeAmount Total protocol fee amount (denominated in the currency) + */ + function _payProtocolFeeAndAffiliateFee( + address currency, + address bidUser, + address affiliate, + uint256 totalProtocolFeeAmount + ) internal { + if (totalProtocolFeeAmount != 0) { + // Transfer remaining protocol fee to the protocol fee recipient + _transferFungibleTokens(currency, bidUser, protocolFeeRecipient, totalProtocolFeeAmount); + } + } + + /** + * @notice This function is internal and is used to transfer fungible tokens. + * @param currency Currency address + * @param sender Sender address + * @param recipient Recipient address + * @param amount Amount (in fungible tokens) + */ + function _transferFungibleTokens(address currency, address sender, address recipient, uint256 amount) internal { + if (currency == address(0)) { + _transferETHAndWrapIfFailWithGasLimit(WETH, recipient, amount, _gasLimitETHTransfer); + } else { + _executeERC20TransferFrom(currency, sender, recipient, amount); + } + } + + /** + * @notice This function is private and used to transfer funds to + * (1) creator recipient (if any) + * (2) ask recipient. + * @param recipients Recipient addresses + * @param feeAmounts Fees + * @param currency Currency address + * @param bidUser Bid user address + * @dev It does not send to the 0-th element in the array since it is the protocol fee, + * which is paid later in the execution flow. + */ + function _transferToAskRecipientAndCreatorIfAny( + address[2] memory recipients, + uint256[3] memory feeAmounts, + address currency, + address bidUser + ) private { + // @dev There is no check for address(0) since the ask recipient can never be address(0) + // If ask recipient is the maker --> the signer cannot be the null address + // If ask is the taker --> either it is the sender address or + // if the recipient (in TakerAsk) is set to address(0), it is adjusted to the original taker address + uint256 sellerProceed = feeAmounts[0]; + if (sellerProceed != 0) { + _transferFungibleTokens(currency, bidUser, recipients[0], sellerProceed); + } + + // @dev There is no check for address(0), if the creator recipient is address(0), the fee is set to 0 + uint256 creatorFeeAmount = feeAmounts[1]; + if (creatorFeeAmount != 0) { + _transferFungibleTokens(currency, bidUser, recipients[1], creatorFeeAmount); + } + } + + /** + * @notice This function is private and used to compute the domain separator and store the current chain id. + */ + function _updateDomainSeparator() private { + domainSeparator = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("LooksRareProtocol"), + keccak256(bytes("2")), + block.chainid, + address(this) + ) + ); + chainId = block.chainid; + } + + /** + * @notice This function is internal and is called during the execution of a transaction to decide + * how to map the user's order nonce. + * @param isNonceInvalidated Whether the nonce is being invalidated + * @param signer Signer address + * @param orderNonce Maker user order nonce + * @param orderHash Hash of the order struct + * @dev If isNonceInvalidated is true, this function invalidates the user order nonce for future execution. + * If it is equal to false, this function maps the order hash for this user order nonce + * to prevent other order structs sharing the same order nonce to be executed. + */ + function _updateUserOrderNonce( + bool isNonceInvalidated, + address signer, + uint256 orderNonce, + bytes32 orderHash + ) private { + userOrderNonce[signer][orderNonce] = (isNonceInvalidated ? MAGIC_VALUE_ORDER_NONCE_EXECUTED : orderHash); + } + + /** + * @notice This function is private and used to verify the chain id, compute the digest, and verify the signature. + * @dev If chainId is not equal to the cached chain id, it would revert. + * @param computedHash Hash of order (maker bid or maker ask) or merkle root + * @param makerSignature Signature of the maker + * @param signer Signer address + */ + function _computeDigestAndVerify(bytes32 computedHash, bytes calldata makerSignature, address signer) private view { + if (chainId == block.chainid) { + // \x19\x01 is the standard encoding prefix + SignatureCheckerCalldata.verify( + keccak256(abi.encodePacked("\x19\x01", domainSeparator, computedHash)), + signer, + makerSignature + ); + } else { + revert ChainIdInvalid(); + } + } + + /** + * @notice This function is private and called to verify whether the merkle proofs provided for the order hash + * are correct or verify the order hash if the order is not part of a merkle tree. + * @param merkleTree Merkle tree + * @param orderHash Order hash (can be maker bid hash or maker ask hash) + * @param signature Maker order signature + * @param signer Maker address + * @dev It verifies (1) merkle proof (if necessary) (2) signature is from the expected signer + */ + function _verifyMerkleProofOrOrderHash( + OrderStructs.MerkleTree calldata merkleTree, + bytes32 orderHash, + bytes calldata signature, + address signer + ) private view { + uint256 proofLength = merkleTree.proof.length; + + if (proofLength != 0) { + if (proofLength > MAX_CALLDATA_PROOF_LENGTH) { + revert MerkleProofTooLarge(proofLength); + } + + if (!MerkleProofCalldataWithNodes.verifyCalldata(merkleTree.proof, merkleTree.root, orderHash)) { + revert MerkleProofInvalid(); + } + + orderHash = hashBatchOrder(merkleTree.root, proofLength); + } + + _computeDigestAndVerify(orderHash, signature, signer); + } +} diff --git a/contracts/src/marketplace/NonceManager.sol b/contracts/src/marketplace/NonceManager.sol new file mode 100644 index 00000000..bc3dda7a --- /dev/null +++ b/contracts/src/marketplace/NonceManager.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Interfaces and errors +import { INonceManager } from "./interfaces/INonceManager.sol"; +import { LengthsInvalid } from "./errors/SharedErrors.sol"; + +/** + * @title NonceManager + * @notice This contract handles the nonce logic that is used for invalidating maker orders that exist off-chain. + * The nonce logic revolves around three parts at the user level: + * - order nonce (orders sharing an order nonce are conditional, OCO-like) + * - subset (orders can be grouped under a same subset) + * - bid/ask (all orders can be executed only if the bid/ask nonce matches the user's one on-chain) + * Only the order nonce is invalidated at the time of the execution of a maker order that contains it. + * @author LooksRare protocol team (👀,💎) + */ +contract NonceManager is INonceManager { + /** + * @notice Magic value nonce returned if executed (or cancelled). + */ + bytes32 public constant MAGIC_VALUE_ORDER_NONCE_EXECUTED = keccak256("ORDER_NONCE_EXECUTED"); + + /** + * @notice This tracks the bid and ask nonces for a user address. + */ + mapping(address => UserBidAskNonces) public userBidAskNonces; + + /** + * @notice This checks whether the order nonce for a user was executed or cancelled. + */ + mapping(address => mapping(uint256 => bytes32)) public userOrderNonce; + + /** + * @notice This checks whether the subset nonce for a user was cancelled. + */ + mapping(address => mapping(uint256 => bool)) public userSubsetNonce; + + /** + * @notice This function allows a user to cancel an array of order nonces. + * @param orderNonces Array of order nonces + * @dev It does not check the status of the nonces to save gas + * and to prevent revertion if one of the orders is filled in the same + * block. + */ + function cancelOrderNonces(uint256[] calldata orderNonces) external { + uint256 length = orderNonces.length; + if (length == 0) { + revert LengthsInvalid(); + } + + for (uint256 i; i < length; ) { + userOrderNonce[msg.sender][orderNonces[i]] = MAGIC_VALUE_ORDER_NONCE_EXECUTED; + unchecked { + ++i; + } + } + + emit OrderNoncesCancelled(msg.sender, orderNonces); + } + + /** + * @notice This function allows a user to cancel an array of subset nonces. + * @param subsetNonces Array of subset nonces + * @dev It does not check the status of the nonces to save gas. + */ + function cancelSubsetNonces(uint256[] calldata subsetNonces) external { + uint256 length = subsetNonces.length; + + if (length == 0) { + revert LengthsInvalid(); + } + + for (uint256 i; i < length; ) { + userSubsetNonce[msg.sender][subsetNonces[i]] = true; + unchecked { + ++i; + } + } + + emit SubsetNoncesCancelled(msg.sender, subsetNonces); + } + + /** + * @notice This function increments a user's bid/ask nonces. + * @param bid Whether to increment the user bid nonce + * @param ask Whether to increment the user ask nonce + * @dev The logic for computing the quasi-random number is inspired by Seaport v1.2. + * The pseudo-randomness allows non-deterministic computation of the next ask/bid nonce. + * A deterministic increment would make the cancel-all process non-effective in certain cases + * (orders signed with a greater ask/bid nonce). + * The same quasi-random number is used for incrementing both the bid and ask nonces if both values + * are incremented in the same transaction. + * If this function is used twice in the same block, it will return the same quasiRandomNumber + * but this will not impact the overall business logic. + */ + function incrementBidAskNonces(bool bid, bool ask) external { + // Use second half of the previous block hash as a quasi-random number + uint256 quasiRandomNumber = uint256(blockhash(block.number - 1) >> 128); + uint256 newBidNonce = userBidAskNonces[msg.sender].bidNonce; + uint256 newAskNonce = userBidAskNonces[msg.sender].askNonce; + + if (bid) { + newBidNonce += quasiRandomNumber; + userBidAskNonces[msg.sender].bidNonce = newBidNonce; + } + + if (ask) { + newAskNonce += quasiRandomNumber; + userBidAskNonces[msg.sender].askNonce = newAskNonce; + } + + emit NewBidAskNonces(msg.sender, newBidNonce, newAskNonce); + } +} diff --git a/contracts/src/marketplace/ProtocolFeeRecipient.sol b/contracts/src/marketplace/ProtocolFeeRecipient.sol new file mode 100644 index 00000000..2d3d2c73 --- /dev/null +++ b/contracts/src/marketplace/ProtocolFeeRecipient.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { LowLevelERC20Transfer } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol"; +import { IWETH } from "@looksrare/contracts-libs/contracts/interfaces/generic/IWETH.sol"; +import { IERC20 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC20.sol"; + +/** + * @title ProtocolFeeRecipient + * @notice This contract is used to receive protocol fees and transfer them to the fee sharing setter. + * Fee sharing setter cannot receive ETH directly, so we need to use this contract as a middleman + * to convert ETH into WETH before sending it. + * @author LooksRare protocol team (👀,💎) + */ +contract ProtocolFeeRecipient is LowLevelERC20Transfer { + address public immutable FEE_SHARING_SETTER; + IWETH public immutable WETH; + + error NothingToTransfer(); + + constructor(address _feeSharingSetter, address _weth) { + FEE_SHARING_SETTER = _feeSharingSetter; + WETH = IWETH(_weth); + } + + function transferETH() external { + uint256 ethBalance = address(this).balance; + + if (ethBalance != 0) { + WETH.deposit{ value: ethBalance }(); + } + + uint256 wethBalance = IERC20(address(WETH)).balanceOf(address(this)); + + if (wethBalance == 0) { + revert NothingToTransfer(); + } + _executeERC20DirectTransfer(address(WETH), FEE_SHARING_SETTER, wethBalance); + } + + /** + * @param currency ERC20 currency address + */ + function transferERC20(address currency) external { + uint256 balance = IERC20(currency).balanceOf(address(this)); + if (balance == 0) { + revert NothingToTransfer(); + } + _executeERC20DirectTransfer(currency, FEE_SHARING_SETTER, balance); + } + + receive() external payable {} +} diff --git a/contracts/src/marketplace/StrategyManager.sol b/contracts/src/marketplace/StrategyManager.sol new file mode 100644 index 00000000..c80af8df --- /dev/null +++ b/contracts/src/marketplace/StrategyManager.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { CurrencyManager } from "./CurrencyManager.sol"; + +// Interfaces +import { IStrategy } from "./interfaces/IStrategy.sol"; +import { IStrategyManager } from "./interfaces/IStrategyManager.sol"; + +/** + * @title StrategyManager + * @notice This contract handles the addition and the update of execution strategies. + * @author LooksRare protocol team (👀,💎) + */ +contract StrategyManager is IStrategyManager, CurrencyManager { + /** + * @notice This variable keeps the count of how many strategies exist. + * It includes strategies that have been removed. + */ + uint256 private _countStrategies = 1; + + /** + * @notice This returns the strategy information for a strategy id. + */ + mapping(uint256 => Strategy) public strategyInfo; + + /** + * @notice Constructor + * @param _owner Owner address + */ + constructor(address _owner) CurrencyManager(_owner) { + strategyInfo[0] = Strategy({ + isActive: true, + standardProtocolFeeBp: 50, + minTotalFeeBp: 50, + maxProtocolFeeBp: 200, + selector: bytes4(0), + isMakerBid: false, + implementation: address(0) + }); + + emit NewStrategy(0, 50, 50, 200, bytes4(0), false, address(0)); + } + + /** + * @notice This function allows the owner to add a new execution strategy to the protocol. + * @param standardProtocolFeeBp Standard protocol fee (in basis point) + * @param minTotalFeeBp Minimum total fee (in basis point) + * @param maxProtocolFeeBp Maximum protocol fee (in basis point) + * @param selector Function selector for the strategy + * @param isMakerBid Whether the function selector is for maker bid + * @param implementation Implementation address + * @dev Strategies have an id that is incremental. + * Only callable by owner. + */ + function addStrategy( + uint16 standardProtocolFeeBp, + uint16 minTotalFeeBp, + uint16 maxProtocolFeeBp, + bytes4 selector, + bool isMakerBid, + address implementation + ) external onlyOwner { + if (minTotalFeeBp > maxProtocolFeeBp || standardProtocolFeeBp > minTotalFeeBp || maxProtocolFeeBp > 500) { + revert StrategyProtocolFeeTooHigh(); + } + + if (selector == bytes4(0)) { + revert StrategyHasNoSelector(); + } + + if (!IStrategy(implementation).isLooksRareV2Strategy()) { + revert NotV2Strategy(); + } + + strategyInfo[_countStrategies] = Strategy({ + isActive: true, + standardProtocolFeeBp: standardProtocolFeeBp, + minTotalFeeBp: minTotalFeeBp, + maxProtocolFeeBp: maxProtocolFeeBp, + selector: selector, + isMakerBid: isMakerBid, + implementation: implementation + }); + + emit NewStrategy( + _countStrategies++, + standardProtocolFeeBp, + minTotalFeeBp, + maxProtocolFeeBp, + selector, + isMakerBid, + implementation + ); + } + + /** + * @notice This function allows the owner to update parameters for an existing execution strategy. + * @param strategyId Strategy id + * @param isActive Whether the strategy must be active + * @param newStandardProtocolFee New standard protocol fee (in basis point) + * @param newMinTotalFee New minimum total fee (in basis point) + * @dev Only callable by owner. + */ + function updateStrategy( + uint256 strategyId, + bool isActive, + uint16 newStandardProtocolFee, + uint16 newMinTotalFee + ) external onlyOwner { + if (strategyId >= _countStrategies) { + revert StrategyNotUsed(); + } + + if (newMinTotalFee > strategyInfo[strategyId].maxProtocolFeeBp || newStandardProtocolFee > newMinTotalFee) { + revert StrategyProtocolFeeTooHigh(); + } + + strategyInfo[strategyId].isActive = isActive; + strategyInfo[strategyId].standardProtocolFeeBp = newStandardProtocolFee; + strategyInfo[strategyId].minTotalFeeBp = newMinTotalFee; + + emit StrategyUpdated(strategyId, isActive, newStandardProtocolFee, newMinTotalFee); + } +} diff --git a/contracts/src/marketplace/TransferManager.sol b/contracts/src/marketplace/TransferManager.sol new file mode 100644 index 00000000..f0015e67 --- /dev/null +++ b/contracts/src/marketplace/TransferManager.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { OwnableTwoSteps } from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; +import { LowLevelERC721Transfer } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC721Transfer.sol"; +import { LowLevelERC1155Transfer } from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC1155Transfer.sol"; + +// Interfaces and errors +import { ITransferManager } from "./interfaces/ITransferManager.sol"; +import { AmountInvalid, LengthsInvalid } from "./errors/SharedErrors.sol"; + +// Libraries +import { OrderStructs } from "./libraries/OrderStructs.sol"; + +// Enums +import { CollectionType } from "./enums/CollectionType.sol"; + +/** + * @title TransferManager + * @notice This contract provides the transfer functions for ERC721/ERC1155/Hypercert/Hyperboard for contracts that require them. + * Collection type "0" refers to ERC721 transfer functions. + * Collection type "1" refers to ERC1155 transfer functions. + * Collection type "2" refers to Hypercert transfer functions. + * Collection type "3" refers to Hyperboard transfer functions. + * @dev "Safe" transfer functions for ERC721 are not implemented since they come with added gas costs + * to verify if the recipient is a contract as it requires verifying the receiver interface is valid. + * @author LooksRare protocol team (👀,💎) + */ +// TODO Needs to be updated to split a fraction and transfer the new fraction to the bidder +contract TransferManager is ITransferManager, LowLevelERC721Transfer, LowLevelERC1155Transfer, OwnableTwoSteps { + /** + * @notice This returns whether the user has approved the operator address. + * The first address is the user and the second address is the operator (e.g. LooksRareProtocol). + */ + mapping(address => mapping(address => bool)) public hasUserApprovedOperator; + + /** + * @notice This returns whether the operator address is allowed by this contract's owner. + */ + mapping(address => bool) public isOperatorAllowed; + + /** + * @notice Constructor + * @param _owner Owner address + */ + constructor(address _owner) OwnableTwoSteps(_owner) {} + + /** + * @notice This function transfers items for a single ERC721 collection. + * @param collection Collection address + * @param from Sender address + * @param to Recipient address + * @param itemIds Array of itemIds + * @param amounts Array of amounts + */ + function transferItemsERC721( + address collection, + address from, + address to, + uint256[] calldata itemIds, + uint256[] calldata amounts + ) external { + uint256 length = itemIds.length; + if (length == 0) { + revert LengthsInvalid(); + } + + _isOperatorValidForTransfer(from, msg.sender); + + for (uint256 i; i < length; ) { + if (amounts[i] != 1) { + revert AmountInvalid(); + } + _executeERC721TransferFrom(collection, from, to, itemIds[i]); + unchecked { + ++i; + } + } + } + + /** + * @notice This function transfers items for a single ERC1155 collection. + * @param collection Collection address + * @param from Sender address + * @param to Recipient address + * @param itemIds Array of itemIds + * @param amounts Array of amounts + * @dev It does not allow batch transferring if from = msg.sender since native function should be used. + */ + function transferItemsERC1155( + address collection, + address from, + address to, + uint256[] calldata itemIds, + uint256[] calldata amounts + ) external { + uint256 length = itemIds.length; + + if (length == 0 || amounts.length != length) { + revert LengthsInvalid(); + } + + _isOperatorValidForTransfer(from, msg.sender); + + if (length == 1) { + if (amounts[0] == 0) { + revert AmountInvalid(); + } + _executeERC1155SafeTransferFrom(collection, from, to, itemIds[0], amounts[0]); + } else { + for (uint256 i; i < length; ) { + if (amounts[i] == 0) { + revert AmountInvalid(); + } + + unchecked { + ++i; + } + } + _executeERC1155SafeBatchTransferFrom(collection, from, to, itemIds, amounts); + } + } + + /** + * @notice This function transfers items for a single Hypercert. + * @param collection Collection address + * @param from Sender address + * @param to Recipient address + * @param itemIds Array of itemIds + * @param amounts Array of amounts + * @dev It does not allow batch transferring if from = msg.sender since native function should be used. + */ + function transferItemsHypercert( + address collection, + address from, + address to, + uint256[] calldata itemIds, + uint256[] calldata amounts + ) external { + uint256 length = itemIds.length; + + if (length == 0 || amounts.length != length) { + revert LengthsInvalid(); + } + + _isOperatorValidForTransfer(from, msg.sender); + + if (length == 1) { + if (amounts[0] == 0) { + revert AmountInvalid(); + } + _executeERC1155SafeTransferFrom(collection, from, to, itemIds[0], amounts[0]); + } else { + for (uint256 i; i < length; ) { + if (amounts[i] == 0) { + revert AmountInvalid(); + } + + unchecked { + ++i; + } + } + _executeERC1155SafeBatchTransferFrom(collection, from, to, itemIds, amounts); + } + } + + /** + * @notice This function transfers items for a single Hyperboard. + * @param collection Collection address + * @param from Sender address + * @param to Recipient address + * @param itemIds Array of itemIds + * @param amounts Array of amounts + * @dev It does not allow batch transferring if from = msg.sender since native function should be used. + */ + function transferItemsHyperboard( + address collection, + address from, + address to, + uint256[] calldata itemIds, + uint256[] calldata amounts + ) external { + uint256 length = itemIds.length; + + if (length == 0 || amounts.length != length) { + revert LengthsInvalid(); + } + + _isOperatorValidForTransfer(from, msg.sender); + + if (length == 1) { + if (amounts[0] == 0) { + revert AmountInvalid(); + } + _executeERC1155SafeTransferFrom(collection, from, to, itemIds[0], amounts[0]); + } else { + for (uint256 i; i < length; ) { + if (amounts[i] == 0) { + revert AmountInvalid(); + } + + unchecked { + ++i; + } + } + _executeERC1155SafeBatchTransferFrom(collection, from, to, itemIds, amounts); + } + } + + /** + * @notice This function transfers items across an array of collections that can be both ERC721 and ERC1155. + * @param items Array of BatchTransferItem + * @param from Sender address + * @param to Recipient address + */ + function transferBatchItemsAcrossCollections( + BatchTransferItem[] calldata items, + address from, + address to + ) external { + uint256 itemsLength = items.length; + + if (itemsLength == 0) { + revert LengthsInvalid(); + } + + if (from != msg.sender) { + _isOperatorValidForTransfer(from, msg.sender); + } + + for (uint256 i; i < itemsLength; ) { + uint256[] calldata itemIds = items[i].itemIds; + uint256 itemIdsLengthForSingleCollection = itemIds.length; + uint256[] calldata amounts = items[i].amounts; + + if (itemIdsLengthForSingleCollection == 0 || amounts.length != itemIdsLengthForSingleCollection) { + revert LengthsInvalid(); + } + + CollectionType collectionType = items[i].collectionType; + if (collectionType == CollectionType.ERC721) { + for (uint256 j; j < itemIdsLengthForSingleCollection; ) { + if (amounts[j] != 1) { + revert AmountInvalid(); + } + _executeERC721TransferFrom(items[i].collection, from, to, itemIds[j]); + unchecked { + ++j; + } + } + } else if (collectionType == CollectionType.ERC1155) { + for (uint256 j; j < itemIdsLengthForSingleCollection; ) { + if (amounts[j] == 0) { + revert AmountInvalid(); + } + + unchecked { + ++j; + } + } + _executeERC1155SafeBatchTransferFrom(items[i].collection, from, to, itemIds, amounts); + } + + unchecked { + ++i; + } + } + } + + /** + * @notice This function allows a user to grant approvals for an array of operators. + * Users cannot grant approvals if the operator is not allowed by this contract's owner. + * @param operators Array of operator addresses + * @dev Each operator address must be globally allowed to be approved. + */ + function grantApprovals(address[] calldata operators) external { + uint256 length = operators.length; + + if (length == 0) { + revert LengthsInvalid(); + } + + for (uint256 i; i < length; ) { + address operator = operators[i]; + + if (!isOperatorAllowed[operator]) { + revert OperatorNotAllowed(); + } + + if (hasUserApprovedOperator[msg.sender][operator]) { + revert OperatorAlreadyApprovedByUser(); + } + + hasUserApprovedOperator[msg.sender][operator] = true; + + unchecked { + ++i; + } + } + + emit ApprovalsGranted(msg.sender, operators); + } + + /** + * @notice This function allows a user to revoke existing approvals for an array of operators. + * @param operators Array of operator addresses + * @dev Each operator address must be approved at the user level to be revoked. + */ + function revokeApprovals(address[] calldata operators) external { + uint256 length = operators.length; + if (length == 0) { + revert LengthsInvalid(); + } + + for (uint256 i; i < length; ) { + address operator = operators[i]; + + if (!hasUserApprovedOperator[msg.sender][operator]) { + revert OperatorNotApprovedByUser(); + } + + delete hasUserApprovedOperator[msg.sender][operator]; + unchecked { + ++i; + } + } + + emit ApprovalsRemoved(msg.sender, operators); + } + + /** + * @notice This function allows an operator to be added for the shared transfer system. + * Once the operator is allowed, users can grant NFT approvals to this operator. + * @param operator Operator address to allow + * @dev Only callable by owner. + */ + function allowOperator(address operator) external onlyOwner { + if (isOperatorAllowed[operator]) { + revert OperatorAlreadyAllowed(); + } + + isOperatorAllowed[operator] = true; + + emit OperatorAllowed(operator); + } + + /** + * @notice This function allows the user to remove an operator for the shared transfer system. + * @param operator Operator address to remove + * @dev Only callable by owner. + */ + function removeOperator(address operator) external onlyOwner { + if (!isOperatorAllowed[operator]) { + revert OperatorNotAllowed(); + } + + delete isOperatorAllowed[operator]; + + emit OperatorRemoved(operator); + } + + /** + * @notice This function is internal and verifies whether the transfer + * (by an operator on behalf of a user) is valid. If not, it reverts. + * @param user User address + * @param operator Operator address + */ + function _isOperatorValidForTransfer(address user, address operator) private view { + if (isOperatorAllowed[operator] && hasUserApprovedOperator[user][operator]) { + return; + } + + revert TransferCallerInvalid(); + } +} diff --git a/contracts/src/marketplace/TransferSelectorNFT.sol b/contracts/src/marketplace/TransferSelectorNFT.sol new file mode 100644 index 00000000..5e504203 --- /dev/null +++ b/contracts/src/marketplace/TransferSelectorNFT.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Direct dependencies +import { PackableReentrancyGuard } from "@looksrare/contracts-libs/contracts/PackableReentrancyGuard.sol"; +import { ExecutionManager } from "./ExecutionManager.sol"; +import { TransferManager } from "./TransferManager.sol"; + +// Libraries +import { OrderStructs } from "./libraries/OrderStructs.sol"; + +// Enums +import { CollectionType } from "./enums/CollectionType.sol"; + +/** + * @title TransferSelectorNFT + * @notice This contract handles the logic for transferring non-fungible items. + * @author LooksRare protocol team (👀,💎) + */ +contract TransferSelectorNFT is ExecutionManager, PackableReentrancyGuard { + error UnsupportedCollectionType(); + /** + * @notice Transfer manager for ERC721 and ERC1155. + */ + + TransferManager public immutable transferManager; + + /** + * @notice Constructor + * @param _owner Owner address + * @param _protocolFeeRecipient Protocol fee recipient address + * @param _transferManager Address of the transfer manager for ERC721/ERC1155 + */ + constructor( + address _owner, + address _protocolFeeRecipient, + address _transferManager + ) ExecutionManager(_owner, _protocolFeeRecipient) { + transferManager = TransferManager(_transferManager); + } + + /** + * @notice This function is internal and used to transfer non-fungible tokens. + * @param collection Collection address + * @param collectionType Collection type (e.g. 0 = ERC721, 1 = ERC1155) + * @param sender Sender address + * @param recipient Recipient address + * @param itemIds Array of itemIds + * @param amounts Array of amounts + */ + function _transferNFT( + address collection, + CollectionType collectionType, + address sender, + address recipient, + uint256[] memory itemIds, + uint256[] memory amounts + ) internal { + if (collectionType == CollectionType.ERC721) { + transferManager.transferItemsERC721(collection, sender, recipient, itemIds, amounts); + } else if (collectionType == CollectionType.ERC1155) { + transferManager.transferItemsERC1155(collection, sender, recipient, itemIds, amounts); + } else if (collectionType == CollectionType.Hypercert) { + transferManager.transferItemsERC1155(collection, sender, recipient, itemIds, amounts); + } else if (collectionType == CollectionType.Hyperboard) { + revert UnsupportedCollectionType(); + } + } +} diff --git a/contracts/src/marketplace/constants/AssemblyConstants.sol b/contracts/src/marketplace/constants/AssemblyConstants.sol new file mode 100644 index 00000000..8557dece --- /dev/null +++ b/contracts/src/marketplace/constants/AssemblyConstants.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/* + * @dev error OrderInvalid() + * Memory layout: + * - 0x00: Left-padded selector (data begins at 0x1c) + * Revert buffer is memory[0x1c:0x20] + */ +uint256 constant OrderInvalid_error_selector = 0x2e0c0f71; +uint256 constant OrderInvalid_error_length = 0x04; + +/* + * @dev error CurrencyInvalid() + * Memory layout: + * - 0x00: Left-padded selector (data begins at 0x1c) + * Revert buffer is memory[0x1c:0x20] + */ +uint256 constant CurrencyInvalid_error_selector = 0x4f795487; +uint256 constant CurrencyInvalid_error_length = 0x04; + +/* + * @dev error OutsideOfTimeRange() + * Memory layout: + * - 0x00: Left-padded selector (data begins at 0x1c) + * Revert buffer is memory[0x1c:0x20] + */ +uint256 constant OutsideOfTimeRange_error_selector = 0x7476320f; +uint256 constant OutsideOfTimeRange_error_length = 0x04; + +/* + * @dev error NoSelectorForStrategy() + * Memory layout: + * - 0x00: Left-padded selector (data begins at 0x1c) + * Revert buffer is memory[0x1c:0x20] + */ +uint256 constant NoSelectorForStrategy_error_selector = 0xab984846; +uint256 constant NoSelectorForStrategy_error_length = 0x04; + +uint256 constant Error_selector_offset = 0x1c; + +uint256 constant OneWord = 0x20; diff --git a/contracts/src/marketplace/constants/NumericConstants.sol b/contracts/src/marketplace/constants/NumericConstants.sol new file mode 100644 index 00000000..63029eea --- /dev/null +++ b/contracts/src/marketplace/constants/NumericConstants.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @dev 100% represented in basis point is 10_000. + */ +uint256 constant ONE_HUNDRED_PERCENT_IN_BP = 10_000; + +/** + * @dev The maximum length of a proof for a batch order is 10. + * The maximum merkle tree that can used for signing has a height of + * 2**10 = 1_024. + */ +uint256 constant MAX_CALLDATA_PROOF_LENGTH = 10; diff --git a/contracts/src/marketplace/constants/ValidationCodeConstants.sol b/contracts/src/marketplace/constants/ValidationCodeConstants.sol new file mode 100644 index 00000000..73cbc996 --- /dev/null +++ b/contracts/src/marketplace/constants/ValidationCodeConstants.sol @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * 0. No error + */ + +/** + * @dev The maker order is expected to be valid. + * There can be other reasons that cause makers orders to be + * invalid (e.g. trading restrictions for the protocol, fallbacks). + */ +uint256 constant ORDER_EXPECTED_TO_BE_VALID = 0; + +/** + * 1. Strategy & currency-related codes + */ + +/** + * @dev The currency is not allowed in the protocol. + * This maker order could become valid only with owner action. + * If the order is a maker bid and currency = address(0), it is permanently invalid. + */ +uint256 constant CURRENCY_NOT_ALLOWED = 101; + +/** + * @dev The strategy is not implemented in the protocol. + * This maker order can become valid only with owner action. + */ +uint256 constant STRATEGY_NOT_IMPLEMENTED = 111; + +/** + * @dev The strategy is not for this quote type. + * This maker order can never become valid. + */ +uint256 constant STRATEGY_INVALID_QUOTE_TYPE = 112; + +/** + * @dev The strategy exists but is not currently active. + * This maker order can become valid again only with owner action. + */ +uint256 constant STRATEGY_NOT_ACTIVE = 113; + +/** + * 2. Maker order struct-related codes + */ + +/** + * @dev The maker order is permanently invalid for a standard sale (e.g. invalid collection type or amounts) + * This maker order cannot become valid again. + */ +uint256 constant MAKER_ORDER_INVALID_STANDARD_SALE = 201; + +/** + * @dev The maker order is permanently invalid for a non-standard sale strategy. + * This maker order cannot become valid again. + */ +uint256 constant MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE = 211; + +/** + * @dev The maker order is invalid due to a currency support. + * This maker order may become valid in the future depending on the strategy's currency support. + * Please refer to the strategy's implementation code. + */ +uint256 constant MAKER_ORDER_INVALID_CURRENCY_NON_STANDARD_SALE = 212; + +/** + * @dev The maker order is temporarily invalid due to a strategy-specific reason. + * This maker order may become valid in the future. + * Please refer to the strategy's implementation code. + */ +uint256 constant MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE = 213; + +/** + * 3. Nonce-related codes + */ + +/** + * @dev The signer's subset nonce is cancelled. + * This maker order will not become valid again. + */ +uint256 constant USER_SUBSET_NONCE_CANCELLED = 301; + +/** + * @dev The signer's order nonce is executed or cancelled. + * This maker order will not become valid again. + */ +uint256 constant USER_ORDER_NONCE_EXECUTED_OR_CANCELLED = 311; + +/** + * @dev The signer's order nonce is in partial fill status with an other order hash. + * This maker order will not become valid again. + */ +uint256 constant USER_ORDER_NONCE_IN_EXECUTION_WITH_OTHER_HASH = 312; + +/** + * @dev The signer's global bid nonce is not matching the order's bid nonce. + * This maker order will not become valid again. + */ +uint256 constant INVALID_USER_GLOBAL_BID_NONCE = 321; + +/** + * @dev The signer's global ask nonce is not matching the order's ask nonce. + * This maker order will not become valid again. + */ +uint256 constant INVALID_USER_GLOBAL_ASK_NONCE = 322; + +/** + * 4. Codes related to signatures (EOA, EIP-1271) and merkle tree computations + */ + +/** + * @dev The order hash proof is not in the merkle tree. + * This maker order is not valid with the set of merkle root and proofs. + * It cannot become valid with the current merkle proof and root. + */ +uint256 constant ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE = 401; + +/** + * @dev The merkle proof is too large to be verified according. + * There is a proof's size limit defined in the MerkleProofCalldataWithNodes. + * It cannot become valid with the current merkle proof and root. + */ +uint256 constant MERKLE_PROOF_PROOF_TOO_LARGE = 402; + +/** + * @dev The signature's length is invalid. + * The signature's length must be either 64 or 65 bytes. + * This maker order will never be valid. + */ +uint256 constant INVALID_SIGNATURE_LENGTH = 411; + +/** + * @dev The signature's s parameter is invalid. + * This maker order will never be valid. + */ +uint256 constant INVALID_S_PARAMETER_EOA = 412; + +/** + * @dev The signature's v parameter is invalid. + * It must be either equal to 27 or 28. + * This maker order will never be valid with this signature. + */ +uint256 constant INVALID_V_PARAMETER_EOA = 413; + +/** + * @dev The signer recovered (using ecrecover) is the null address. + * This maker order will never be valid with this signature. + */ +uint256 constant NULL_SIGNER_EOA = 414; + +/** + * @dev The recovered signer is not the target signer. + * This maker order will never be valid with this signature. + */ +uint256 constant INVALID_SIGNER_EOA = 415; + +/** + * @dev The signature is generated by a EIP1271 signer contract but the + * contract does not implement the required function to verify the signature. + */ +uint256 constant MISSING_IS_VALID_SIGNATURE_FUNCTION_EIP1271 = 421; + +/** + * @dev The signature by the EIP1271 signer contract is invalid. + * This maker order may become valid again depending on the implementation of the + * contract signing the order. + */ +uint256 constant SIGNATURE_INVALID_EIP1271 = 422; + +/** + * 5. Timestamp-related codes + */ + +/** + * @dev The start time is greater than the end time. + * This maker order will never be valid. + */ +uint256 constant START_TIME_GREATER_THAN_END_TIME = 501; + +/** + * @dev The block time is greater than the end time. + * This maker order will never be valid. + */ +uint256 constant TOO_LATE_TO_EXECUTE_ORDER = 502; + +/** + * @dev The block time is earlier than the start time. + * A buffer of 5 minutes is included for orders that are about to be valid. + * This maker order will become valid without any user action. + */ +uint256 constant TOO_EARLY_TO_EXECUTE_ORDER = 503; + +/** + * 6. Transfer-related (ERC20, ERC721, ERC1155 tokens), including transfers and approvals, codes. + */ + +/** + * @dev The same itemId is twice in the bundle. + * This maker order can be valid for ERC1155 collections but will never be valid for ERC721. + */ +uint256 constant SAME_ITEM_ID_IN_BUNDLE = 601; + +/** + * @dev The ERC20 balance of the signer (maker bid user) is inferior to the order bid price. + * This maker order can become valid without any user's action. + */ +uint256 constant ERC20_BALANCE_INFERIOR_TO_PRICE = 611; + +/** + * @dev The ERC20 approval amount of the signer (maker bid user) is inferior to the order bid price. + * This maker order can become valid only with the user's action. + */ +uint256 constant ERC20_APPROVAL_INFERIOR_TO_PRICE = 612; + +/** + * @dev The ERC721 itemId does not exist. + * This maker order can become valid if the item is created later. + */ +uint256 constant ERC721_ITEM_ID_DOES_NOT_EXIST = 621; + +/** + * @dev The ERC721 itemId is not owned by the signer (maker ask user). + * This maker order can become valid without any user's action. + */ +uint256 constant ERC721_ITEM_ID_NOT_IN_BALANCE = 622; + +/** + * @dev The transfer manager contract has not been approved by the ERC721 collection + * contract, either for the entire collection or the itemId. + * This maker order can become valid only with the user's action. + * The collection may not follow the ERC721 standard. + */ +uint256 constant ERC721_NO_APPROVAL_FOR_ALL_OR_ITEM_ID = 623; + +/** + * @dev The ERC1155 collection contract does not implement balanceOf. + */ +uint256 constant ERC1155_BALANCE_OF_DOES_NOT_EXIST = 631; + +/** + * @dev The ERC20 balance of the signer (maker ask user) is inferior to the amount + * required to be sold. + * This maker order can become valid without any user's action. + */ +uint256 constant ERC1155_BALANCE_OF_ITEM_ID_INFERIOR_TO_AMOUNT = 632; + +/** + * @dev The ERC1155 collection contract does not implement isApprovedForAll. + * The collection may not follow the ERC1155 standard. + */ +uint256 constant ERC1155_IS_APPROVED_FOR_ALL_DOES_NOT_EXIST = 633; + +/** + * @dev The transfer manager contract has not been approved by the ERC1155 + * collection contract. + * This maker order can become valid only with the user's action. + */ +uint256 constant ERC1155_NO_APPROVAL_FOR_ALL = 634; + +/** + * 7. Asset-type codes + */ + +/** + * @dev The collection type specified in the order seems incorrect. + * It is expected to be collectionType = 0. + */ +uint256 constant POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC721 = 701; + +/** + * @dev The collection type specified in the order seems incorrect. + * It is expected to be collectionType = 1. + */ +uint256 constant POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC1155 = 702; + +/** + * 8. Transfer manager-related codes + */ + +/** + * @dev The user has not approved the protocol to transfer NFTs on behalf + * of the user. + * This maker order can become valid only with the user's action. + */ +uint256 constant NO_TRANSFER_MANAGER_APPROVAL_BY_USER_FOR_EXCHANGE = 801; + +/** + * @dev The transfer manager's owner has revoked the ability to transfer NFTs + * on behalf of all users that have also approved the protocol. + * This maker order can become valid again only with owner action. + */ +uint256 constant TRANSFER_MANAGER_APPROVAL_REVOKED_BY_OWNER_FOR_EXCHANGE = 802; + +/** + * 9. Creator fee-related codes + */ + +/** + * @dev The collection contract has a flexible royalty fee structure that + * prevents this bundle to be traded. + * It applies at the protocol level. + * For instance, 2 items in a bundle have different creator recipients. + */ +uint256 constant BUNDLE_ERC2981_NOT_SUPPORTED = 901; + +/** + * @dev The creator fee applied at the protocol is higher than the threshold + * allowed. The transaction will revert. + * It applies at the protocol level. + * This maker order can become valid only with the creator's action. + */ +uint256 constant CREATOR_FEE_TOO_HIGH = 902; diff --git a/contracts/src/marketplace/enums/CollectionType.sol b/contracts/src/marketplace/enums/CollectionType.sol new file mode 100644 index 00000000..d95a2dc7 --- /dev/null +++ b/contracts/src/marketplace/enums/CollectionType.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @notice CollectionType is used in OrderStructs.Maker's collectionType to determine the collection type being traded. + */ +//TODO remove 721??? +enum CollectionType { + ERC721, + ERC1155, + Hypercert, + Hyperboard +} diff --git a/contracts/src/marketplace/enums/QuoteType.sol b/contracts/src/marketplace/enums/QuoteType.sol new file mode 100644 index 00000000..2cb766d7 --- /dev/null +++ b/contracts/src/marketplace/enums/QuoteType.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @notice QuoteType is used in OrderStructs.Maker's quoteType to determine whether the maker order is a bid or an ask. + */ +enum QuoteType { + Bid, + Ask +} diff --git a/contracts/src/marketplace/errors/ChainlinkErrors.sol b/contracts/src/marketplace/errors/ChainlinkErrors.sol new file mode 100644 index 00000000..bb21b470 --- /dev/null +++ b/contracts/src/marketplace/errors/ChainlinkErrors.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @notice It is returned if the Chainlink price is invalid (e.g. negative). + */ +error ChainlinkPriceInvalid(); + +/** + * @notice It is returned if the decimals from the NFT floor price feed is invalid. + * Chainlink price feeds are expected to have 18 decimals. + * @dev It can only be returned for owner operations. + */ +error DecimalsInvalid(); + +/** + * @notice It is returned if the fixed discount for a maker bid is greater than floor price. + */ +error DiscountGreaterThanFloorPrice(); + +/** + * @notice It is returned if the latency tolerance is set too high (i.e. greater than 3,600 sec). + */ +error LatencyToleranceTooHigh(); + +/** + * @notice It is returned if the price feed for a collection is already set. + * @dev It can only be returned for owner operations. + */ +error PriceFeedAlreadySet(); + +/** + * @notice It is returned when the price feed is not available. + */ +error PriceFeedNotAvailable(); + +/** + * @notice It is returned if the current block time relative to the latest price's update time + * is greater than the latency tolerance. + */ +error PriceNotRecentEnough(); diff --git a/contracts/src/marketplace/errors/SharedErrors.sol b/contracts/src/marketplace/errors/SharedErrors.sol new file mode 100644 index 00000000..bb123902 --- /dev/null +++ b/contracts/src/marketplace/errors/SharedErrors.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @notice It is returned if the amount is invalid. + * For ERC721, any number that is not 1. For ERC1155, if amount is 0. + */ +error AmountInvalid(); + +/** + * @notice It is returned if the ask price is too high for the bid user. + */ +error AskTooHigh(); + +/** + * @notice It is returned if the bid price is too low for the ask user. + */ +error BidTooLow(); + +/** + * @notice It is returned if the function cannot be called by the sender. + */ +error CallerInvalid(); + +/** + * @notice It is returned if the currency is invalid. + */ +error CurrencyInvalid(); + +/** + * @notice The function selector is invalid for this strategy implementation. + */ +error FunctionSelectorInvalid(); + +/** + * @notice It is returned if there is either a mismatch or an error in the length of the array(s). + */ +error LengthsInvalid(); + +/** + * @notice It is returned if the merkle proof provided is invalid. + */ +error MerkleProofInvalid(); + +/** + * @notice It is returned if the length of the merkle proof provided is greater than tolerated. + * @param length Proof length + */ +error MerkleProofTooLarge(uint256 length); + +/** + * @notice It is emitted if the call recipient is not a contract. + */ +error NotAContract(); + +/** + * @notice It is returned if the order is permanently invalid. + * There may be an issue with the order formatting. + */ +error OrderInvalid(); + +/** + * @notice It is returned if the maker quote type is invalid. + */ +error QuoteTypeInvalid(); diff --git a/contracts/src/marketplace/executionStrategies/BaseStrategy.sol b/contracts/src/marketplace/executionStrategies/BaseStrategy.sol new file mode 100644 index 00000000..75ce5f87 --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/BaseStrategy.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Interfaces +import { IStrategy } from "../interfaces/IStrategy.sol"; + +// Assembly constants +import { OrderInvalid_error_selector } from "../constants/AssemblyConstants.sol"; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// Enums +import { CollectionType } from "../enums/CollectionType.sol"; + +/** + * @title BaseStrategy + * @author LooksRare protocol team (👀,💎) + */ +abstract contract BaseStrategy is IStrategy { + /** + * @inheritdoc IStrategy + */ + function isLooksRareV2Strategy() external pure override returns (bool) { + return true; + } + + /** + * @dev This is equivalent to + * if (amount == 0 || (amount != 1 && collectionType == 0)) { + * return (0, OrderInvalid.selector); + * } + * @dev OrderInvalid_error_selector is a left-padded 4 bytes. If the error selector is returned + * instead of reverting, the error selector needs to be right-padded by + * 28 bytes. Therefore it needs to be left shifted by 28 x 8 = 224 bits. + */ + function _validateAmountNoRevert(uint256 amount, CollectionType collectionType) internal pure { + assembly { + if or(iszero(amount), and(xor(amount, 1), iszero(collectionType))) { + mstore(0x00, 0x00) + mstore(0x20, shl(224, OrderInvalid_error_selector)) + return(0, 0x40) + } + } + } +} diff --git a/contracts/src/marketplace/executionStrategies/Chainlink/BaseStrategyChainlinkPriceLatency.sol b/contracts/src/marketplace/executionStrategies/Chainlink/BaseStrategyChainlinkPriceLatency.sol new file mode 100644 index 00000000..f95a5c12 --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/Chainlink/BaseStrategyChainlinkPriceLatency.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { OwnableTwoSteps } from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; + +/** + * @title BaseStrategyChainlinkPriceLatency + * @notice This contract allows the owner to define the maximum acceptable Chainlink price latency. + * @author LooksRare protocol team (👀,💎) + */ +contract BaseStrategyChainlinkPriceLatency is OwnableTwoSteps { + /** + * @notice Maximum latency accepted after which + * the execution strategy rejects the retrieved price. + * + * For ETH, it cannot be higher than 3,600 as Chainlink will at least update the + * price every 3,600 seconds, provided ETH's price does not deviate more than 0.5%. + * + * For NFTs, it cannot be higher than 86,400 as Chainlink will at least update the + * price every 86,400 seconds, provided ETH's price does not deviate more than 2%. + */ + uint256 public immutable maxLatency; + + /** + * @notice Constructor + * @param _owner Owner address + * @param _maxLatency Maximum price latency allowed + */ + constructor(address _owner, uint256 _maxLatency) OwnableTwoSteps(_owner) { + maxLatency = _maxLatency; + } +} diff --git a/contracts/src/marketplace/executionStrategies/Chainlink/StrategyChainlinkUSDDynamicAsk.sol b/contracts/src/marketplace/executionStrategies/Chainlink/StrategyChainlinkUSDDynamicAsk.sol new file mode 100644 index 00000000..ad6b9f9d --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/Chainlink/StrategyChainlinkUSDDynamicAsk.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../../libraries/OrderStructs.sol"; +import { CurrencyValidator } from "../../libraries/CurrencyValidator.sol"; + +// Interfaces +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +// Enums +import { QuoteType } from "../../enums/QuoteType.sol"; + +// Shared errors +import { BidTooLow, OrderInvalid, CurrencyInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "../../errors/SharedErrors.sol"; +import { ChainlinkPriceInvalid, PriceFeedNotAvailable, PriceNotRecentEnough } from "../../errors/ChainlinkErrors.sol"; + +// Base strategy contracts +import { BaseStrategy, IStrategy } from "../BaseStrategy.sol"; +import { BaseStrategyChainlinkPriceLatency } from "./BaseStrategyChainlinkPriceLatency.sol"; + +/** + * @title StrategyChainlinkUSDDynamicAsk + * @notice This contract allows a seller to sell an NFT priced in USD and the receivable amount to be in ETH. + * @author LooksRare protocol team (👀,💎) + */ +contract StrategyChainlinkUSDDynamicAsk is BaseStrategy, BaseStrategyChainlinkPriceLatency { + /** + * @dev It is possible to call priceFeed.decimals() to get the decimals, + * but to save gas, it is hard coded instead. + */ + uint256 public constant ETH_USD_PRICE_FEED_DECIMALS = 1e8; + + /** + * @notice Wrapped ether (WETH) address. + */ + address public immutable WETH; + + /** + * @notice ETH/USD Chainlink price feed + */ + AggregatorV3Interface public immutable priceFeed; + + /** + * @notice Constructor + * @param _weth Wrapped ether address + * @param _owner Owner address + * @param _priceFeed Address of the ETH/USD price feed + */ + constructor(address _owner, address _weth, address _priceFeed) BaseStrategyChainlinkPriceLatency(_owner, 3_600) { + WETH = _weth; + priceFeed = AggregatorV3Interface(_priceFeed); + } + + /** + * @notice This function validates the order under the context of the chosen strategy + * and returns the fulfillable items/amounts/price/nonce invalidation status. + * This strategy looks at the seller's desired sale price in USD and minimum sale price in ETH, + * converts the USD value into ETH using Chainlink's price feed and chooses the higher price. + * @param takerBid Taker bid struct (taker bid-specific parameters for the execution) + * @param makerAsk Maker ask struct (maker ask-specific parameters for the execution) + * @dev The client has to provide the seller's desired sale price in USD as the additionalParameters + */ + function executeStrategyWithTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk + ) + external + view + returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) + { + uint256 itemIdsLength = makerAsk.itemIds.length; + + if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { + revert OrderInvalid(); + } + + CurrencyValidator.allowNativeOrAllowedCurrency(makerAsk.currency, WETH); + + (, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData(); + + if (answer <= 0) { + revert ChainlinkPriceInvalid(); + } + + if (block.timestamp - updatedAt > maxLatency) { + revert PriceNotRecentEnough(); + } + + // The client has to provide a USD value that is augmented by 1e18. + uint256 desiredSalePriceInUSD = abi.decode(makerAsk.additionalParameters, (uint256)); + + uint256 ethPriceInUSD = uint256(answer); + uint256 minPriceInETH = makerAsk.price; + uint256 desiredSalePriceInETH = (desiredSalePriceInUSD * ETH_USD_PRICE_FEED_DECIMALS) / ethPriceInUSD; + + if (minPriceInETH >= desiredSalePriceInETH) { + price = minPriceInETH; + } else { + price = desiredSalePriceInETH; + } + + uint256 maxPrice = abi.decode(takerBid.additionalParameters, (uint256)); + if (maxPrice < price) { + revert BidTooLow(); + } + + itemIds = makerAsk.itemIds; + amounts = makerAsk.amounts; + isNonceInvalidated = true; + } + + /** + * @inheritdoc IStrategy + */ + function isMakerOrderValid( + OrderStructs.Maker calldata makerAsk, + bytes4 functionSelector + ) external view override returns (bool isValid, bytes4 errorSelector) { + if (functionSelector != StrategyChainlinkUSDDynamicAsk.executeStrategyWithTakerBid.selector) { + return (isValid, FunctionSelectorInvalid.selector); + } + + if (makerAsk.quoteType != QuoteType.Ask) { + return (isValid, QuoteTypeInvalid.selector); + } + + uint256 itemIdsLength = makerAsk.itemIds.length; + + if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { + return (isValid, OrderInvalid.selector); + } + + for (uint256 i; i < itemIdsLength; ) { + _validateAmountNoRevert(makerAsk.amounts[i], makerAsk.collectionType); + unchecked { + ++i; + } + } + + if (makerAsk.currency != address(0)) { + if (makerAsk.currency != WETH) { + return (isValid, CurrencyInvalid.selector); + } + } + + (, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData(); + + if (answer <= 0) { + return (isValid, ChainlinkPriceInvalid.selector); + } + + if (block.timestamp - updatedAt > maxLatency) { + return (isValid, PriceNotRecentEnough.selector); + } + + isValid = true; + } +} diff --git a/contracts/src/marketplace/executionStrategies/StrategyCollectionOffer.sol b/contracts/src/marketplace/executionStrategies/StrategyCollectionOffer.sol new file mode 100644 index 00000000..90b3b872 --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/StrategyCollectionOffer.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// OpenZeppelin's library for verifying Merkle proofs +import { MerkleProofMemory } from "../libraries/OpenZeppelin/MerkleProofMemory.sol"; + +// Enums +import { QuoteType } from "../enums/QuoteType.sol"; + +// Shared errors +import { OrderInvalid, FunctionSelectorInvalid, MerkleProofInvalid, QuoteTypeInvalid } from "../errors/SharedErrors.sol"; + +// Base strategy contracts +import { BaseStrategy, IStrategy } from "./BaseStrategy.sol"; + +/** + * @title StrategyCollectionOffer + * @notice This contract offers execution strategies for users to create maker bid offers for items in a collection. + * There are two available functions: + * 1. executeCollectionStrategyWithTakerAsk --> it applies to all itemIds in a collection + * 2. executeCollectionStrategyWithTakerAskWithProof --> it allows adding merkle proof criteria. + * @notice The bidder can only bid on 1 item id at a time. + * 1. If ERC721, the amount must be 1. + * 2. If ERC1155, the amount can be greater than 1. + * @dev Use cases can include trait-based offers or rarity score offers. + * @author LooksRare protocol team (👀,💎) + */ +// TODO This allows for a buyer to declare a set of items they're willing to buy in a merkle tree +contract StrategyCollectionOffer is BaseStrategy { + /** + * @notice This function validates the order under the context of the chosen strategy and + * returns the fulfillable items/amounts/price/nonce invalidation status. + * This strategy executes a collection offer against a taker ask order without the need of merkle proofs. + * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) + * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) + */ + function executeCollectionStrategyWithTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid + ) + external + pure + returns (uint256 price, uint256[] memory itemIds, uint256[] calldata amounts, bool isNonceInvalidated) + { + price = makerBid.price; + amounts = makerBid.amounts; + + // A collection order can only be executable for 1 itemId but quantity to fill can vary + if (amounts.length != 1) { + revert OrderInvalid(); + } + + uint256 offeredItemId = abi.decode(takerAsk.additionalParameters, (uint256)); + itemIds = new uint256[](1); + itemIds[0] = offeredItemId; + isNonceInvalidated = true; + } + + /** + * @notice This function validates the order under the context of the chosen strategy + * and returns the fulfillable items/amounts/price/nonce invalidation status. + * This strategy executes a collection offer against a taker ask order with the need of a merkle proof. + * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) + * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) + * @dev The transaction reverts if the maker does not include a merkle root in the additionalParameters. + */ + function executeCollectionStrategyWithTakerAskWithProof( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid + ) + external + pure + returns (uint256 price, uint256[] memory itemIds, uint256[] calldata amounts, bool isNonceInvalidated) + { + price = makerBid.price; + amounts = makerBid.amounts; + + // A collection order can only be executable for 1 itemId but the actual quantity to fill can vary + if (amounts.length != 1) { + revert OrderInvalid(); + } + + (uint256 offeredItemId, bytes32[] memory proof) = abi.decode( + takerAsk.additionalParameters, + (uint256, bytes32[]) + ); + itemIds = new uint256[](1); + itemIds[0] = offeredItemId; + isNonceInvalidated = true; + + bytes32 root = abi.decode(makerBid.additionalParameters, (bytes32)); + bytes32 node = keccak256(abi.encodePacked(offeredItemId)); + + // Verify the merkle root for the given merkle proof + if (!MerkleProofMemory.verify(proof, root, node)) { + revert MerkleProofInvalid(); + } + } + + /** + * @inheritdoc IStrategy + */ + function isMakerOrderValid( + OrderStructs.Maker calldata makerBid, + bytes4 functionSelector + ) external pure override returns (bool isValid, bytes4 errorSelector) { + if ( + functionSelector != StrategyCollectionOffer.executeCollectionStrategyWithTakerAskWithProof.selector && + functionSelector != StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector + ) { + return (isValid, FunctionSelectorInvalid.selector); + } + + if (makerBid.quoteType != QuoteType.Bid) { + return (isValid, QuoteTypeInvalid.selector); + } + + if (makerBid.amounts.length != 1) { + return (isValid, OrderInvalid.selector); + } + + _validateAmountNoRevert(makerBid.amounts[0], makerBid.collectionType); + + // If no root is provided or invalid length, it should be invalid. + // @dev It does not mean the merkle root is valid against a specific itemId that exists in the collection. + if ( + functionSelector == StrategyCollectionOffer.executeCollectionStrategyWithTakerAskWithProof.selector && + makerBid.additionalParameters.length != 32 + ) { + return (isValid, OrderInvalid.selector); + } + + isValid = true; + } +} diff --git a/contracts/src/marketplace/executionStrategies/StrategyDutchAuction.sol b/contracts/src/marketplace/executionStrategies/StrategyDutchAuction.sol new file mode 100644 index 00000000..377539c0 --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/StrategyDutchAuction.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// Enums +import { QuoteType } from "../enums/QuoteType.sol"; + +// Shared errors +import { BidTooLow, OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "../errors/SharedErrors.sol"; + +// Base strategy contracts +import { BaseStrategy, IStrategy } from "./BaseStrategy.sol"; + +/** + * @title StrategyDutchAuction + * @notice This contract offers a single execution strategy for users to create Dutch auctions. + * @author LooksRare protocol team (👀,💎) + */ +contract StrategyDutchAuction is BaseStrategy { + /** + * @notice This function validates the order under the context of the chosen strategy + * and returns the fulfillable items/amounts/price/nonce invalidation status. + * The execution price set by the seller decreases linearly within the defined period. + * @param takerBid Taker bid struct (taker ask-specific parameters for the execution) + * @param makerAsk Maker ask struct (maker bid-specific parameters for the execution) + * @dev The client has to provide the seller's desired initial start price as the additionalParameters. + */ + function executeStrategyWithTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk + ) + external + view + returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) + { + uint256 itemIdsLength = makerAsk.itemIds.length; + + if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { + revert OrderInvalid(); + } + + uint256 startPrice = abi.decode(makerAsk.additionalParameters, (uint256)); + + if (startPrice < makerAsk.price) { + revert OrderInvalid(); + } + + uint256 startTime = makerAsk.startTime; + uint256 endTime = makerAsk.endTime; + + price = + ((endTime - block.timestamp) * startPrice + (block.timestamp - startTime) * makerAsk.price) / + (endTime - startTime); + + uint256 maxPrice = abi.decode(takerBid.additionalParameters, (uint256)); + if (maxPrice < price) { + revert BidTooLow(); + } + + isNonceInvalidated = true; + + itemIds = makerAsk.itemIds; + amounts = makerAsk.amounts; + } + + /** + * @inheritdoc IStrategy + */ + function isMakerOrderValid( + OrderStructs.Maker calldata makerAsk, + bytes4 functionSelector + ) external pure override returns (bool isValid, bytes4 errorSelector) { + if (functionSelector != StrategyDutchAuction.executeStrategyWithTakerBid.selector) { + return (isValid, FunctionSelectorInvalid.selector); + } + + if (makerAsk.quoteType != QuoteType.Ask) { + return (isValid, QuoteTypeInvalid.selector); + } + + uint256 itemIdsLength = makerAsk.itemIds.length; + + if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { + return (isValid, OrderInvalid.selector); + } + + for (uint256 i; i < itemIdsLength; ) { + _validateAmountNoRevert(makerAsk.amounts[i], makerAsk.collectionType); + unchecked { + ++i; + } + } + + uint256 startPrice = abi.decode(makerAsk.additionalParameters, (uint256)); + + if (startPrice < makerAsk.price) { + return (isValid, OrderInvalid.selector); + } + + isValid = true; + } +} diff --git a/contracts/src/marketplace/executionStrategies/StrategyItemIdsRange.sol b/contracts/src/marketplace/executionStrategies/StrategyItemIdsRange.sol new file mode 100644 index 00000000..81ffd51d --- /dev/null +++ b/contracts/src/marketplace/executionStrategies/StrategyItemIdsRange.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// Enums +import { QuoteType } from "../enums/QuoteType.sol"; + +// Shared errors +import { OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "../errors/SharedErrors.sol"; + +// Base strategy contracts +import { BaseStrategy, IStrategy } from "./BaseStrategy.sol"; + +/** + * @title StrategyItemIdsRange + * @notice This contract offers a single execution strategy for users to bid on + * a specific amount of items in a range bounded by 2 item ids. + * @author LooksRare protocol team (👀,💎) + */ +// TODO this enables bidding on all fractions belonging to a claimID +contract StrategyItemIdsRange is BaseStrategy { + /** + * @notice This function validates the order under the context of the chosen strategy + * and returns the fulfillable items/amounts/price/nonce invalidation status. + * With this strategy, the bidder picks a item id range (e.g. 1-100) + * and a seller can fulfill the order with any tokens within the specified id range. + * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) + * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) + */ + function executeStrategyWithTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid + ) + external + pure + returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) + { + (itemIds, amounts) = abi.decode(takerAsk.additionalParameters, (uint256[], uint256[])); + uint256 length = itemIds.length; + if (length != amounts.length) { + revert OrderInvalid(); + } + + (uint256 minItemId, uint256 maxItemId, uint256 desiredAmount) = abi.decode( + makerBid.additionalParameters, + (uint256, uint256, uint256) + ); + + if (minItemId >= maxItemId || desiredAmount == 0) { + revert OrderInvalid(); + } + + uint256 totalOfferedAmount; + uint256 lastItemId; + + for (uint256 i; i < length; ) { + uint256 offeredItemId = itemIds[i]; + // Force the client to sort the item ids in ascending order, + // in order to prevent taker ask from providing duplicated + // item ids + if (offeredItemId <= lastItemId) { + if (i != 0) { + revert OrderInvalid(); + } + } + + if (offeredItemId < minItemId || offeredItemId > maxItemId) { + revert OrderInvalid(); + } + + totalOfferedAmount += amounts[i]; + + lastItemId = offeredItemId; + + unchecked { + ++i; + } + } + + if (totalOfferedAmount != desiredAmount) { + revert OrderInvalid(); + } + + price = makerBid.price; + isNonceInvalidated = true; + } + + /** + * @inheritdoc IStrategy + */ + function isMakerOrderValid( + OrderStructs.Maker calldata makerBid, + bytes4 functionSelector + ) external pure override returns (bool isValid, bytes4 errorSelector) { + if (functionSelector != StrategyItemIdsRange.executeStrategyWithTakerAsk.selector) { + return (isValid, FunctionSelectorInvalid.selector); + } + + if (makerBid.quoteType != QuoteType.Bid) { + return (isValid, QuoteTypeInvalid.selector); + } + + (uint256 minItemId, uint256 maxItemId, uint256 desiredAmount) = abi.decode( + makerBid.additionalParameters, + (uint256, uint256, uint256) + ); + + if (minItemId >= maxItemId || desiredAmount == 0) { + return (isValid, OrderInvalid.selector); + } + + isValid = true; + } +} diff --git a/contracts/src/marketplace/helpers/OrderValidatorV2A.sol b/contracts/src/marketplace/helpers/OrderValidatorV2A.sol new file mode 100644 index 00000000..2d917b34 --- /dev/null +++ b/contracts/src/marketplace/helpers/OrderValidatorV2A.sol @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC165 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC165.sol"; +import { IERC20 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC20.sol"; +import { IERC721 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC721.sol"; +import { IERC1155 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC1155.sol"; +import { IERC1271 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC1271.sol"; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; +import { MerkleProofCalldataWithNodes } from "../libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol"; + +// Interfaces +import { ICreatorFeeManager } from "../interfaces/ICreatorFeeManager.sol"; +import { IStrategy } from "../interfaces/IStrategy.sol"; +import { IRoyaltyFeeRegistry } from "../interfaces/IRoyaltyFeeRegistry.sol"; + +// Shared errors +import { OrderInvalid } from "../errors/SharedErrors.sol"; + +// Other dependencies +import { LooksRareProtocol } from "../LooksRareProtocol.sol"; +import { TransferManager } from "../TransferManager.sol"; + +// Constants +import "../constants/ValidationCodeConstants.sol"; +import { MAX_CALLDATA_PROOF_LENGTH, ONE_HUNDRED_PERCENT_IN_BP } from "../constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "../enums/CollectionType.sol"; +import { QuoteType } from "../enums/QuoteType.sol"; + +/** + * @title OrderValidatorV2A + * @notice This contract is used to check the validity of maker ask/bid orders in the LooksRareProtocol (v2). + * It performs checks for: + * 1. Protocol allowlist issues (i.e. currency or strategy not allowed) + * 2. Maker order-specific issues (e.g., order invalid due to format or other-strategy specific issues) + * 3. Nonce related issues (e.g., nonce executed or cancelled) + * 4. Signature related issues and merkle tree parameters + * 5. Timestamp related issues (e.g., order expired) + * 6. Asset-related issues for ERC20/ERC721/ERC1155 (approvals and balances) + * 7. Collection-type suggestions + * 8. Transfer manager related issues + * 9. Creator fee related issues (e.g., creator fee too high, ERC2981 bundles) + * @dev This version does not handle strategies with partial fills. + * @author LooksRare protocol team (👀,💎) + */ +//TODO this might need hypercerts specific changes like checking on amount of units in a fraction +contract OrderValidatorV2A { + using OrderStructs for OrderStructs.Maker; + + /** + * @notice ERC721 potential interfaceId. + */ + bytes4 public constant ERC721_INTERFACE_ID_1 = 0x5b5e139f; + + /** + * @notice ERC721 potential interfaceId. + */ + bytes4 public constant ERC721_INTERFACE_ID_2 = 0x80ac58cd; + + /** + * @notice ERC1155 interfaceId. + */ + bytes4 public constant ERC1155_INTERFACE_ID = 0xd9b67a26; + + /** + * @notice Magic value nonce returned if executed (or cancelled). + */ + bytes32 public constant MAGIC_VALUE_ORDER_NONCE_EXECUTED = keccak256("ORDER_NONCE_EXECUTED"); + + /** + * @notice Number of distinct criteria groups checked to evaluate the validity of an order. + */ + uint256 public constant CRITERIA_GROUPS = 9; + + /** + * @notice LooksRareProtocol domain separator. + */ + bytes32 public domainSeparator; + + /** + * @notice Maximum creator fee (in basis point). + */ + uint256 public maxCreatorFeeBp; + + /** + * @notice CreatorFeeManager. + */ + ICreatorFeeManager public creatorFeeManager; + + /** + * @notice LooksRareProtocol. + */ + LooksRareProtocol public looksRareProtocol; + + /** + * @notice TransferManager + */ + TransferManager public transferManager; + + /** + * @notice Constructor + * @param _looksRareProtocol LooksRare protocol address + * @dev It derives automatically other external variables such as the creator fee manager and domain separator. + */ + constructor(address _looksRareProtocol) { + looksRareProtocol = LooksRareProtocol(_looksRareProtocol); + transferManager = looksRareProtocol.transferManager(); + + _deriveProtocolParameters(); + } + + /** + * @notice Derive protocol parameters. Anyone can call this function. + * @dev It allows adjusting if the domain separator or creator fee manager address were to change. + */ + function deriveProtocolParameters() external { + _deriveProtocolParameters(); + } + + /** + * @notice This function verifies the validity of an array of maker orders. + * @param makerOrders Array of maker orders + * @param signatures Array of signatures + * @param merkleTrees Array of merkle trees + * @return validationCodes Arrays of validation codes + */ + function checkMultipleMakerOrderValidities( + OrderStructs.Maker[] calldata makerOrders, + bytes[] calldata signatures, + OrderStructs.MerkleTree[] calldata merkleTrees + ) external view returns (uint256[9][] memory validationCodes) { + uint256 length = makerOrders.length; + + validationCodes = new uint256[CRITERIA_GROUPS][](length); + + for (uint256 i; i < length; ) { + validationCodes[i] = checkMakerOrderValidity(makerOrders[i], signatures[i], merkleTrees[i]); + unchecked { + ++i; + } + } + } + + /** + * @notice This function verifies the validity of a maker order. + * @param makerOrder Maker struct + * @param signature Signature + * @param merkleTree Merkle tree + * @return validationCodes Array of validation codes + */ + function checkMakerOrderValidity( + OrderStructs.Maker calldata makerOrder, + bytes calldata signature, + OrderStructs.MerkleTree calldata merkleTree + ) public view returns (uint256[9] memory validationCodes) { + bytes32 orderHash = makerOrder.hash(); + + validationCodes[0] = _checkValidityCurrencyAndStrategy( + makerOrder.quoteType, + makerOrder.currency, + makerOrder.strategyId + ); + + // It will exit here if the strategy does not exist. + // However, if the strategy is implemented but invalid (except if wrong quote type), + // it can continue the validation process. + if (validationCodes[0] == STRATEGY_NOT_IMPLEMENTED || validationCodes[0] == STRATEGY_INVALID_QUOTE_TYPE) { + return validationCodes; + } + + uint256 validationCode1; + uint256[] memory itemIds; + uint256[] memory amounts; + uint256 price; + + if (makerOrder.quoteType == QuoteType.Ask) { + (validationCode1, itemIds, amounts, price) = _checkValidityMakerAskItemIdsAndAmountsAndPrice(makerOrder); + } else { + (validationCode1, itemIds, , price) = _checkValidityMakerBidItemIdsAndAmountsAndPrice(makerOrder); + } + + validationCodes[1] = validationCode1; + + validationCodes[2] = _checkValidityNonces( + makerOrder.quoteType, + makerOrder.signer, + makerOrder.globalNonce, + makerOrder.orderNonce, + makerOrder.subsetNonce, + orderHash + ); + + validationCodes[3] = _checkValidityMerkleProofAndOrderHash(merkleTree, orderHash, signature, makerOrder.signer); + validationCodes[4] = _checkValidityTimestamps(makerOrder.startTime, makerOrder.endTime); + + validationCodes[3] = _checkValidityMerkleProofAndOrderHash(merkleTree, orderHash, signature, makerOrder.signer); + validationCodes[4] = _checkValidityTimestamps(makerOrder.startTime, makerOrder.endTime); + + if (makerOrder.quoteType == QuoteType.Bid) { + validationCodes[5] = _checkValidityMakerBidERC20Assets(makerOrder.currency, makerOrder.signer, price); + } else { + validationCodes[5] = _checkValidityMakerAskNFTAssets( + makerOrder.collection, + makerOrder.collectionType, + makerOrder.signer, + itemIds, + amounts + ); + } + + validationCodes[6] = _checkIfPotentialInvalidCollectionTypes(makerOrder.collection, makerOrder.collectionType); + + if (makerOrder.quoteType == QuoteType.Bid) { + validationCodes[7] = ORDER_EXPECTED_TO_BE_VALID; + } else { + validationCodes[7] = _checkValidityTransferManagerApprovals(makerOrder.signer); + } + + validationCodes[8] = _checkValidityCreatorFee(makerOrder.collection, price, itemIds); + } + + /** + * @notice This function is private and is used to adjust the protocol parameters. + */ + function _deriveProtocolParameters() private { + domainSeparator = looksRareProtocol.domainSeparator(); + creatorFeeManager = looksRareProtocol.creatorFeeManager(); + maxCreatorFeeBp = looksRareProtocol.maxCreatorFeeBp(); + } + + /** + * @notice This function is private and verifies the validity of nonces for maker order. + * @param makerSigner Address of the maker signer + * @param globalNonce Global nonce + * @param orderNonce Order nonce + * @param subsetNonce Subset nonce + * @param orderHash Order hash + * @return validationCode Validation code + */ + function _checkValidityNonces( + QuoteType quoteType, + address makerSigner, + uint256 globalNonce, + uint256 orderNonce, + uint256 subsetNonce, + bytes32 orderHash + ) private view returns (uint256 validationCode) { + // 1. Check subset nonce + if (looksRareProtocol.userSubsetNonce(makerSigner, subsetNonce)) { + return USER_SUBSET_NONCE_CANCELLED; + } + + // 2. Check order nonce + bytes32 orderNonceStatus = looksRareProtocol.userOrderNonce(makerSigner, orderNonce); + + if (orderNonceStatus == MAGIC_VALUE_ORDER_NONCE_EXECUTED) { + return USER_ORDER_NONCE_EXECUTED_OR_CANCELLED; + } + + if (orderNonceStatus != bytes32(0) && orderNonceStatus != orderHash) { + return USER_ORDER_NONCE_IN_EXECUTION_WITH_OTHER_HASH; + } + + // 3. Check global nonces + (uint256 globalBidNonce, uint256 globalAskNonce) = looksRareProtocol.userBidAskNonces(makerSigner); + + if (quoteType == QuoteType.Bid && globalNonce != globalBidNonce) { + return INVALID_USER_GLOBAL_BID_NONCE; + } + if (quoteType == QuoteType.Ask && globalNonce != globalAskNonce) { + return INVALID_USER_GLOBAL_ASK_NONCE; + } + } + + /** + * @notice This function is private and verifies the validity of the currency and strategy. + * @param quoteType Quote type + * @param currency Address of the currency + * @param strategyId Strategy id + * @return validationCode Validation code + */ + function _checkValidityCurrencyAndStrategy( + QuoteType quoteType, + address currency, + uint256 strategyId + ) private view returns (uint256 validationCode) { + // 1. Verify whether the currency is allowed + if (!looksRareProtocol.isCurrencyAllowed(currency)) { + return CURRENCY_NOT_ALLOWED; + } + + if (currency == address(0) && quoteType == QuoteType.Bid) { + return CURRENCY_NOT_ALLOWED; + } + + // 2. Verify whether the strategy is valid + (bool strategyIsActive, , , , , bool strategyIsMakerBid, address strategyImplementation) = looksRareProtocol + .strategyInfo(strategyId); + + if (strategyId != 0 && strategyImplementation == address(0)) { + return STRATEGY_NOT_IMPLEMENTED; + } + + if (strategyId != 0) { + if ( + (strategyIsMakerBid && quoteType != QuoteType.Bid) || + (!strategyIsMakerBid && quoteType != QuoteType.Ask) + ) { + return STRATEGY_INVALID_QUOTE_TYPE; + } + } + + if (!strategyIsActive) { + return STRATEGY_NOT_ACTIVE; + } + } + + /** + * @notice This function verifies the validity for order timestamps. + * @param startTime Start time + * @param endTime End time + * @return validationCode Validation code + */ + function _checkValidityTimestamps( + uint256 startTime, + uint256 endTime + ) private view returns (uint256 validationCode) { + // @dev It is possible for startTime to be equal to endTime. + // If so, the execution only succeeds when the startTime = endTime = block.timestamp. + // For order invalidation, if the call succeeds, it is already too late for later execution since the + // next block will have a greater timestamp than the current one. + if (startTime >= endTime) { + return START_TIME_GREATER_THAN_END_TIME; + } + + if (endTime <= block.timestamp) { + return TOO_LATE_TO_EXECUTE_ORDER; + } + if (startTime >= block.timestamp + 5 minutes) { + return TOO_EARLY_TO_EXECUTE_ORDER; + } + } + + /** + * @notice This function is private and checks if the collection type may be potentially invalid. + * @param collection Address of the collection + * @param collectionType Collection type in the maker order + * @return validationCode Validation code + * @dev This function may return false positives. + * (i.e. collections that are tradable but do not implement the proper interfaceId). + * If ERC165 is not implemented, it will revert. + */ + function _checkIfPotentialInvalidCollectionTypes( + address collection, + CollectionType collectionType + ) private view returns (uint256 validationCode) { + if (collectionType == CollectionType.ERC721) { + bool isERC721 = IERC165(collection).supportsInterface(ERC721_INTERFACE_ID_1) || + IERC165(collection).supportsInterface(ERC721_INTERFACE_ID_2); + + if (!isERC721) { + return POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC721; + } + } else if (collectionType == CollectionType.ERC1155) { + if (!IERC165(collection).supportsInterface(ERC1155_INTERFACE_ID)) { + return POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC1155; + } + } + } + + /** + * @notice This function verifies that (1) ERC20 approvals + * and (2) balances are sufficient to process the maker bid order. + * @param currency Currency address + * @param user User address + * @param price Price (defined by the maker order) + * @return validationCode Validation code + */ + function _checkValidityMakerBidERC20Assets( + address currency, + address user, + uint256 price + ) private view returns (uint256 validationCode) { + if (currency != address(0)) { + if (IERC20(currency).balanceOf(user) < price) { + return ERC20_BALANCE_INFERIOR_TO_PRICE; + } + + if (IERC20(currency).allowance(user, address(looksRareProtocol)) < price) { + return ERC20_APPROVAL_INFERIOR_TO_PRICE; + } + } + } + + /** + * @notice This function verifies the validity of NFT assets (approvals, balances, and others). + * @param collection Collection address + * @param collectionType Collection type + * @param user User address + * @param itemIds Array of item ids + * @param amounts Array of amounts + * @return validationCode Validation code + */ + function _checkValidityMakerAskNFTAssets( + address collection, + CollectionType collectionType, + address user, + uint256[] memory itemIds, + uint256[] memory amounts + ) private view returns (uint256 validationCode) { + validationCode = _checkIfItemIdsDiffer(itemIds); + + if (validationCode != ORDER_EXPECTED_TO_BE_VALID) { + return validationCode; + } + + if (collectionType == CollectionType.ERC721) { + validationCode = _checkValidityERC721AndEquivalents(collection, user, itemIds); + } else if (collectionType == CollectionType.ERC1155) { + validationCode = _checkValidityERC1155(collection, user, itemIds, amounts); + } + } + + /** + * @notice This function verifies the validity of (1) ERC721 approvals + * and (2) balances to process the maker ask order. + * @param collection Collection address + * @param user User address + * @param itemIds Array of item ids + * @return validationCode Validation code + */ + function _checkValidityERC721AndEquivalents( + address collection, + address user, + uint256[] memory itemIds + ) private view returns (uint256 validationCode) { + // 1. Verify itemId is owned by user and catch revertion if ERC721 ownerOf fails + uint256 length = itemIds.length; + + bool success; + bytes memory data; + + for (uint256 i; i < length; ) { + (success, data) = collection.staticcall(abi.encodeCall(IERC721.ownerOf, (itemIds[i]))); + + if (!success) { + return ERC721_ITEM_ID_DOES_NOT_EXIST; + } + + if (abi.decode(data, (address)) != user) { + return ERC721_ITEM_ID_NOT_IN_BALANCE; + } + + unchecked { + ++i; + } + } + + // 2. Verify if collection is approved by transfer manager + (success, data) = collection.staticcall( + abi.encodeCall(IERC721.isApprovedForAll, (user, address(transferManager))) + ); + + bool isApprovedAll; + if (success) { + isApprovedAll = abi.decode(data, (bool)); + } + + if (!isApprovedAll) { + for (uint256 i; i < length; ) { + // 3. If collection is not approved by transfer manager, try to see if it is approved individually + (success, data) = collection.staticcall(abi.encodeCall(IERC721.getApproved, (itemIds[i]))); + + address approvedAddress; + + if (success) { + approvedAddress = abi.decode(data, (address)); + } + + if (approvedAddress != address(transferManager)) { + return ERC721_NO_APPROVAL_FOR_ALL_OR_ITEM_ID; + } + + unchecked { + ++i; + } + } + } + } + + /** + * @notice This function verifies the validity of (1) ERC1155 approvals + * (2) and balances to process the maker ask order. + * @param collection Collection address + * @param user User address + * @param itemIds Array of item ids + * @param amounts Array of amounts + * @return validationCode Validation code + */ + function _checkValidityERC1155( + address collection, + address user, + uint256[] memory itemIds, + uint256[] memory amounts + ) private view returns (uint256 validationCode) { + // 1. Verify each itemId is owned by user and catch revertion if ERC1155 ownerOf fails + address[] memory users = new address[](1); + users[0] = user; + + uint256 length = itemIds.length; + + // 1.1 Use balanceOfBatch + (bool success, bytes memory data) = collection.staticcall( + abi.encodeCall(IERC1155.balanceOfBatch, (users, itemIds)) + ); + + if (success) { + uint256[] memory balances = abi.decode(data, (uint256[])); + for (uint256 i; i < length; ) { + if (balances[i] < amounts[i]) { + return ERC1155_BALANCE_OF_ITEM_ID_INFERIOR_TO_AMOUNT; + } + unchecked { + ++i; + } + } + } else { + // 1.2 If the balanceOfBatch does not work, use loop with balanceOf function + for (uint256 i; i < length; ) { + (success, data) = collection.staticcall(abi.encodeCall(IERC1155.balanceOf, (user, itemIds[i]))); + + if (!success) { + return ERC1155_BALANCE_OF_DOES_NOT_EXIST; + } + + if (abi.decode(data, (uint256)) < amounts[i]) { + return ERC1155_BALANCE_OF_ITEM_ID_INFERIOR_TO_AMOUNT; + } + + unchecked { + ++i; + } + } + } + + // 2. Verify if collection is approved by transfer manager + (success, data) = collection.staticcall( + abi.encodeCall(IERC1155.isApprovedForAll, (user, address(transferManager))) + ); + + if (!success) { + return ERC1155_IS_APPROVED_FOR_ALL_DOES_NOT_EXIST; + } + + if (!abi.decode(data, (bool))) { + return ERC1155_NO_APPROVAL_FOR_ALL; + } + } + + /** + * @notice This function verifies the validity of a Merkle proof and the order hash. + * @param merkleTree Merkle tree struct + * @param orderHash Order hash + * @param signature Signature + * @param signer Signer address + * @return validationCode Validation code + */ + function _checkValidityMerkleProofAndOrderHash( + OrderStructs.MerkleTree calldata merkleTree, + bytes32 orderHash, + bytes calldata signature, + address signer + ) private view returns (uint256 validationCode) { + if (merkleTree.proof.length != 0) { + if (merkleTree.proof.length > MAX_CALLDATA_PROOF_LENGTH) { + return MERKLE_PROOF_PROOF_TOO_LARGE; + } + + if (!MerkleProofCalldataWithNodes.verifyCalldata(merkleTree.proof, merkleTree.root, orderHash)) { + return ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE; + } + + bytes32 batchOrderHash = looksRareProtocol.hashBatchOrder(merkleTree.root, merkleTree.proof.length); + + return _computeDigestAndVerify(batchOrderHash, signature, signer); + } else { + return _computeDigestAndVerify(orderHash, signature, signer); + } + } + + /** + * @notice Check the validity of creator fee + * @param collection Collection address + * @param itemIds Item ids + * @return validationCode Validation code + */ + function _checkValidityCreatorFee( + address collection, + uint256 price, + uint256[] memory itemIds + ) private view returns (uint256 validationCode) { + if (address(creatorFeeManager) != address(0)) { + (bool status, bytes memory data) = address(creatorFeeManager).staticcall( + abi.encodeCall(ICreatorFeeManager.viewCreatorFeeInfo, (collection, price, itemIds)) + ); + + // The only path possible (to revert) in the fee manager is the bundle being not supported. + if (!status) { + return BUNDLE_ERC2981_NOT_SUPPORTED; + } + + (address creator, uint256 creatorFeeAmount) = abi.decode(data, (address, uint256)); + + if (creator != address(0)) { + if (creatorFeeAmount * ONE_HUNDRED_PERCENT_IN_BP > (price * uint256(maxCreatorFeeBp))) { + return CREATOR_FEE_TOO_HIGH; + } + } + } + } + + /** + * @notice This function computes the digest and verify the signature. + * @param computedHash Hash of order or merkle root + * @param makerSignature Signature of the maker + * @param signer Signer address + * @return validationCode Validation code + */ + function _computeDigestAndVerify( + bytes32 computedHash, + bytes calldata makerSignature, + address signer + ) private view returns (uint256 validationCode) { + return + _validateSignature( + keccak256(abi.encodePacked("\x19\x01", domainSeparator, computedHash)), + makerSignature, + signer + ); + } + + /** + * @notice This function checks the validity of the signature. + * @param hash Message hash + * @param signature A 64 or 65 bytes signature + * @param signer Signer address + * @return validationCode Validation code + */ + function _validateSignature( + bytes32 hash, + bytes calldata signature, + address signer + ) private view returns (uint256 validationCode) { + // Logic if EOA + if (signer.code.length == 0) { + bytes32 r; + bytes32 s; + uint8 v; + + if (signature.length == 64) { + bytes32 vs; + assembly { + r := calldataload(signature.offset) + vs := calldataload(add(signature.offset, 0x20)) + s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + v := add(shr(255, vs), 27) + } + } else if (signature.length == 65) { + assembly { + r := calldataload(signature.offset) + s := calldataload(add(signature.offset, 0x20)) + v := byte(0, calldataload(add(signature.offset, 0x40))) + } + } else { + return INVALID_SIGNATURE_LENGTH; + } + + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + return INVALID_S_PARAMETER_EOA; + } + + if (v != 27 && v != 28) { + return INVALID_V_PARAMETER_EOA; + } + + address recoveredSigner = ecrecover(hash, v, r, s); + + if (recoveredSigner == address(0)) { + return NULL_SIGNER_EOA; + } + + if (signer != recoveredSigner) { + return INVALID_SIGNER_EOA; + } + } else { + // Logic if ERC1271 + (bool success, bytes memory data) = signer.staticcall( + abi.encodeCall(IERC1271.isValidSignature, (hash, signature)) + ); + + if (!success) { + return MISSING_IS_VALID_SIGNATURE_FUNCTION_EIP1271; + } + + if (abi.decode(data, (bytes4)) != IERC1271.isValidSignature.selector) { + return SIGNATURE_INVALID_EIP1271; + } + } + } + + /** + * @dev This function checks if transfer manager approvals are not revoked by user, nor by the owner + * @param user Address of the user + * @return validationCode Validation code + */ + function _checkValidityTransferManagerApprovals(address user) private view returns (uint256 validationCode) { + if (!transferManager.hasUserApprovedOperator(user, address(looksRareProtocol))) { + return NO_TRANSFER_MANAGER_APPROVAL_BY_USER_FOR_EXCHANGE; + } + + if (!transferManager.isOperatorAllowed(address(looksRareProtocol))) { + return TRANSFER_MANAGER_APPROVAL_REVOKED_BY_OWNER_FOR_EXCHANGE; + } + } + + function _checkValidityMakerAskItemIdsAndAmountsAndPrice( + OrderStructs.Maker memory makerAsk + ) private view returns (uint256 validationCode, uint256[] memory itemIds, uint256[] memory amounts, uint256 price) { + if (makerAsk.strategyId == 0) { + itemIds = makerAsk.itemIds; + amounts = makerAsk.amounts; + price = makerAsk.price; + + validationCode = _getOrderValidationCodeForStandardStrategy( + makerAsk.collectionType, + itemIds.length, + amounts + ); + } else { + itemIds = makerAsk.itemIds; + amounts = makerAsk.amounts; + // @dev It should ideally be adjusted by real price + price = makerAsk.price; + + (, , , , bytes4 strategySelector, , address strategyImplementation) = looksRareProtocol.strategyInfo( + makerAsk.strategyId + ); + + (bool isValid, bytes4 errorSelector) = IStrategy(strategyImplementation).isMakerOrderValid( + makerAsk, + strategySelector + ); + + validationCode = _getOrderValidationCodeForNonStandardStrategies(isValid, errorSelector); + } + } + + function _checkValidityMakerBidItemIdsAndAmountsAndPrice( + OrderStructs.Maker memory makerBid + ) private view returns (uint256 validationCode, uint256[] memory itemIds, uint256[] memory amounts, uint256 price) { + if (makerBid.strategyId == 0) { + itemIds = makerBid.itemIds; + amounts = makerBid.amounts; + price = makerBid.price; + + validationCode = _getOrderValidationCodeForStandardStrategy( + makerBid.collectionType, + itemIds.length, + amounts + ); + } else { + // @dev It should ideally be adjusted by real price + // amounts and itemIds are not used since most non-native maker bids won't target a single item + price = makerBid.price; + + (, , , , bytes4 strategySelector, , address strategyImplementation) = looksRareProtocol.strategyInfo( + makerBid.strategyId + ); + + (bool isValid, bytes4 errorSelector) = IStrategy(strategyImplementation).isMakerOrderValid( + makerBid, + strategySelector + ); + + validationCode = _getOrderValidationCodeForNonStandardStrategies(isValid, errorSelector); + } + } + + /** + * @notice This function checks if the same itemId is repeated + * in an array of item ids. + * @param itemIds Array of item ids + * @dev This is for bundles. + * For example, if itemIds = [1,2,1], it will return SAME_ITEM_ID_IN_BUNDLE. + * @return validationCode Validation code + */ + function _checkIfItemIdsDiffer(uint256[] memory itemIds) private pure returns (uint256 validationCode) { + uint256 length = itemIds.length; + + // Only check if length of array is greater than 1 + if (length > 1) { + for (uint256 i = 0; i < length - 1; ) { + for (uint256 j = i + 1; j < length; ) { + if (itemIds[i] == itemIds[j]) { + return SAME_ITEM_ID_IN_BUNDLE; + } + + unchecked { + ++j; + } + } + + unchecked { + ++i; + } + } + } + } + + function _getOrderValidationCodeForStandardStrategy( + CollectionType collectionType, + uint256 expectedLength, + uint256[] memory amounts + ) private pure returns (uint256 validationCode) { + if (expectedLength == 0 || (amounts.length != expectedLength)) { + validationCode = MAKER_ORDER_INVALID_STANDARD_SALE; + } else { + for (uint256 i; i < expectedLength; ) { + uint256 amount = amounts[i]; + + if (amount == 0) { + validationCode = MAKER_ORDER_INVALID_STANDARD_SALE; + } + + if (collectionType == CollectionType.ERC721 && amount != 1) { + validationCode = MAKER_ORDER_INVALID_STANDARD_SALE; + } + + unchecked { + ++i; + } + } + } + } + + function _getOrderValidationCodeForNonStandardStrategies( + bool isValid, + bytes4 errorSelector + ) private pure returns (uint256 validationCode) { + if (isValid) { + validationCode = ORDER_EXPECTED_TO_BE_VALID; + } else { + if (errorSelector == OrderInvalid.selector) { + validationCode = MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE; + } else { + validationCode = MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE; + } + } + } +} diff --git a/contracts/src/marketplace/helpers/ProtocolHelpers.sol b/contracts/src/marketplace/helpers/ProtocolHelpers.sol new file mode 100644 index 00000000..01162985 --- /dev/null +++ b/contracts/src/marketplace/helpers/ProtocolHelpers.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { SignatureCheckerCalldata } from "@looksrare/contracts-libs/contracts/SignatureCheckerCalldata.sol"; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// Other dependencies +import { LooksRareProtocol } from "../LooksRareProtocol.sol"; + +/** + * @title ProtocolHelpers + * @notice This contract contains helper view functions for order creation. + * @author LooksRare protocol team (👀,💎) + */ +contract ProtocolHelpers { + using OrderStructs for OrderStructs.Maker; + + // Encoding prefix for EIP-712 signatures + string internal constant _ENCODING_PREFIX = "\x19\x01"; + + // LooksRareProtocol + LooksRareProtocol public looksRareProtocol; + + /** + * @notice Constructor + * @param _looksRareProtocol LooksRare protocol address + */ + constructor(address _looksRareProtocol) { + looksRareProtocol = LooksRareProtocol(_looksRareProtocol); + } + + /** + * @notice Compute digest for maker bid struct + * @param maker Maker struct + * @return digest Digest + */ + function computeMakerDigest(OrderStructs.Maker memory maker) public view returns (bytes32 digest) { + bytes32 domainSeparator = looksRareProtocol.domainSeparator(); + return keccak256(abi.encodePacked(_ENCODING_PREFIX, domainSeparator, maker.hash())); + } + + /** + * @notice Compute digest for merkle tree struct + * @param merkleTree Merkle tree struct + * @return digest Digest + */ + function computeDigestMerkleTree(OrderStructs.MerkleTree memory merkleTree) public view returns (bytes32 digest) { + bytes32 domainSeparator = looksRareProtocol.domainSeparator(); + bytes32 batchOrderHash = looksRareProtocol.hashBatchOrder(merkleTree.root, merkleTree.proof.length); + return keccak256(abi.encodePacked(_ENCODING_PREFIX, domainSeparator, batchOrderHash)); + } + + /** + * @notice Verify maker order signature + * @param maker Maker struct + * @param makerSignature Maker signature + * @param signer Signer address + * @dev It returns true only if the SignatureCheckerCalldata does not revert before. + */ + function verifyMakerSignature( + OrderStructs.Maker memory maker, + bytes calldata makerSignature, + address signer + ) public view returns (bool) { + bytes32 digest = computeMakerDigest(maker); + SignatureCheckerCalldata.verify(digest, signer, makerSignature); + return true; + } + + /** + * @notice Verify merkle tree signature + * @param merkleTree Merkle tree struct + * @param makerSignature Maker signature + * @param signer Signer address + * @dev It returns true only if the SignatureCheckerCalldata does not revert before. + */ + function verifyMerkleTree( + OrderStructs.MerkleTree memory merkleTree, + bytes calldata makerSignature, + address signer + ) public view returns (bool) { + bytes32 digest = computeDigestMerkleTree(merkleTree); + SignatureCheckerCalldata.verify(digest, signer, makerSignature); + return true; + } +} diff --git a/contracts/src/marketplace/interfaces/ICreatorFeeManager.sol b/contracts/src/marketplace/interfaces/ICreatorFeeManager.sol new file mode 100644 index 00000000..d469ff27 --- /dev/null +++ b/contracts/src/marketplace/interfaces/ICreatorFeeManager.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Interfaces +import { IRoyaltyFeeRegistry } from "./IRoyaltyFeeRegistry.sol"; + +/** + * @title ICreatorFeeManager + * @author LooksRare protocol team (👀,💎) + */ +interface ICreatorFeeManager { + /** + * @notice It is returned if the bundle contains multiple itemIds with different creator fee structure. + */ + error BundleEIP2981NotAllowed(address collection); + + /** + * @notice It returns the royalty fee registry address/interface. + * @return royaltyFeeRegistry Interface of the royalty fee registry + */ + function royaltyFeeRegistry() external view returns (IRoyaltyFeeRegistry royaltyFeeRegistry); + + /** + * @notice This function returns the creator address and calculates the creator fee amount. + * @param collection Collection address + * @param price Transaction price + * @param itemIds Array of item ids + * @return creator Creator address + * @return creatorFeeAmount Creator fee amount + */ + function viewCreatorFeeInfo( + address collection, + uint256 price, + uint256[] memory itemIds + ) external view returns (address creator, uint256 creatorFeeAmount); +} diff --git a/contracts/src/marketplace/interfaces/ICurrencyManager.sol b/contracts/src/marketplace/interfaces/ICurrencyManager.sol new file mode 100644 index 00000000..72d50d2e --- /dev/null +++ b/contracts/src/marketplace/interfaces/ICurrencyManager.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title ICurrencyManager + * @author LooksRare protocol team (👀,💎) + */ +interface ICurrencyManager { + /** + * @notice It is emitted if the currency status in the allowlist is updated. + * @param currency Currency address (address(0) = ETH) + * @param isAllowed Whether the currency is allowed + */ + event CurrencyStatusUpdated(address currency, bool isAllowed); +} diff --git a/contracts/src/marketplace/interfaces/IExecutionManager.sol b/contracts/src/marketplace/interfaces/IExecutionManager.sol new file mode 100644 index 00000000..945637b5 --- /dev/null +++ b/contracts/src/marketplace/interfaces/IExecutionManager.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title IExecutionManager + * @author LooksRare protocol team (👀,💎) + */ +interface IExecutionManager { + /** + * @notice It is issued when there is a new creator fee manager. + * @param creatorFeeManager Address of the new creator fee manager + */ + event NewCreatorFeeManager(address creatorFeeManager); + + /** + * @notice It is issued when there is a new maximum creator fee (in basis point). + * @param maxCreatorFeeBp New maximum creator fee (in basis point) + */ + event NewMaxCreatorFeeBp(uint256 maxCreatorFeeBp); + + /** + * @notice It is issued when there is a new protocol fee recipient address. + * @param protocolFeeRecipient Address of the new protocol fee recipient + */ + event NewProtocolFeeRecipient(address protocolFeeRecipient); + + /** + * @notice It is returned if the creator fee (in basis point) is too high. + */ + error CreatorFeeBpTooHigh(); + + /** + * @notice It is returned if the new protocol fee recipient is set to address(0). + */ + error NewProtocolFeeRecipientCannotBeNullAddress(); + + /** + * @notice It is returned if there is no selector for maker ask/bid for a given strategyId, + * depending on the quote type. + */ + error NoSelectorForStrategy(); + + /** + * @notice It is returned if the current block timestamp is not between start and end times in the maker order. + */ + error OutsideOfTimeRange(); + + /** + * @notice It is returned if the strategy id has no implementation. + * @dev It is returned if there is no implementation address and the strategyId is strictly greater than 0. + */ + error StrategyNotAvailable(uint256 strategyId); +} diff --git a/contracts/src/marketplace/interfaces/IImmutableCreate2Factory.sol b/contracts/src/marketplace/interfaces/IImmutableCreate2Factory.sol new file mode 100644 index 00000000..9071ab43 --- /dev/null +++ b/contracts/src/marketplace/interfaces/IImmutableCreate2Factory.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +interface IImmutableCreate2Factory { + function safeCreate2( + bytes32 salt, + bytes calldata initializationCode + ) external payable returns (address deploymentAddress); +} diff --git a/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol b/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol new file mode 100644 index 00000000..b9305d03 --- /dev/null +++ b/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +/** + * @title ILooksRareProtocol + * @author LooksRare protocol team (👀,💎) + */ +interface ILooksRareProtocol { + /** + * @notice This struct contains an order nonce's invalidation status + * and the order hash that triggered the status change. + * @param orderHash Maker order hash + * @param orderNonce Order nonce + * @param isNonceInvalidated Whether this transaction invalidated the maker user's order nonce at the protocol level + */ + struct NonceInvalidationParameters { + bytes32 orderHash; + uint256 orderNonce; + bool isNonceInvalidated; + } + + /** + * @notice It is emitted when there is an affiliate fee paid. + * @param affiliate Affiliate address + * @param currency Address of the currency + * @param affiliateFee Affiliate fee (in the currency) + */ + event AffiliatePayment(address affiliate, address currency, uint256 affiliateFee); + + /** + * @notice It is emitted if there is a change in the domain separator. + */ + event NewDomainSeparator(); + + /** + * @notice It is emitted when there is a new gas limit for a ETH transfer (before it is wrapped to WETH). + * @param gasLimitETHTransfer Gas limit for an ETH transfer + */ + event NewGasLimitETHTransfer(uint256 gasLimitETHTransfer); + + /** + * @notice It is emitted when a taker ask transaction is completed. + * @param nonceInvalidationParameters Struct about nonce invalidation parameters + * @param askUser Address of the ask user + * @param bidUser Address of the bid user + * @param strategyId Id of the strategy + * @param currency Address of the currency + * @param collection Address of the collection + * @param itemIds Array of item ids + * @param amounts Array of amounts (for item ids) + * @param feeRecipients Array of fee recipients + * feeRecipients[0] User who receives the proceeds of the sale (it can be the taker ask user or different) + * feeRecipients[1] Creator fee recipient (if none, address(0)) + * @param feeAmounts Array of fee amounts + * feeAmounts[0] Fee amount for the user receiving sale proceeds + * feeAmounts[1] Creator fee amount + * feeAmounts[2] Protocol fee amount prior to adjustment for a potential affiliate payment + */ + event TakerAsk( + NonceInvalidationParameters nonceInvalidationParameters, + address askUser, // taker (initiates the transaction) + address bidUser, // maker (receives the NFT) + uint256 strategyId, + address currency, + address collection, + uint256[] itemIds, + uint256[] amounts, + address[2] feeRecipients, + uint256[3] feeAmounts + ); + + /** + * @notice It is emitted when a taker bid transaction is completed. + * @param nonceInvalidationParameters Struct about nonce invalidation parameters + * @param bidUser Address of the bid user + * @param bidRecipient Address of the recipient of the bid + * @param strategyId Id of the strategy + * @param currency Address of the currency + * @param collection Address of the collection + * @param itemIds Array of item ids + * @param amounts Array of amounts (for item ids) + * @param feeRecipients Array of fee recipients + * feeRecipients[0] User who receives the proceeds of the sale (it is the maker ask user) + * feeRecipients[1] Creator fee recipient (if none, address(0)) + * @param feeAmounts Array of fee amounts + * feeAmounts[0] Fee amount for the user receiving sale proceeds + * feeAmounts[1] Creator fee amount + * feeAmounts[2] Protocol fee amount prior to adjustment for a potential affiliate payment + */ + event TakerBid( + NonceInvalidationParameters nonceInvalidationParameters, + address bidUser, // taker (initiates the transaction) + address bidRecipient, // taker (receives the NFT) + uint256 strategyId, + address currency, + address collection, + uint256[] itemIds, + uint256[] amounts, + address[2] feeRecipients, + uint256[3] feeAmounts + ); + + /** + * @notice It is returned if the gas limit for a standard ETH transfer is too low. + */ + error NewGasLimitETHTransferTooLow(); + + /** + * @notice It is returned if the domain separator cannot be updated (i.e. the chainId is the same). + */ + error SameDomainSeparator(); + + /** + * @notice It is returned if the domain separator should change. + */ + error ChainIdInvalid(); + + /** + * @notice It is returned if the nonces are invalid. + */ + error NoncesInvalid(); + + /** + * @notice This function allows a user to execute a taker ask (against a maker bid). + * @param takerAsk Taker ask struct + * @param makerBid Maker bid struct + * @param makerSignature Maker signature + * @param merkleTree Merkle tree struct (if the signature contains multiple maker orders) + * @param affiliate Affiliate address + */ + function executeTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external; + + /** + * @notice This function allows a user to execute a taker bid (against a maker ask). + * @param takerBid Taker bid struct + * @param makerAsk Maker ask struct + * @param makerSignature Maker signature + * @param merkleTree Merkle tree struct (if the signature contains multiple maker orders) + * @param affiliate Affiliate address + */ + function executeTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external payable; + + /** + * @notice This function allows a user to batch buy with an array of taker bids (against an array of maker asks). + * @param takerBids Array of taker bid structs + * @param makerAsks Array of maker ask structs + * @param makerSignatures Array of maker signatures + * @param merkleTrees Array of merkle tree structs if the signature contains multiple maker orders + * @param affiliate Affiliate address + * @param isAtomic Whether the execution should be atomic + * i.e. whether it should revert if 1 or more transactions fail + */ + function executeMultipleTakerBids( + OrderStructs.Taker[] calldata takerBids, + OrderStructs.Maker[] calldata makerAsks, + bytes[] calldata makerSignatures, + OrderStructs.MerkleTree[] calldata merkleTrees, + address affiliate, + bool isAtomic + ) external payable; +} diff --git a/contracts/src/marketplace/interfaces/INonceManager.sol b/contracts/src/marketplace/interfaces/INonceManager.sol new file mode 100644 index 00000000..6011291d --- /dev/null +++ b/contracts/src/marketplace/interfaces/INonceManager.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title INonceManager + * @author LooksRare protocol team (👀,💎) + */ +interface INonceManager { + /** + * @notice This struct contains the global bid and ask nonces of a user. + * @param bidNonce Bid nonce + * @param askNonce Ask nonce + */ + struct UserBidAskNonces { + uint256 bidNonce; + uint256 askNonce; + } + + /** + * @notice It is emitted when there is an update of the global bid/ask nonces for a user. + * @param user Address of the user + * @param bidNonce New bid nonce + * @param askNonce New ask nonce + */ + event NewBidAskNonces(address user, uint256 bidNonce, uint256 askNonce); + + /** + * @notice It is emitted when order nonces are cancelled for a user. + * @param user Address of the user + * @param orderNonces Array of order nonces cancelled + */ + event OrderNoncesCancelled(address user, uint256[] orderNonces); + + /** + * @notice It is emitted when subset nonces are cancelled for a user. + * @param user Address of the user + * @param subsetNonces Array of subset nonces cancelled + */ + event SubsetNoncesCancelled(address user, uint256[] subsetNonces); +} diff --git a/contracts/src/marketplace/interfaces/IRoyaltyFeeRegistry.sol b/contracts/src/marketplace/interfaces/IRoyaltyFeeRegistry.sol new file mode 100644 index 00000000..a76c84cb --- /dev/null +++ b/contracts/src/marketplace/interfaces/IRoyaltyFeeRegistry.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title IRoyaltyFeeRegistry + * @author LooksRare protocol team (👀,💎) + */ +interface IRoyaltyFeeRegistry { + /** + * @notice This function returns the royalty information for a collection at a given transaction price. + * @param collection Collection address + * @param price Transaction price + * @return receiver Receiver address + * @return royaltyFee Royalty fee amount + */ + function royaltyInfo( + address collection, + uint256 price + ) external view returns (address receiver, uint256 royaltyFee); +} diff --git a/contracts/src/marketplace/interfaces/IStrategy.sol b/contracts/src/marketplace/interfaces/IStrategy.sol new file mode 100644 index 00000000..39585c5f --- /dev/null +++ b/contracts/src/marketplace/interfaces/IStrategy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +/** + * @title IStrategy + * @author LooksRare protocol team (👀,💎) + */ +interface IStrategy { + /** + * @notice Validate *only the maker* order under the context of the chosen strategy. It does not revert if + * the maker order is invalid. Instead it returns false and the error's 4 bytes selector. + * @param makerOrder Maker struct (maker specific parameters for the execution) + * @param functionSelector Function selector for the strategy + * @return isValid Whether the maker struct is valid + * @return errorSelector If isValid is false, it returns the error's 4 bytes selector + */ + function isMakerOrderValid( + OrderStructs.Maker calldata makerOrder, + bytes4 functionSelector + ) external view returns (bool isValid, bytes4 errorSelector); + + /** + * @notice This function acts as a safety check for the protocol's owner when adding new execution strategies. + * @return isStrategy Whether it is a LooksRare V2 protocol strategy + */ + function isLooksRareV2Strategy() external pure returns (bool isStrategy); +} diff --git a/contracts/src/marketplace/interfaces/IStrategyManager.sol b/contracts/src/marketplace/interfaces/IStrategyManager.sol new file mode 100644 index 00000000..283431a2 --- /dev/null +++ b/contracts/src/marketplace/interfaces/IStrategyManager.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title IStrategyManager + * @author LooksRare protocol team (👀,💎) + */ +interface IStrategyManager { + /** + * @notice This struct contains the parameter of an execution strategy. + * @param strategyId Id of the new strategy + * @param standardProtocolFeeBp Standard protocol fee (in basis point) + * @param minTotalFeeBp Minimum total fee (in basis point) + * @param maxProtocolFeeBp Maximum protocol fee (in basis point) + * @param selector Function selector for the transaction to be executed + * @param isMakerBid Whether the strategyId is for maker bid + * @param implementation Address of the implementation of the strategy + */ + struct Strategy { + bool isActive; + uint16 standardProtocolFeeBp; + uint16 minTotalFeeBp; + uint16 maxProtocolFeeBp; + bytes4 selector; + bool isMakerBid; + address implementation; + } + + /** + * @notice It is emitted when a new strategy is added. + * @param strategyId Id of the new strategy + * @param standardProtocolFeeBp Standard protocol fee (in basis point) + * @param minTotalFeeBp Minimum total fee (in basis point) + * @param maxProtocolFeeBp Maximum protocol fee (in basis point) + * @param selector Function selector for the transaction to be executed + * @param isMakerBid Whether the strategyId is for maker bid + * @param implementation Address of the implementation of the strategy + */ + event NewStrategy( + uint256 strategyId, + uint16 standardProtocolFeeBp, + uint16 minTotalFeeBp, + uint16 maxProtocolFeeBp, + bytes4 selector, + bool isMakerBid, + address implementation + ); + + /** + * @notice It is emitted when an existing strategy is updated. + * @param strategyId Id of the strategy + * @param isActive Whether the strategy is active (or not) after the update + * @param standardProtocolFeeBp Standard protocol fee (in basis point) + * @param minTotalFeeBp Minimum total fee (in basis point) + */ + event StrategyUpdated(uint256 strategyId, bool isActive, uint16 standardProtocolFeeBp, uint16 minTotalFeeBp); + + /** + * @notice If the strategy has not set properly its implementation contract. + * @dev It can only be returned for owner operations. + */ + error NotV2Strategy(); + + /** + * @notice It is returned if the strategy has no selector. + * @dev It can only be returned for owner operations. + */ + error StrategyHasNoSelector(); + + /** + * @notice It is returned if the strategyId is invalid. + */ + error StrategyNotUsed(); + + /** + * @notice It is returned if the strategy's protocol fee is too high. + * @dev It can only be returned for owner operations. + */ + error StrategyProtocolFeeTooHigh(); +} diff --git a/contracts/src/marketplace/interfaces/ITransferManager.sol b/contracts/src/marketplace/interfaces/ITransferManager.sol new file mode 100644 index 00000000..56f70e19 --- /dev/null +++ b/contracts/src/marketplace/interfaces/ITransferManager.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../libraries/OrderStructs.sol"; + +// Enums +import { CollectionType } from "../enums/CollectionType.sol"; + +/** + * @title ITransferManager + * @author LooksRare protocol team (👀,💎) + */ +interface ITransferManager { + /** + * @notice This struct is only used for transferBatchItemsAcrossCollections. + * @param collection Collection address + * @param collectionType 0 for ERC721, 1 for ERC1155 + * @param itemIds Array of item ids to transfer + * @param amounts Array of amounts to transfer + */ + struct BatchTransferItem { + address collection; + CollectionType collectionType; + uint256[] itemIds; + uint256[] amounts; + } + + /** + * @notice It is emitted if operators' approvals to transfer NFTs are granted by a user. + * @param user Address of the user + * @param operators Array of operator addresses + */ + event ApprovalsGranted(address user, address[] operators); + + /** + * @notice It is emitted if operators' approvals to transfer NFTs are revoked by a user. + * @param user Address of the user + * @param operators Array of operator addresses + */ + event ApprovalsRemoved(address user, address[] operators); + + /** + * @notice It is emitted if a new operator is added to the global allowlist. + * @param operator Operator address + */ + event OperatorAllowed(address operator); + + /** + * @notice It is emitted if an operator is removed from the global allowlist. + * @param operator Operator address + */ + event OperatorRemoved(address operator); + + /** + * @notice It is returned if the operator to approve has already been approved by the user. + */ + error OperatorAlreadyApprovedByUser(); + + /** + * @notice It is returned if the operator to revoke has not been previously approved by the user. + */ + error OperatorNotApprovedByUser(); + + /** + * @notice It is returned if the transfer caller is already allowed by the owner. + * @dev This error can only be returned for owner operations. + */ + error OperatorAlreadyAllowed(); + + /** + * @notice It is returned if the operator to approve is not in the global allowlist defined by the owner. + * @dev This error can be returned if the user tries to grant approval to an operator address not in the + * allowlist or if the owner tries to remove the operator from the global allowlist. + */ + error OperatorNotAllowed(); + + /** + * @notice It is returned if the transfer caller is invalid. + * For a transfer called to be valid, the operator must be in the global allowlist and + * approved by the 'from' user. + */ + error TransferCallerInvalid(); +} diff --git a/contracts/src/marketplace/libraries/CurrencyValidator.sol b/contracts/src/marketplace/libraries/CurrencyValidator.sol new file mode 100644 index 00000000..d7523744 --- /dev/null +++ b/contracts/src/marketplace/libraries/CurrencyValidator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Assembly +import { CurrencyInvalid_error_selector, CurrencyInvalid_error_length, Error_selector_offset } from "../constants/AssemblyConstants.sol"; + +/** + * @title CurrencyValidator + * @notice This library validates the order currency to be the + * chain's native currency or the specified ERC20 token. + * @author LooksRare protocol team (👀,💎) + */ +library CurrencyValidator { + /** + * @dev This is equivalent to + * if (orderCurrency != address(0)) { + * if (orderCurrency != allowedCurrency) { + * revert CurrencyInvalid(); + * } + * } + * + * 1. If orderCurrency == WETH, allowedCurrency == WETH -> WETH * 0 == 0 + * 2. If orderCurrency == ETH, allowedCurrency == WETH -> 0 * 1 == 0 + * 3. If orderCurrency == USDC, allowedCurrency == WETH -> USDC * 1 != 0 + */ + function allowNativeOrAllowedCurrency(address orderCurrency, address allowedCurrency) internal pure { + assembly { + if mul(orderCurrency, iszero(eq(orderCurrency, allowedCurrency))) { + mstore(0x00, CurrencyInvalid_error_selector) + revert(Error_selector_offset, CurrencyInvalid_error_length) + } + } + } +} diff --git a/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol b/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol new file mode 100644 index 00000000..d3d04cfe --- /dev/null +++ b/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "../../libraries/OrderStructs.sol"; + +/** + * @title MerkleProofCalldataWithNodes + * @notice This library is adjusted from the work of OpenZeppelin. + * It is based on the 4.7.0 (utils/cryptography/MerkleProof.sol). + * @author OpenZeppelin (adjusted by LooksRare) + */ +library MerkleProofCalldataWithNodes { + /** + * @notice This returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. + * For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the + * root of the tree. Each pair of leaves and each pair of pre-images are assumed to be sorted. + */ + function verifyCalldata( + OrderStructs.MerkleTreeNode[] calldata proof, + bytes32 root, + bytes32 leaf + ) internal pure returns (bool) { + return processProofCalldata(proof, leaf) == root; + } + + /** + * @notice This returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. + * A `proof` is valid if and only if the rebuilt hash matches the root of the tree. + * When processing the proof, the pairs of leafs & pre-images are assumed to be sorted. + */ + function processProofCalldata( + OrderStructs.MerkleTreeNode[] calldata proof, + bytes32 leaf + ) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + uint256 length = proof.length; + + for (uint256 i = 0; i < length; ) { + if (proof[i].position == OrderStructs.MerkleTreeNodePosition.Left) { + computedHash = _efficientHash(proof[i].value, computedHash); + } else { + computedHash = _efficientHash(computedHash, proof[i].value); + } + unchecked { + ++i; + } + } + return computedHash; + } + + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofMemory.sol b/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofMemory.sol new file mode 100644 index 00000000..1e00db5a --- /dev/null +++ b/contracts/src/marketplace/libraries/OpenZeppelin/MerkleProofMemory.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title MerkleProofMemory + * @notice This library is adjusted from the work of OpenZeppelin. + * It is based on the 4.7.0 (utils/cryptography/MerkleProof.sol). + * @author OpenZeppelin (adjusted by LooksRare) + */ +// TODO compare changes with OpenZeppelin +library MerkleProofMemory { + /** + * @notice This returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. + * For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the + * root of the tree. Each pair of leaves and each pair of pre-images are assumed to be sorted. + */ + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @notice This returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. + * A `proof` is valid if and only if the rebuilt hash matches the root of the tree. + * When processing the proof, the pairs of leafs & pre-images are assumed to be sorted. + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + uint256 length = proof.length; + + for (uint256 i = 0; i < length; ) { + computedHash = _hashPair(computedHash, proof[i]); + unchecked { + ++i; + } + } + return computedHash; + } + + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? _efficientHash(a, b) : _efficientHash(b, a); + } + + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/contracts/src/marketplace/libraries/OrderStructs.sol b/contracts/src/marketplace/libraries/OrderStructs.sol new file mode 100644 index 00000000..e7600695 --- /dev/null +++ b/contracts/src/marketplace/libraries/OrderStructs.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Enums +import { CollectionType } from "../enums/CollectionType.sol"; +import { QuoteType } from "../enums/QuoteType.sol"; + +/** + * @title OrderStructs + * @notice This library contains all order struct types for the LooksRare protocol (v2). + * @author LooksRare protocol team (👀,💎) + */ +library OrderStructs { + /** + * 1. Maker struct + */ + + /** + * @notice Maker is the struct for a maker order. + * @param quoteType Quote type (i.e. 0 = BID, 1 = ASK) + * @param globalNonce Global user order nonce for maker orders + * @param subsetNonce Subset nonce (shared across bid/ask maker orders) + * @param orderNonce Order nonce (it can be shared across bid/ask maker orders) + * @param strategyId Strategy id + * @param collectionType Collection type (i.e. 0 = ERC721, 1 = ERC1155, 2 = Hypercert, 3 = Hyperboard) + * @param collection Collection address + * @param currency Currency address (@dev address(0) = ETH) + * @param signer Signer address + * @param startTime Start timestamp + * @param endTime End timestamp + * @param price Minimum price for maker ask, maximum price for maker bid + * @param itemIds Array of itemIds + * @param amounts Array of amounts + * @param additionalParameters Extra data specific for the order + */ + struct Maker { + QuoteType quoteType; + uint256 globalNonce; + uint256 subsetNonce; + uint256 orderNonce; + uint256 strategyId; + CollectionType collectionType; + address collection; + address currency; + address signer; + uint256 startTime; + uint256 endTime; + uint256 price; + uint256[] itemIds; + uint256[] amounts; + bytes additionalParameters; + } + + /** + * 2. Taker struct + */ + + /** + * @notice Taker is the struct for a taker ask/bid order. It contains the parameters required for a direct purchase. + * @dev Taker struct is matched against MakerAsk/MakerBid structs at the protocol level. + * @param recipient Recipient address (to receive NFTs or non-fungible tokens) + * @param additionalParameters Extra data specific for the order + */ + struct Taker { + address recipient; + bytes additionalParameters; + } + + /** + * 3. Merkle tree struct + */ + + enum MerkleTreeNodePosition { + Left, + Right + } + + /** + * @notice MerkleTreeNode is a MerkleTree's node. + * @param value It can be an order hash or a proof + * @param position The node's position in its branch. + * It can be left or right or none + * (before the tree is sorted). + */ + struct MerkleTreeNode { + bytes32 value; + MerkleTreeNodePosition position; + } + + /** + * @notice MerkleTree is the struct for a merkle tree of order hashes. + * @dev A Merkle tree can be computed with order hashes. + * It can contain order hashes from both maker bid and maker ask structs. + * @param root Merkle root + * @param proof Array containing the merkle proof + */ + struct MerkleTree { + bytes32 root; + MerkleTreeNode[] proof; + } + + /** + * 4. Constants + */ + + /** + * @notice This is the type hash constant used to compute the maker order hash. + */ + bytes32 internal constant _MAKER_TYPEHASH = + keccak256( + "Maker(" + "uint8 quoteType," + "uint256 globalNonce," + "uint256 subsetNonce," + "uint256 orderNonce," + "uint256 strategyId," + "uint8 collectionType," + "address collection," + "address currency," + "address signer," + "uint256 startTime," + "uint256 endTime," + "uint256 price," + "uint256[] itemIds," + "uint256[] amounts," + "bytes additionalParameters" + ")" + ); + + /** + * 5. Hash functions + */ + + /** + * @notice This function is used to compute the order hash for a maker struct. + * @param maker Maker order struct + * @return makerHash Hash of the maker struct + */ + function hash(Maker memory maker) internal pure returns (bytes32) { + // Encoding is done into two parts to avoid stack too deep issues + return + keccak256( + bytes.concat( + abi.encode( + _MAKER_TYPEHASH, + maker.quoteType, + maker.globalNonce, + maker.subsetNonce, + maker.orderNonce, + maker.strategyId, + maker.collectionType, + maker.collection, + maker.currency + ), + abi.encode( + maker.signer, + maker.startTime, + maker.endTime, + maker.price, + keccak256(abi.encodePacked(maker.itemIds)), + keccak256(abi.encodePacked(maker.amounts)), + keccak256(maker.additionalParameters) + ) + ) + ); + } +} diff --git a/contracts/test/foundry/marketplace/BatchMakerCollectionOrders.t.sol b/contracts/test/foundry/marketplace/BatchMakerCollectionOrders.t.sol new file mode 100644 index 00000000..9222bd04 --- /dev/null +++ b/contracts/test/foundry/marketplace/BatchMakerCollectionOrders.t.sol @@ -0,0 +1,114 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Strategies +import { StrategyCollectionOffer } from "@hypercerts/marketplace/executionStrategies/StrategyCollectionOffer.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Helpers +import { EIP712MerkleTree } from "./utils/EIP712MerkleTree.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract BatchMakerCollectionOrdersTest is ProtocolBase { + StrategyCollectionOffer private strategy; + uint256 private constant price = 1 ether; // Fixed price of sale + EIP712MerkleTree private eip712MerkleTree; + + function setUp() public { + _setUp(); + + strategy = new StrategyCollectionOffer(); + vm.prank(_owner); + looksRareProtocol.addStrategy( + _standardProtocolFeeBp, + _minTotalFeeBp, + _maxProtocolFeeBp, + StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector, + true, + address(strategy) + ); + + _setUpUsers(); + eip712MerkleTree = new EIP712MerkleTree(looksRareProtocol); + } + + function testTakerAskMultipleOrdersSignedERC721(uint256 numberOrders) public { + vm.assume(numberOrders > 0 && numberOrders <= 10); + + mockERC721.batchMint(takerUser, numberOrders); + + OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); + + (bytes memory signature, ) = eip712MerkleTree.sign(makerUserPK, makerBids, 0); + + for (uint256 i; i < numberOrders; i++) { + // To prove that we only need 1 signature for multiple collection offers, + // we are not using the signature from the sign call in the loop. + (, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign(makerUserPK, makerBids, i); + + OrderStructs.Maker memory makerBidToExecute = makerBids[i]; + + // Verify validity + _assertValidMakerOrderWithMerkleTree(makerBidToExecute, signature, merkleTree); + + OrderStructs.Taker memory takerOrder = OrderStructs.Taker(takerUser, abi.encode(i)); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerOrder, makerBidToExecute, signature, merkleTree, _EMPTY_AFFILIATE); + + // Maker user has received the asset + assertEq(mockERC721.ownerOf(i), makerUser); + + // Verify the nonce is marked as executed + assertEq( + looksRareProtocol.userOrderNonce(makerUser, makerBidToExecute.orderNonce), + MAGIC_VALUE_ORDER_NONCE_EXECUTED + ); + } + + uint256 totalValue = price * numberOrders; + assertEq( + weth.balanceOf(makerUser), + _initialWETHBalanceUser - totalValue, + "Maker bid user should pay the whole price" + ); + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + + (totalValue * _sellerProceedBpWithStandardProtocolFeeBp) / + ONE_HUNDRED_PERCENT_IN_BP, + "Taker ask user should receive 99.5% of the whole price (0.5% protocol)" + ); + } + + function _createBatchMakerBids(uint256 numberOrders) private view returns (OrderStructs.Maker[] memory makerBids) { + makerBids = new OrderStructs.Maker[](numberOrders); + for (uint256 i; i < numberOrders; i++) { + makerBids[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: i, // incremental + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 // Not used + }); + } + } +} diff --git a/contracts/test/foundry/marketplace/BatchMakerOrders.t.sol b/contracts/test/foundry/marketplace/BatchMakerOrders.t.sol new file mode 100644 index 00000000..fabceaae --- /dev/null +++ b/contracts/test/foundry/marketplace/BatchMakerOrders.t.sol @@ -0,0 +1,413 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Murky (third-party) library is used to compute Merkle trees in Solidity +import { Merkle } from "murky/Merkle.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Errors and constants +import { MerkleProofTooLarge, MerkleProofInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { MERKLE_PROOF_PROOF_TOO_LARGE, ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; +import { ONE_HUNDRED_PERCENT_IN_BP, MAX_CALLDATA_PROOF_LENGTH } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Helpers +import { EIP712MerkleTree } from "./utils/EIP712MerkleTree.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract BatchMakerOrdersTest is ProtocolBase { + uint256 private constant price = 1.2222 ether; // Fixed price of sale + EIP712MerkleTree private eip712MerkleTree; + + function setUp() public { + _setUp(); + _setUpUsers(); + eip712MerkleTree = new EIP712MerkleTree(looksRareProtocol); + } + + function testTakerBidMultipleOrdersSignedERC721(uint256 numberOrders, uint256 orderIndex) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(makerUser, numberOrders); + + OrderStructs.Maker[] memory makerAsks = _createBatchMakerAsks(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerAsks, + orderIndex + ); + + OrderStructs.Maker memory makerAskToExecute = makerAsks[orderIndex]; + + // Verify validity + _assertValidMakerOrderWithMerkleTree(makerAskToExecute, signature, merkleTree); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + _genericTakerOrder(), + makerAskToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(orderIndex), takerUser); + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - price); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq( + looksRareProtocol.userOrderNonce(makerUser, makerAskToExecute.orderNonce), + MAGIC_VALUE_ORDER_NONCE_EXECUTED + ); + } + + function testTakerAskMultipleOrdersSignedERC721(uint256 numberOrders, uint256 orderIndex) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(takerUser, numberOrders); + + OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerBids, + orderIndex + ); + + OrderStructs.Maker memory makerBidToExecute = makerBids[orderIndex]; + + // Verify validity + _assertValidMakerOrderWithMerkleTree(makerBidToExecute, signature, merkleTree); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBidToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + + // Maker user has received the asset + assertEq(mockERC721.ownerOf(orderIndex), makerUser); + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Taker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq( + looksRareProtocol.userOrderNonce(makerUser, makerBidToExecute.orderNonce), + MAGIC_VALUE_ORDER_NONCE_EXECUTED + ); + } + + function testTakerBidMultipleOrdersSignedERC721MerkleProofInvalid(uint256 numberOrders, uint256 orderIndex) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(makerUser, numberOrders); + + OrderStructs.Maker[] memory makerAsks = _createBatchMakerAsks(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerAsks, + orderIndex + ); + + bytes32 tamperedRoot = bytes32(uint256(merkleTree.root) + 1); + merkleTree.root = tamperedRoot; + + OrderStructs.Maker memory makerAskToExecute = makerAsks[orderIndex]; + + // Verify invalidity of maker ask order + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerAskToExecute, + signature, + merkleTree, + ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE + ); + + vm.prank(takerUser); + vm.expectRevert(MerkleProofInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + _genericTakerOrder(), + makerAskToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + + function testTakerAskMultipleOrdersSignedERC721MerkleProofInvalid(uint256 numberOrders, uint256 orderIndex) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(takerUser, numberOrders); + + OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerBids, + orderIndex + ); + + bytes32 tamperedRoot = bytes32(uint256(merkleTree.root) + 1); + merkleTree.root = tamperedRoot; + + OrderStructs.Maker memory makerBidToExecute = makerBids[orderIndex]; + + // Verify invalidity of maker bid order + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerBidToExecute, + signature, + merkleTree, + ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE + ); + + vm.prank(takerUser); + vm.expectRevert(MerkleProofInvalid.selector); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBidToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + + function testTakerBidMultipleOrdersSignedERC721MerkleProofWrongPosition( + uint256 numberOrders, + uint256 orderIndex + ) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(makerUser, numberOrders); + + OrderStructs.Maker[] memory makerAsks = _createBatchMakerAsks(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerAsks, + orderIndex + ); + + // Swap every node's position + OrderStructs.MerkleTreeNode[] memory proof = merkleTree.proof; + for (uint256 i; i < proof.length; i++) { + if (proof[i].position == OrderStructs.MerkleTreeNodePosition.Left) { + proof[i].position = OrderStructs.MerkleTreeNodePosition.Right; + } else { + proof[i].position = OrderStructs.MerkleTreeNodePosition.Left; + } + } + + OrderStructs.Maker memory makerAskToExecute = makerAsks[orderIndex]; + + // Verify invalidity of maker ask order + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerAskToExecute, + signature, + merkleTree, + ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE + ); + + vm.prank(takerUser); + vm.expectRevert(MerkleProofInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + _genericTakerOrder(), + makerAskToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + + function testTakerAskMultipleOrdersSignedERC721MerkleProofWrongPosition( + uint256 numberOrders, + uint256 orderIndex + ) public { + _assertMerkleTreeAssumptions(numberOrders, orderIndex); + + mockERC721.batchMint(takerUser, numberOrders); + + OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerBids, + orderIndex + ); + + // Swap every node's position + OrderStructs.MerkleTreeNode[] memory proof = merkleTree.proof; + for (uint256 i; i < proof.length; i++) { + if (proof[i].position == OrderStructs.MerkleTreeNodePosition.Left) { + proof[i].position = OrderStructs.MerkleTreeNodePosition.Right; + } else { + proof[i].position = OrderStructs.MerkleTreeNodePosition.Left; + } + } + + OrderStructs.Maker memory makerBidToExecute = makerBids[orderIndex]; + + // Verify invalidity of maker bid order + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerBidToExecute, + signature, + merkleTree, + ORDER_HASH_PROOF_NOT_IN_MERKLE_TREE + ); + + vm.prank(takerUser); + vm.expectRevert(MerkleProofInvalid.selector); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBidToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + + function testTakerBidRevertsIfProofTooLarge() public { + uint256 testProofLengthUpTo = MAX_CALLDATA_PROOF_LENGTH + 3; + mockERC721.batchMint(makerUser, 2 ** testProofLengthUpTo); + + // Keep it reasonably large + for (uint256 proofLength = MAX_CALLDATA_PROOF_LENGTH + 1; proofLength <= testProofLengthUpTo; proofLength++) { + uint256 numberOrders = 2 ** proofLength; + uint256 orderIndex = numberOrders - 1; + + OrderStructs.Maker[] memory makerAsks = _createBatchMakerAsks(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerAsks, + orderIndex + ); + + OrderStructs.Maker memory makerAskToExecute = makerAsks[orderIndex]; + + // Verify validity + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerAskToExecute, + signature, + merkleTree, + MERKLE_PROOF_PROOF_TOO_LARGE + ); + + vm.prank(takerUser); + vm.expectRevert(abi.encodeWithSelector(MerkleProofTooLarge.selector, proofLength)); + looksRareProtocol.executeTakerBid{ value: price }( + _genericTakerOrder(), + makerAskToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + } + + function testTakerAskRevertsIfProofTooLarge() public { + uint256 testProofLengthUpTo = MAX_CALLDATA_PROOF_LENGTH + 3; + mockERC721.batchMint(takerUser, 2 ** testProofLengthUpTo); + + // Keep it reasonably large + for (uint256 proofLength = MAX_CALLDATA_PROOF_LENGTH + 1; proofLength <= testProofLengthUpTo; proofLength++) { + uint256 numberOrders = 2 ** proofLength; + uint256 orderIndex = numberOrders - 1; + + OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); + + (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign( + makerUserPK, + makerBids, + orderIndex + ); + + OrderStructs.Maker memory makerBidToExecute = makerBids[orderIndex]; + + // Verify validity + _assertMakerOrderReturnValidationCodeWithMerkleTree( + makerBidToExecute, + signature, + merkleTree, + MERKLE_PROOF_PROOF_TOO_LARGE + ); + + vm.prank(takerUser); + vm.expectRevert(abi.encodeWithSelector(MerkleProofTooLarge.selector, proofLength)); + looksRareProtocol.executeTakerBid{ value: price }( + _genericTakerOrder(), + makerBidToExecute, + signature, + merkleTree, + _EMPTY_AFFILIATE + ); + } + } + + function _createBatchMakerAsks(uint256 numberOrders) private view returns (OrderStructs.Maker[] memory makerAsks) { + makerAsks = new OrderStructs.Maker[](numberOrders); + for (uint256 i; i < numberOrders; i++) { + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, // incremental + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, + itemId: i + }); + } + } + + function _createBatchMakerBids(uint256 numberOrders) private view returns (OrderStructs.Maker[] memory makerBids) { + makerBids = new OrderStructs.Maker[](numberOrders); + for (uint256 i; i < numberOrders; i++) { + makerBids[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, // incremental + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: i + }); + } + } + + function _assertMerkleTreeAssumptions(uint256 numberOrders, uint256 orderIndex) private pure { + vm.assume(numberOrders > 0 && numberOrders <= 2 ** MAX_CALLDATA_PROOF_LENGTH); + vm.assume(orderIndex < numberOrders); + } +} diff --git a/contracts/test/foundry/marketplace/BatchOrderTypehashRegistry.t.sol b/contracts/test/foundry/marketplace/BatchOrderTypehashRegistry.t.sol new file mode 100644 index 00000000..cbbf7cb3 --- /dev/null +++ b/contracts/test/foundry/marketplace/BatchOrderTypehashRegistry.t.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { Test } from "forge-std/Test.sol"; + +import { BatchOrderTypehashRegistry } from "@hypercerts/marketplace/BatchOrderTypehashRegistry.sol"; + +// Shared errors +import { MerkleProofTooLarge } from "@hypercerts/marketplace/errors/SharedErrors.sol"; + +contract BatchOrderTypehashRegistryInheriter is BatchOrderTypehashRegistry { + function getBatchOrderTypehash(uint256 height) external pure returns (bytes32 typehash) { + return _getBatchOrderTypehash(height); + } +} + +contract BatchOrderTypehashRegistryTest is Test { + function testHash() public { + BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); + bytes32 root = hex"6942000000000000000000000000000000000000000000000000000000000000"; + assertEq( + registry.hashBatchOrder(root, 1), + hex"8f0c85a215cff55fe39cf62ee7a1e0b5205a8ade02ff12ffee9ece02d626ffc3" + ); + assertEq( + registry.hashBatchOrder(root, 2), + hex"f04a7d8a4688cf084b00b51ed583de7e5a19e59b073635e00a45a474899e89ec" + ); + assertEq( + registry.hashBatchOrder(root, 3), + hex"56ef3bb8c564d19cfe494776934aa5e7ed84c41ae609d5f10e726f76281dd30b" + ); + assertEq( + registry.hashBatchOrder(root, 4), + hex"2b0cb021eacab73e36d9ac9a04c1cf58589ff5bb4dc0d9b88ec29f67358ca812" + ); + assertEq( + registry.hashBatchOrder(root, 5), + hex"253b3cc8d591a8b01fc8967cefe3ac3d0e078b884d96aa589f1ffd4536921bbb" + ); + assertEq( + registry.hashBatchOrder(root, 6), + hex"7e4c4a2c5806fc4765bca325e8b78ccf9633bd1c7643144a56210293daefcbca" + ); + assertEq( + registry.hashBatchOrder(root, 7), + hex"e8e39cebe7137f0fadf6b88ba611044ac79c0168444eab66ca53bddd0c5fb717" + ); + assertEq( + registry.hashBatchOrder(root, 8), + hex"6e02f123509255ed381c7552de5e2ac1c1ea401a23e026e2452f01b70564affb" + ); + assertEq( + registry.hashBatchOrder(root, 9), + hex"7eeb4a7fe4655841fdd66f8ecfcf6cd261d50eafabbaebb10f63f5fe84ddddc9" + ); + assertEq( + registry.hashBatchOrder(root, 10), + hex"a96dee8b7b88deda5d50b55f641ca08c1ee00825eeb1db7a324f392fa0b8bb83" + ); + } + + function testGetTypehash() public { + BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); + bytes memory makerOrderString = bytes( + "Maker(" + "uint8 quoteType," + "uint256 globalNonce," + "uint256 subsetNonce," + "uint256 orderNonce," + "uint256 strategyId," + "uint8 collectionType," + "address collection," + "address currency," + "address signer," + "uint256 startTime," + "uint256 endTime," + "uint256 price," + "uint256[] itemIds," + "uint256[] amounts," + "bytes additionalParameters" + ")" + ); + + assertEq( + registry.getBatchOrderTypehash(1), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(2), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(3), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(4), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(5), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(6), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(7), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(8), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(9), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2][2] tree)"), makerOrderString)) + ); + + assertEq( + registry.getBatchOrderTypehash(10), + keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2][2][2] tree)"), makerOrderString)) + ); + } + + function testGetTypehashMerkleProofTooLarge(uint256 height) public { + vm.assume(height > 10); + + BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); + vm.expectRevert(abi.encodeWithSelector(MerkleProofTooLarge.selector, height)); + registry.getBatchOrderTypehash(height); + } +} diff --git a/contracts/test/foundry/marketplace/BundleTransactions.t.sol b/contracts/test/foundry/marketplace/BundleTransactions.t.sol new file mode 100644 index 00000000..c7331cd3 --- /dev/null +++ b/contracts/test/foundry/marketplace/BundleTransactions.t.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Other tests +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +contract BundleTransactionsTest is ProtocolBase { + function setUp() public { + _setUp(); + } + + function testTakerAskERC721BundleNoRoyalties() public { + _setUpUsers(); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle(address(mockERC721), address(weth), numberItemsInBundle); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerBid, signature); + + // Mint the items + mockERC721.batchMint(takerUser, makerBid.itemIds); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertMockERC721Ownership(makerBid.itemIds, makerUser); + + _assertSuccessfulTakerAskNoRoyalties(makerBid); + } + + function testTakerAskERC1155BundleNoRoyalties() public { + _setUpUsers(); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle(address(mockERC1155), address(weth), numberItemsInBundle); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerBid, signature); + + // Mint the items + mockERC1155.batchMint(takerUser, makerBid.itemIds, makerBid.amounts); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + for (uint256 i; i < makerBid.itemIds.length; i++) { + // Maker user has received all the assets in the bundle + assertEq(mockERC1155.balanceOf(makerUser, makerBid.itemIds[i]), makerBid.amounts[i]); + } + + _assertSuccessfulTakerAskNoRoyalties(makerBid); + } + + function testTakerAskERC721BundleWithRoyaltiesFromRegistry() public { + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle(address(mockERC721), address(weth), numberItemsInBundle); + + uint256 price = makerBid.price; + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerBid, signature); + + // Mint the items + mockERC721.batchMint(takerUser, makerBid.itemIds); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertMockERC721Ownership(makerBid.itemIds, makerUser); + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Royalty recipient receives royalties + assertEq( + weth.balanceOf(_royaltyRecipient), + _initialWETHBalanceRoyaltyRecipient + (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive protocol fee" + ); + // Taker ask user receives 99.5% of the whole price + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function testTakerBidERC721BundleNoRoyalties() public { + _setUpUsers(); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerAsk, + OrderStructs.Taker memory takerBid + ) = _createMockMakerAskAndTakerBidWithBundle(address(mockERC721), numberItemsInBundle); + + uint256 price = makerAsk.price; + + // Mint the items and sign the order + mockERC721.batchMint(makerUser, makerAsk.itemIds); + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerAsk, signature); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + _assertMockERC721Ownership(makerAsk.itemIds, takerUser); + + _assertSuccessfulTakerBidNoRoyalties(makerAsk); + } + + function testTakerBidERC1155BundleNoRoyalties() public { + _setUpUsers(); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerAsk, + OrderStructs.Taker memory takerBid + ) = _createMockMakerAskAndTakerBidWithBundle(address(mockERC1155), numberItemsInBundle); + + uint256 price = makerAsk.price; + + // Mint the items and sign the order + mockERC1155.batchMint(makerUser, makerAsk.itemIds, makerAsk.amounts); + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerAsk, signature); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + for (uint256 i; i < makerAsk.itemIds.length; i++) { + // Taker user has received all the assets in the bundle + assertEq(mockERC1155.balanceOf(takerUser, makerAsk.itemIds[i]), makerAsk.amounts[i]); + } + + _assertSuccessfulTakerBidNoRoyalties(makerAsk); + } + + function testTakerBidERC721BundleWithRoyaltiesFromRegistry() public { + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerAsk, + OrderStructs.Taker memory takerBid + ) = _createMockMakerAskAndTakerBidWithBundle(address(mockERC721), numberItemsInBundle); + + uint256 price = makerAsk.price; + + // Mint the items and sign the order + mockERC721.batchMint(makerUser, makerAsk.itemIds); + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Verify validity + _assertValidMakerOrder(makerAsk, signature); + + // Execute taker bid transaction + vm.prank(takerUser); + + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + _assertMockERC721Ownership(makerAsk.itemIds, takerUser); + + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - price); + // Royalty recipient receives the royalties + assertEq( + address(_royaltyRecipient).balance, + _initialETHBalanceRoyaltyRecipient + (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + assertEq( + address(protocolFeeRecipient).balance, + (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive protocol fee" + ); + // Maker ask user receives 99.5% of the whole price + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerAsk.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function _assertSuccessfulTakerAskNoRoyalties(OrderStructs.Maker memory makerBid) private { + uint256 price = makerBid.price; + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Royalty recipient receives no royalty + assertEq(weth.balanceOf(_royaltyRecipient), _initialWETHBalanceRoyaltyRecipient); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + (price * _minTotalFeeBp) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive protocol fee" + ); + // Taker ask user receives 99.5% of the whole price (no royalties are paid) + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function _assertSuccessfulTakerBidNoRoyalties(OrderStructs.Maker memory makerAsk) private { + uint256 price = makerAsk.price; + + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - price); + // Royalty recipient receives no royalty + assertEq(address(_royaltyRecipient).balance, _initialETHBalanceRoyaltyRecipient); + assertEq( + address(protocolFeeRecipient).balance, + (price * _minTotalFeeBp) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive protocol fee" + ); + // Maker ask user receives 99.5% of the whole price (no royalties are paid) + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerAsk.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } +} diff --git a/contracts/test/foundry/marketplace/CreatorFeeManagerWithRebates.t.sol b/contracts/test/foundry/marketplace/CreatorFeeManagerWithRebates.t.sol new file mode 100644 index 00000000..6a3b7ac0 --- /dev/null +++ b/contracts/test/foundry/marketplace/CreatorFeeManagerWithRebates.t.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC721 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC721.sol"; + +import { CreatorFeeManagerWithRebates } from "@hypercerts/marketplace/CreatorFeeManagerWithRebates.sol"; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { ICreatorFeeManager } from "@hypercerts/marketplace/interfaces/ICreatorFeeManager.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; + +// Shared errors +import { BUNDLE_ERC2981_NOT_SUPPORTED } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +contract CreatorFeeManagerWithRebatesTest is ProtocolBase { + function setUp() public { + _setUp(); + CreatorFeeManagerWithRebates creatorFeeManager = new CreatorFeeManagerWithRebates(address(royaltyFeeRegistry)); + vm.prank(_owner); + looksRareProtocol.updateCreatorFeeManager(address(creatorFeeManager)); + orderValidator.deriveProtocolParameters(); + } + + function _setUpRoyaltiesRegistry(uint256 fee) private { + vm.prank(_owner); + royaltyFeeRegistry.updateRoyaltyInfoForCollection( + address(mockERC721), + _royaltyRecipient, + _royaltyRecipient, + fee + ); + } + + function _testCreatorFeeRebatesArePaid(address erc721) private { + _setUpUsers(); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + erc721, + address(weth) + ); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + if (erc721 == address(mockERC721)) { + // Adjust royalties + _setUpRoyaltiesRegistry(_standardRoyaltyFee); + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + } else if (erc721 == address(mockERC721WithRoyalties)) { + // Adjust ERC721 with royalties + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[0], + _royaltyRecipient, + _standardRoyaltyFee + ); + // Mint asset + mockERC721WithRoyalties.mint(takerUser, makerBid.itemIds[0]); + } + + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Verify ownership is transferred + assertEq(IERC721(erc721).ownerOf(makerBid.itemIds[0]), makerUser); + _assertSuccessfulTakerAsk(makerBid); + } + + function _testCreatorFeeRebatesArePaidForBundles(address erc721) private { + _setUpUsers(); + _setUpRoyaltiesRegistry(_standardRoyaltyFee); + + // Parameters + uint256 numberItemsInBundle = 5; + + // Create order + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle(erc721, address(weth), numberItemsInBundle); + + if (erc721 == address(mockERC721)) { + // Adjust royalties + _setUpRoyaltiesRegistry(_standardRoyaltyFee); + // Mint the items + mockERC721.batchMint(takerUser, makerBid.itemIds); + } else if (erc721 == address(mockERC721WithRoyalties)) { + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + _royaltyRecipient, + _standardRoyaltyFee + ); + } + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + } + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertValidMakerOrder(makerBid, signature); + + // Taker user actions + vm.prank(takerUser); + + // Execute taker ask transaction + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Verify ownership is transferred + for (uint256 i; i < makerBid.itemIds.length; i++) { + // Maker user has received all the assets in the bundle + assertEq(IERC721(erc721).ownerOf(makerBid.itemIds[i]), makerUser); + } + _assertSuccessfulTakerAsk(makerBid); + } + + function testCreatorRebatesArePaidForRoyaltyFeeManager() public { + _testCreatorFeeRebatesArePaid(address(mockERC721)); + } + + function testCreatorRebatesArePaidForERC2981() public { + _testCreatorFeeRebatesArePaid(address(mockERC721WithRoyalties)); + } + + function testCreatorRebatesArePaidForRoyaltyFeeManagerWithBundles() public { + _testCreatorFeeRebatesArePaidForBundles(address(mockERC721)); + } + + function testCreatorRoyaltiesGetPaidForERC2981WithBundles() public { + _testCreatorFeeRebatesArePaidForBundles(address(mockERC721WithRoyalties)); + } + + function testCreatorRoyaltiesRevertForEIP2981WithBundlesIfInfoDiffer() public { + _setUpUsers(); + + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle( + address(mockERC721WithRoyalties), + address(weth), + numberItemsInBundle + ); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + + _assertValidMakerOrder(makerBid, signature); + + /** + * Different recipient + */ + + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + i == 0 ? _royaltyRecipient : address(50), + 50 + ); + } + + _assertMakerOrderReturnValidationCode(makerBid, signature, BUNDLE_ERC2981_NOT_SUPPORTED); + + vm.prank(takerUser); + vm.expectRevert( + abi.encodeWithSelector( + ICreatorFeeManager.BundleEIP2981NotAllowed.selector, + address(mockERC721WithRoyalties) + ) + ); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCreatorRoyaltiesRevertForEIP2981WithBundlesIfAtLeastOneCallReverts(uint256 revertIndex) public { + _setUpUsers(); + + uint256 numberItemsInBundle = 5; + vm.assume(revertIndex < numberItemsInBundle); + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle( + address(mockERC721WithRoyalties), + address(weth), + numberItemsInBundle + ); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + + _assertValidMakerOrder(makerBid, signature); + + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + _royaltyRecipient, + // if greater than 10,000, will revert in royaltyInfo + i == revertIndex ? 10_001 : 50 + ); + } + + _assertMakerOrderReturnValidationCode(makerBid, signature, BUNDLE_ERC2981_NOT_SUPPORTED); + + vm.prank(takerUser); + vm.expectRevert( + abi.encodeWithSelector( + ICreatorFeeManager.BundleEIP2981NotAllowed.selector, + address(mockERC721WithRoyalties) + ) + ); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function _assertSuccessfulTakerAsk(OrderStructs.Maker memory makerBid) private { + uint256 price = makerBid.price; + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP, + "Protocol fee recipient should receive 0.5% of the whole price" + ); + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * 9_900) / ONE_HUNDRED_PERCENT_IN_BP, + "Taker ask user should receive 99% of the whole price" + ); + assertEq( + weth.balanceOf(_royaltyRecipient), + _initialWETHBalanceRoyaltyRecipient + (price * 50) / ONE_HUNDRED_PERCENT_IN_BP, + "Royalty recipient receives 0.5% of the whole price" + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } +} diff --git a/contracts/test/foundry/marketplace/CreatorFeeManagerWithRoyalties.t.sol b/contracts/test/foundry/marketplace/CreatorFeeManagerWithRoyalties.t.sol new file mode 100644 index 00000000..d4fea298 --- /dev/null +++ b/contracts/test/foundry/marketplace/CreatorFeeManagerWithRoyalties.t.sol @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { ICreatorFeeManager } from "@hypercerts/marketplace/interfaces/ICreatorFeeManager.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; + +// Core contract +import { CreatorFeeManagerWithRoyalties } from "@hypercerts/marketplace/CreatorFeeManagerWithRoyalties.sol"; + +// Shared errors +import { BUNDLE_ERC2981_NOT_SUPPORTED, CREATOR_FEE_TOO_HIGH } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +contract CreatorFeeManagerWithRoyaltiesTest is ProtocolBase { + CreatorFeeManagerWithRoyalties public creatorFeeManagerWithRoyalties; + + // New protocol fee + uint16 internal constant _newProtocolFee = 200; + + // New creator royalty fee + uint256 internal constant _newCreatorRoyaltyFee = 300; + + function _setUpRoyaltiesRegistry(uint256 fee) internal { + vm.prank(_owner); + royaltyFeeRegistry.updateRoyaltyInfoForCollection( + address(mockERC721), + _royaltyRecipient, + _royaltyRecipient, + fee + ); + } + + function setUp() public { + _setUp(); + creatorFeeManagerWithRoyalties = new CreatorFeeManagerWithRoyalties(address(royaltyFeeRegistry)); + vm.startPrank(_owner); + looksRareProtocol.updateCreatorFeeManager(address(creatorFeeManagerWithRoyalties)); + // Set up 2% as protocol fee, which is now equal to minimum fee + looksRareProtocol.updateStrategy(0, true, _newProtocolFee, _newProtocolFee); + vm.stopPrank(); + + // Adjust for new creator fee manager + orderValidator.deriveProtocolParameters(); + } + + function testCreatorRoyaltiesGetPaidForRoyaltyFeeManager() public { + _setUpUsers(); + + // Adjust royalties + _setUpRoyaltiesRegistry(_newCreatorRoyaltyFee); + + (OrderStructs.Maker memory makerBid, ) = _createMockMakerBidAndTakerAsk(address(mockERC721), address(weth)); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBid, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(makerBid.itemIds[0]), makerUser); + _assertSuccessfulTakerAsk(makerBid); + } + + function testCreatorRoyaltiesGetPaidForERC2981() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerBid, ) = _createMockMakerBidAndTakerAsk( + address(mockERC721WithRoyalties), + address(weth) + ); + + // Adjust ERC721 with royalties + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[0], + _royaltyRecipient, + _newCreatorRoyaltyFee + ); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721WithRoyalties.mint(takerUser, makerBid.itemIds[0]); + + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBid, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Taker user has received the asset + assertEq(mockERC721WithRoyalties.ownerOf(makerBid.itemIds[0]), makerUser); + _assertSuccessfulTakerAsk(makerBid); + } + + function testCreatorRoyaltiesGetPaidForRoyaltyFeeManagerWithBundles() public { + _setUpUsers(); + + // Adjust royalties + _setUpRoyaltiesRegistry(_newCreatorRoyaltyFee); + + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle(address(mockERC721), address(weth), numberItemsInBundle); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721.batchMint(takerUser, makerBid.itemIds); + + // Check order validity + _assertValidMakerOrder(makerBid, signature); + + // Taker user actions + vm.prank(takerUser); + + // Execute taker ask transaction + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertMockERC721Ownership(makerBid.itemIds, makerUser); + + _assertSuccessfulTakerAskBundle(makerBid); + } + + function testCreatorRoyaltiesGetPaidForERC2981WithBundles() public { + _setUpUsers(); + + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle( + address(mockERC721WithRoyalties), + address(weth), + numberItemsInBundle + ); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + _royaltyRecipient, + _newCreatorRoyaltyFee + ); + } + + _assertValidMakerOrder(makerBid, signature); + + // Taker user actions + vm.prank(takerUser); + + // Execute taker ask transaction + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertSuccessfulTakerAskBundle(makerBid); + } + + function testCreatorRoyaltiesRevertForEIP2981WithBundlesIfInfoDiffer() public { + _setUpUsers(); + + uint256 numberItemsInBundle = 5; + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle( + address(mockERC721WithRoyalties), + address(weth), + numberItemsInBundle + ); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + + /** + * 1. Different fee structure but same recipient + */ + + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + _royaltyRecipient, + _newCreatorRoyaltyFee - i // It is not equal + ); + } + + _assertMakerOrderReturnValidationCode(makerBid, signature, BUNDLE_ERC2981_NOT_SUPPORTED); + + // Taker user action should revert + vm.prank(takerUser); + vm.expectRevert( + abi.encodeWithSelector( + ICreatorFeeManager.BundleEIP2981NotAllowed.selector, + address(mockERC721WithRoyalties) + ) + ); + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + /** + * 2. Same fee structure but different recipient + */ + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + i == 0 ? _royaltyRecipient : address(50), + _newCreatorRoyaltyFee + ); + } + + _assertMakerOrderReturnValidationCode(makerBid, signature, BUNDLE_ERC2981_NOT_SUPPORTED); + + vm.prank(takerUser); + vm.expectRevert( + abi.encodeWithSelector( + ICreatorFeeManager.BundleEIP2981NotAllowed.selector, + address(mockERC721WithRoyalties) + ) + ); + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCreatorRoyaltiesRevertForEIP2981WithBundlesIfAtLeastOneCallReverts(uint256 revertIndex) public { + _setUpUsers(); + + uint256 numberItemsInBundle = 5; + vm.assume(revertIndex < numberItemsInBundle); + + ( + OrderStructs.Maker memory makerBid, + OrderStructs.Taker memory takerAsk + ) = _createMockMakerBidAndTakerAskWithBundle( + address(mockERC721WithRoyalties), + address(weth), + numberItemsInBundle + ); + + // Sign the order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint the items + mockERC721WithRoyalties.batchMint(takerUser, makerBid.itemIds); + + // Adjust ERC721 with royalties + for (uint256 i; i < makerBid.itemIds.length; i++) { + mockERC721WithRoyalties.addCustomRoyaltyInformationForTokenId( + makerBid.itemIds[i], + _royaltyRecipient, + // if greater than 10,000, will revert in royaltyInfo + i == revertIndex ? 10_001 : _newCreatorRoyaltyFee + ); + } + + _assertMakerOrderReturnValidationCode(makerBid, signature, BUNDLE_ERC2981_NOT_SUPPORTED); + + // Taker user action should revert + vm.prank(takerUser); + vm.expectRevert( + abi.encodeWithSelector( + ICreatorFeeManager.BundleEIP2981NotAllowed.selector, + address(mockERC721WithRoyalties) + ) + ); + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCreatorRoyaltiesRevertIfFeeHigherThanLimit() public { + _setUpUsers(); + uint256 _creatorRoyaltyFeeTooHigh = looksRareProtocol.maxCreatorFeeBp() + 1; + + // Adjust royalties + _setUpRoyaltiesRegistry(_creatorRoyaltyFeeTooHigh); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + _assertMakerOrderReturnValidationCode(makerBid, signature, CREATOR_FEE_TOO_HIGH); + + vm.expectRevert(IExecutionManager.CreatorFeeBpTooHigh.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 2. Maker ask + + // Mint asset + mockERC721.mint(makerUser, 1); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + // The itemId changes as it is already minted before + makerAsk.itemIds[0] = 1; + + signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, CREATOR_FEE_TOO_HIGH); + + vm.expectRevert(IExecutionManager.CreatorFeeBpTooHigh.selector); + vm.prank(takerUser); + + looksRareProtocol.executeTakerBid{ value: 1 ether }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function _assertSuccessfulTakerAsk(OrderStructs.Maker memory makerBid) private { + uint256 price = makerBid.price; + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + (price * _newProtocolFee) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive 2% of the whole price" + ); + // Taker ask user receives 95% of the whole price + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + (price * 9_500) / ONE_HUNDRED_PERCENT_IN_BP); + // Royalty recipient receives 3% of the whole price + assertEq( + weth.balanceOf(_royaltyRecipient), + _initialWETHBalanceRoyaltyRecipient + (price * _newCreatorRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function _assertSuccessfulTakerAskBundle(OrderStructs.Maker memory makerBid) private { + uint256 price = makerBid.price; + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Royalty recipient receives royalties + assertEq( + weth.balanceOf(_royaltyRecipient), + _initialWETHBalanceRoyaltyRecipient + (price * _newCreatorRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + (price * _newProtocolFee) / ONE_HUNDRED_PERCENT_IN_BP, + "ProtocolFeeRecipient should receive protocol fee" + ); + // Taker ask user receives 95% of the whole price + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + (price * 9_500) / ONE_HUNDRED_PERCENT_IN_BP); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } +} diff --git a/contracts/test/foundry/marketplace/CurrencyManager.t.sol b/contracts/test/foundry/marketplace/CurrencyManager.t.sol new file mode 100644 index 00000000..3b00915a --- /dev/null +++ b/contracts/test/foundry/marketplace/CurrencyManager.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; + +// Core contracts +import { CurrencyManager, ICurrencyManager } from "@hypercerts/marketplace/CurrencyManager.sol"; + +// Other mocks and utils +import { TestHelpers } from "./utils/TestHelpers.sol"; +import { TestParameters } from "./utils/TestParameters.sol"; +import { MockERC20 } from "../../mock/MockERC20.sol"; + +contract CurrencyManagerTest is TestHelpers, TestParameters, ICurrencyManager { + CurrencyManager private currencyManager; + MockERC20 private mockERC20; + + function setUp() public asPrankedUser(_owner) { + currencyManager = new CurrencyManager(_owner); + mockERC20 = new MockERC20(); + } + + function testUpdateCurrencyStatus() public asPrankedUser(_owner) { + // Set to true + vm.expectEmit(true, false, false, true); + emit CurrencyStatusUpdated(address(mockERC20), true); + currencyManager.updateCurrencyStatus(address(mockERC20), true); + assertTrue(currencyManager.isCurrencyAllowed(address(mockERC20))); + + // Set to false + vm.expectEmit(true, false, false, true); + emit CurrencyStatusUpdated(address(mockERC20), false); + currencyManager.updateCurrencyStatus(address(mockERC20), false); + assertFalse(currencyManager.isCurrencyAllowed(address(mockERC20))); + } + + function testUpdateCurrencyStatusNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + currencyManager.updateCurrencyStatus(address(mockERC20), true); + } +} diff --git a/contracts/test/foundry/marketplace/DelegationRecipientsTaker.t.sol b/contracts/test/foundry/marketplace/DelegationRecipientsTaker.t.sol new file mode 100644 index 00000000..b052c146 --- /dev/null +++ b/contracts/test/foundry/marketplace/DelegationRecipientsTaker.t.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { CreatorFeeManagerWithRoyalties } from "@hypercerts/marketplace/CreatorFeeManagerWithRoyalties.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +contract DelegationRecipientsTakerTest is ProtocolBase { + function setUp() public { + _setUp(); + CreatorFeeManagerWithRoyalties creatorFeeManager = new CreatorFeeManagerWithRoyalties( + address(royaltyFeeRegistry) + ); + vm.prank(_owner); + looksRareProtocol.updateCreatorFeeManager(address(creatorFeeManager)); + } + + // Fixed price of sale + uint256 private constant price = 1 ether; + + /** + * One ERC721 is sold through a taker ask using WETH and the proceeds of the sale goes to a random recipient. + */ + function testTakerAskERC721WithRoyaltiesFromRegistryWithDelegation() public { + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + address randomRecipientSaleProceeds = address(420); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Adjust recipient + takerAsk.recipient = randomRecipientSaleProceeds; + + // Verify maker bid order + _assertValidMakerOrder(makerBid, signature); + + // Arrays for events + address[2] memory expectedRecipients; + expectedRecipients[0] = randomRecipientSaleProceeds; + expectedRecipients[1] = _royaltyRecipient; + + uint256[3] memory expectedFees; + expectedFees[2] = (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + expectedFees[1] = (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP; + expectedFees[0] = price - (expectedFees[1] + expectedFees[2]); + + vm.prank(takerUser); + vm.expectEmit(true, false, false, true); + + emit TakerAsk( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerBid), + orderNonce: makerBid.orderNonce, + isNonceInvalidated: true + }), + takerUser, + makerUser, + makerBid.strategyId, + makerBid.currency, + makerBid.collection, + makerBid.itemIds, + makerBid.amounts, + expectedRecipients, + expectedFees + ); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(makerBid.itemIds[0]), makerUser); + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Random recipient user receives 99.5% of the whole price and taker user receives nothing. + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser); + assertEq( + weth.balanceOf(randomRecipientSaleProceeds), + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Royalty recipient receives 0.5% of the whole price + assertEq( + weth.balanceOf(_royaltyRecipient), + _initialWETHBalanceRoyaltyRecipient + (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + /** + * One ERC721 is sold through a taker bid and the NFT transfer goes to a random recipient. + */ + function testTakerBidERC721WithRoyaltiesFromRegistryWithDelegation() public { + address randomRecipientNFT = address(420); + + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Adjust recipient to random recipient + takerBid.recipient = randomRecipientNFT; + + // Verify validity of maker ask order + _assertValidMakerOrder(makerAsk, signature); + + // Arrays for events + address[2] memory expectedRecipients; + expectedRecipients[0] = makerUser; + expectedRecipients[1] = _royaltyRecipient; + + uint256[3] memory expectedFees; + expectedFees[0] = price - (expectedFees[1] + expectedFees[0]); + expectedFees[1] = (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP; + expectedFees[2] = (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + + vm.prank(takerUser); + + emit TakerBid( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerAsk), + orderNonce: makerAsk.orderNonce, + isNonceInvalidated: true + }), + takerUser, + randomRecipientNFT, + makerAsk.strategyId, + makerAsk.currency, + makerAsk.collection, + makerAsk.itemIds, + makerAsk.amounts, + expectedRecipients, + expectedFees + ); + + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Random recipient user has received the asset + assertEq(mockERC721.ownerOf(makerAsk.itemIds[0]), randomRecipientNFT); + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - price); + // Maker ask user receives 99.5% of the whole price + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Royalty recipient receives 0.5% of the whole price + assertEq( + address(_royaltyRecipient).balance, + _initialETHBalanceRoyaltyRecipient + (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerAsk.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } +} diff --git a/contracts/test/foundry/marketplace/DomainSeparatorUpdates.t.sol b/contracts/test/foundry/marketplace/DomainSeparatorUpdates.t.sol new file mode 100644 index 00000000..3936ba5a --- /dev/null +++ b/contracts/test/foundry/marketplace/DomainSeparatorUpdates.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; +import { SignatureEOAInvalid } from "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { ILooksRareProtocol } from "@hypercerts/marketplace/interfaces/ILooksRareProtocol.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +contract DomainSeparatorUpdatesTest is ProtocolBase { + function setUp() public { + _setUp(); + } + + function testUpdateDomainSeparator(uint64 newChainId) public asPrankedUser(_owner) { + vm.assume(newChainId != block.chainid); + + vm.chainId(newChainId); + vm.expectEmit(true, false, false, true); + emit NewDomainSeparator(); + looksRareProtocol.updateDomainSeparator(); + assertEq(looksRareProtocol.chainId(), newChainId); + assertEq( + looksRareProtocol.domainSeparator(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("LooksRareProtocol"), + keccak256(bytes("2")), + newChainId, + address(looksRareProtocol) + ) + ) + ); + } + + function testCannotTradeIfDomainSeparatorHasBeenUpdated(uint64 newChainId) public { + vm.assume(newChainId != block.chainid); + + _setUpUsers(); + + // ChainId update + vm.chainId(newChainId); + + // Owner updates the domain separator + vm.prank(_owner); + looksRareProtocol.updateDomainSeparator(); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + vm.prank(takerUser); + vm.expectRevert(SignatureEOAInvalid.selector); + looksRareProtocol.executeTakerBid{ value: makerAsk.price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testCannotTradeIfChainIdHasChanged(uint64 newChainId) public { + vm.assume(newChainId != block.chainid); + + _setUpUsers(); + + // ChainId update + vm.chainId(newChainId); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + vm.prank(takerUser); + vm.expectRevert(ILooksRareProtocol.ChainIdInvalid.selector); + looksRareProtocol.executeTakerBid{ value: makerAsk.price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testUpdateDomainSeparatorSameDomainSeparator() public asPrankedUser(_owner) { + vm.expectRevert(SameDomainSeparator.selector); + looksRareProtocol.updateDomainSeparator(); + } + + function testUpdateDomainSeparatorNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateDomainSeparator(); + } +} diff --git a/contracts/test/foundry/marketplace/ExecutionManager.t.sol b/contracts/test/foundry/marketplace/ExecutionManager.t.sol new file mode 100644 index 00000000..edc413af --- /dev/null +++ b/contracts/test/foundry/marketplace/ExecutionManager.t.sol @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Shared errors +import { OrderInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { MAKER_ORDER_INVALID_STANDARD_SALE, STRATEGY_INVALID_QUOTE_TYPE, STRATEGY_INVALID_QUOTE_TYPE, STRATEGY_NOT_ACTIVE, START_TIME_GREATER_THAN_END_TIME, TOO_LATE_TO_EXECUTE_ORDER, TOO_EARLY_TO_EXECUTE_ORDER } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +contract ExecutionManagerTest is ProtocolBase, IExecutionManager, IStrategyManager { + function setUp() public { + _setUp(); + } + + function testUpdateCreatorFeeManager() public asPrankedUser(_owner) { + vm.expectEmit(true, false, false, true); + emit NewCreatorFeeManager(address(1)); + looksRareProtocol.updateCreatorFeeManager(address(1)); + assertEq(address(looksRareProtocol.creatorFeeManager()), address(1)); + } + + function testUpdateCreatorFeeManagerNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateCreatorFeeManager(address(1)); + } + + function testUpdateMaxCreatorFeeBp(uint16 newMaxCreatorFeeBp) public asPrankedUser(_owner) { + vm.assume(newMaxCreatorFeeBp <= 2_500); + vm.expectEmit(true, false, false, true); + emit NewMaxCreatorFeeBp(newMaxCreatorFeeBp); + looksRareProtocol.updateMaxCreatorFeeBp(newMaxCreatorFeeBp); + assertEq(looksRareProtocol.maxCreatorFeeBp(), newMaxCreatorFeeBp); + } + + function testUpdateMaxCreatorFeeBpNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateMaxCreatorFeeBp(uint16(2_500)); + } + + function testUpdateMaxCreatorFeeBpTooHigh(uint16 newMaxCreatorFeeBp) public asPrankedUser(_owner) { + vm.assume(newMaxCreatorFeeBp > 2_500); + vm.expectRevert(CreatorFeeBpTooHigh.selector); + looksRareProtocol.updateMaxCreatorFeeBp(newMaxCreatorFeeBp); + } + + function testUpdateProtocolFeeRecipient() public asPrankedUser(_owner) { + vm.expectEmit(true, false, false, true); + emit NewProtocolFeeRecipient(address(1)); + looksRareProtocol.updateProtocolFeeRecipient(address(1)); + assertEq(looksRareProtocol.protocolFeeRecipient(), address(1)); + } + + function testUpdateProtocolFeeRecipientCannotBeNullAddress() public asPrankedUser(_owner) { + vm.expectRevert(IExecutionManager.NewProtocolFeeRecipientCannotBeNullAddress.selector); + looksRareProtocol.updateProtocolFeeRecipient(address(0)); + } + + function testUpdateProtocolFeeRecipientNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateProtocolFeeRecipient(address(1)); + } + + function testCannotValidateOrderIfTooEarlyToExecute(uint256 timestamp) public asPrankedUser(takerUser) { + // 300 because because it is deducted by 5 minutes + 1 second + vm.assume(timestamp > 300 && timestamp < type(uint256).max); + // Change timestamp to avoid underflow issues + vm.warp(timestamp); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + makerBid.startTime = block.timestamp; + makerBid.endTime = block.timestamp + 1 seconds; + + // Maker bid is valid if its start time is within 5 minutes into the future + vm.warp(makerBid.startTime - 5 minutes); + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + _assertMakerOrderReturnValidationCode(makerBid, signature, TOO_EARLY_TO_EXECUTE_ORDER); + + // Maker bid is invalid if its start time is not within 5 minutes into the future + vm.warp(makerBid.startTime - 5 minutes - 1 seconds); + vm.expectRevert(OutsideOfTimeRange.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotValidateOrderIfTooLateToExecute(uint256 timestamp) public asPrankedUser(takerUser) { + vm.assume(timestamp > 0 && timestamp < type(uint256).max); + // Change timestamp to avoid underflow issues + vm.warp(timestamp); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + makerBid.startTime = block.timestamp - 1 seconds; + makerBid.endTime = block.timestamp; + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + vm.warp(block.timestamp); + _assertMakerOrderReturnValidationCode(makerBid, signature, TOO_LATE_TO_EXECUTE_ORDER); + + vm.warp(block.timestamp + 1 seconds); + vm.expectRevert(OutsideOfTimeRange.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotValidateOrderIfStartTimeLaterThanEndTime(uint256 timestamp) public asPrankedUser(takerUser) { + vm.assume(timestamp < type(uint256).max); + // Change timestamp to avoid underflow issues + vm.warp(timestamp); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + makerBid.startTime = block.timestamp + 1 seconds; + makerBid.endTime = block.timestamp; + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, START_TIME_GREATER_THAN_END_TIME); + + vm.expectRevert(OutsideOfTimeRange.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotValidateOrderIfMakerBidItemIdsIsEmpty() public { + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + uint256[] memory itemIds = new uint256[](0); + makerBid.itemIds = itemIds; + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotValidateOrderIfMakerBidItemIdsLengthMismatch( + uint256 makerBidItemIdsLength + ) public asPrankedUser(takerUser) { + vm.assume(makerBidItemIdsLength > 1 && makerBidItemIdsLength < 100_000); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + uint256[] memory itemIds = new uint256[](makerBidItemIdsLength); + makerBid.itemIds = itemIds; + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotValidateOrderIfMakerAskItemIdsIsEmpty() public asPrankedUser(takerUser) { + vm.deal(takerUser, 100 ether); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + // Change maker itemIds array to make its length equal to 0 + uint256[] memory itemIds = new uint256[](0); + makerAsk.itemIds = itemIds; + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerBid{ value: makerAsk.price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testCannotValidateOrderIfMakerAskItemIdsLengthMismatch( + uint256 makerAskItemIdsLength + ) public asPrankedUser(takerUser) { + vm.deal(takerUser, 100 ether); + + vm.assume(makerAskItemIdsLength > 1 && makerAskItemIdsLength < 100_000); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + uint256[] memory itemIds = new uint256[](makerAskItemIdsLength); + makerAsk.itemIds = itemIds; + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerBid{ value: makerAsk.price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + // TODO check is we need this test and replace the chainlink floor strategy + // function testCannotExecuteTransactionIfMakerBidWithStrategyForMakerAsk() public { + // _setUpUsers(); + + // vm.prank(_owner); + // StrategyChainlinkFloor strategy = new StrategyChainlinkFloor(_owner, address(weth)); + + // bool isMakerBid = true; + // vm.prank(_owner); + // looksRareProtocol.addStrategy( + // 250, + // 250, + // 300, + // StrategyChainlinkFloor.executeBasisPointsDiscountCollectionOfferStrategyWithTakerAsk.selector, + // isMakerBid, + // address(strategy) + // ); + + // (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = + // _createMockMakerAskAndTakerBid(address(mockERC721)); + // makerAsk.strategyId = 1; // Fake strategy + + // bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // // Mint asset + // mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // _assertMakerOrderReturnValidationCode(makerAsk, signature, STRATEGY_INVALID_QUOTE_TYPE); + + // vm.prank(takerUser); + // vm.expectRevert(IExecutionManager.NoSelectorForStrategy.selector); + // looksRareProtocol.executeTakerBid{value: makerAsk.price}( + // takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE + // ); + // } + + // TODO check is we need this test and replace the chainlink floor strategy + // function testCannotExecuteTransactionIfMakerAskWithStrategyForMakerBid() public { + // _setUpUsers(); + + // vm.prank(_owner); + // StrategyChainlinkFloor strategy = new StrategyChainlinkFloor(_owner, address(weth)); + + // bool isMakerBid = false; + // vm.prank(_owner); + // // All parameters are random, including the selector and the implementation + // looksRareProtocol.addStrategy( + // 250, + // 250, + // 300, + // StrategyChainlinkFloor.executeFixedPremiumStrategyWithTakerBid.selector, + // isMakerBid, + // address(strategy) + // ); + + // (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = + // _createMockMakerBidAndTakerAsk(address(mockERC721), address(weth)); + // makerBid.strategyId = 1; // Fake strategy + + // bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // // Mint asset to ask user + // mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // _assertMakerOrderReturnValidationCode(makerBid, signature, STRATEGY_INVALID_QUOTE_TYPE); + + // vm.prank(takerUser); + // vm.expectRevert(IExecutionManager.NoSelectorForStrategy.selector); + // looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + // } +} diff --git a/contracts/test/foundry/marketplace/GasGriefing.t.sol b/contracts/test/foundry/marketplace/GasGriefing.t.sol new file mode 100644 index 00000000..31852b5a --- /dev/null +++ b/contracts/test/foundry/marketplace/GasGriefing.t.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { WETH } from "solmate/src/tokens/WETH.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Mocks and other utils +import { GasGriefer } from "./utils/GasGriefer.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract GasGriefingTest is ProtocolBase { + uint256 private constant price = 1 ether; // Fixed price of sale + address private gasGriefer; + + // WETH events + event Deposit(address indexed from, uint256 amount); + event Transfer(address indexed from, address indexed to, uint256 amount); + + function setUp() public { + _setUp(); + gasGriefer = address(new GasGriefer()); + _setUpUser(gasGriefer); + _setUpUser(takerUser); + } + + function testTakerBidGasGriefing() public { + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.signer = gasGriefer; + + // Mint asset + mockERC721.mint(gasGriefer, makerAsk.itemIds[0]); + + bytes memory signature; + + uint256 sellerProceed = (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + + vm.expectEmit(true, true, false, true); + emit Deposit(address(looksRareProtocol), sellerProceed); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(looksRareProtocol), gasGriefer, sellerProceed); + + vm.prank(takerUser); + // Execute taker bid transaction + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(makerAsk.itemIds[0]), takerUser); + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - price); + // Maker ask user receives 99.5% of the whole price + assertEq(weth.balanceOf(gasGriefer), _initialWETHBalanceUser + sellerProceed); + // Royalty recipient receives 0.5% of the whole price + assertEq( + address(_royaltyRecipient).balance, + _initialETHBalanceRoyaltyRecipient + (price * _standardRoyaltyFee) / ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(gasGriefer, makerAsk.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function testThreeTakerBidsGasGriefing() public { + uint256 numberPurchases = 3; + + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](numberPurchases); + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](numberPurchases); + bytes[] memory signatures = new bytes[](numberPurchases); + + for (uint256 i; i < numberPurchases; i++) { + // Mint asset + mockERC721.mint(gasGriefer, i); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, + collection: address(mockERC721), + currency: ETH, + signer: gasGriefer, + price: price, // Fixed + itemId: i // (0, 1, etc.) + }); + + takerBids[i] = _genericTakerOrder(); + } + + // Other execution parameters + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + uint256 sellerProceedPerItem = (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + + vm.expectEmit(true, true, false, true); + emit Deposit(address(looksRareProtocol), sellerProceedPerItem); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(looksRareProtocol), gasGriefer, sellerProceedPerItem); + + vm.prank(takerUser); + // Execute taker bid transaction + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + for (uint256 i; i < numberPurchases; i++) { + // Taker user has received the asset + assertEq(mockERC721.ownerOf(i), takerUser); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(gasGriefer, i), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - (numberPurchases * price)); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq(weth.balanceOf(gasGriefer), _initialWETHBalanceUser + sellerProceedPerItem * numberPurchases); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + } +} diff --git a/contracts/test/foundry/marketplace/InitialStates.t.sol b/contracts/test/foundry/marketplace/InitialStates.t.sol new file mode 100644 index 00000000..c17f827b --- /dev/null +++ b/contracts/test/foundry/marketplace/InitialStates.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Interfaces +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +contract InitialStatesTest is ProtocolBase, IStrategyManager { + function setUp() public { + _setUp(); + } + + /** + * Verify initial post-deployment states are as expected + */ + function testInitialStates() public { + assertEq(looksRareProtocol.owner(), _owner); + assertEq(looksRareProtocol.protocolFeeRecipient(), address(protocolFeeRecipient)); + assertEq(address(looksRareProtocol.transferManager()), address(transferManager)); + assertEq(looksRareProtocol.WETH(), address(weth)); + assertEq(looksRareProtocol.chainId(), block.chainid); + + bytes32 domainSeparator = looksRareProtocol.domainSeparator(); + bytes32 expectedDomainSeparator = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("LooksRareProtocol"), + keccak256(bytes("2")), + block.chainid, + address(looksRareProtocol) + ) + ); + assertEq(domainSeparator, expectedDomainSeparator); + + ( + bool strategyIsActive, + uint16 strategyStandardProtocolFee, + uint16 strategyMinTotalFee, + uint16 strategyMaxProtocolFee, + bytes4 strategySelector, + bool strategyIsMakerBid, + address strategyImplementation + ) = looksRareProtocol.strategyInfo(0); + + assertTrue(strategyIsActive); + assertEq(strategyStandardProtocolFee, _standardProtocolFeeBp); + assertEq(strategyMinTotalFee, _minTotalFeeBp); + assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); + assertEq(strategySelector, _EMPTY_BYTES4); + assertFalse(strategyIsMakerBid); + assertEq(strategyImplementation, address(0)); + } +} diff --git a/contracts/test/foundry/marketplace/LooksRareProtocol.t.sol b/contracts/test/foundry/marketplace/LooksRareProtocol.t.sol new file mode 100644 index 00000000..73027c4b --- /dev/null +++ b/contracts/test/foundry/marketplace/LooksRareProtocol.t.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Shared errors +import { AmountInvalid, CallerInvalid, CurrencyInvalid, OrderInvalid, QuoteTypeInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { CURRENCY_NOT_ALLOWED, MAKER_ORDER_INVALID_STANDARD_SALE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Other mocks and utils +import { MockERC20 } from "../../mock/MockERC20.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract LooksRareProtocolTest is ProtocolBase { + // Fixed price of sale + uint256 private constant price = 1 ether; + + // Mock files + MockERC20 private mockERC20; + + function setUp() public { + _setUp(); + vm.prank(_owner); + mockERC20 = new MockERC20(); + } + + function testCannotTradeIfInvalidAmounts() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // 1. Amount = 0 + makerAsk.amounts[0] = 0; + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // 2. Amount > 1 for ERC721 + makerAsk.amounts[0] = 2; + + // Sign order + signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_INVALID_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testCannotTradeIfCurrencyInvalid() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.currency = address(mockERC20); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Verify validity of maker ask order + _assertMakerOrderReturnValidationCode(makerAsk, signature, CURRENCY_NOT_ALLOWED); + + vm.prank(takerUser); + vm.expectRevert(CurrencyInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](1); + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](1); + bytes[] memory signatures = new bytes[](1); + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](1); + + makerAsks[0] = makerAsk; + takerBids[0] = takerBid; + signatures[0] = signature; + + vm.prank(takerUser); + vm.expectRevert(CurrencyInvalid.selector); + looksRareProtocol.executeMultipleTakerBids{ value: price }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + true // Atomic + ); + + vm.prank(takerUser); + vm.expectRevert(CurrencyInvalid.selector); + looksRareProtocol.executeMultipleTakerBids{ value: price }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false // Non-atomic + ); + } + + function testCannotTradeIfETHIsUsedForMakerBid() public { + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + ETH + ); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify maker bid order + _assertMakerOrderReturnValidationCode(makerBid, signature, CURRENCY_NOT_ALLOWED); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Execute taker ask transaction + vm.prank(takerUser); + vm.expectRevert(CurrencyInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotTradeIfInvalidQuoteType() public { + // 1. QuoteType = BID but executeTakerBid + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + vm.prank(takerUser); + vm.expectRevert(QuoteTypeInvalid.selector); + looksRareProtocol.executeTakerBid(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 2. QuoteType = ASK but executeTakerAsk + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.currency = address(weth); + + // Sign order + signature = _signMakerOrder(makerAsk, makerUserPK); + + vm.prank(takerUser); + vm.expectRevert(QuoteTypeInvalid.selector); + looksRareProtocol.executeTakerAsk(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testUpdateETHGasLimitForTransfer() public asPrankedUser(_owner) { + vm.expectEmit(true, false, false, true); + emit NewGasLimitETHTransfer(10_000); + looksRareProtocol.updateETHGasLimitForTransfer(10_000); + assertEq(uint256(vm.load(address(looksRareProtocol), bytes32(uint256(12)))), 10_000); + } + + function testUpdateETHGasLimitForTransferRevertsIfTooLow() public asPrankedUser(_owner) { + uint256 newGasLimitETHTransfer = 2_300; + vm.expectRevert(NewGasLimitETHTransferTooLow.selector); + looksRareProtocol.updateETHGasLimitForTransfer(newGasLimitETHTransfer - 1); + + looksRareProtocol.updateETHGasLimitForTransfer(newGasLimitETHTransfer); + assertEq(uint256(vm.load(address(looksRareProtocol), bytes32(uint256(12)))), newGasLimitETHTransfer); + } + + function testUpdateETHGasLimitForTransferNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateETHGasLimitForTransfer(10_000); + } + + function testCannotCallRestrictedExecuteTakerBid() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + vm.prank(takerUser); + vm.expectRevert(CallerInvalid.selector); + looksRareProtocol.restrictedExecuteTakerBid(takerBid, makerAsk, takerUser, _computeOrderHash(makerAsk)); + } + + /** + * Cannot execute two or more taker bids if the currencies are different + */ + function testCannotExecuteMultipleTakerBidsIfDifferentCurrenciesIsAtomic() public { + _testCannotExecuteMultipleTakerBidsIfDifferentCurrencies(true); + } + + function testCannotExecuteMultipleTakerBidsIfDifferentCurrenciesIsNonAtomic() public { + _testCannotExecuteMultipleTakerBidsIfDifferentCurrencies(false); + } + + function _testCannotExecuteMultipleTakerBidsIfDifferentCurrencies(bool isAtomic) public { + _setUpUsers(); + vm.prank(_owner); + looksRareProtocol.updateCurrencyStatus(address(mockERC20), true); + + uint256 numberPurchases = 2; + + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](numberPurchases); + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](numberPurchases); + bytes[] memory signatures = new bytes[](numberPurchases); + + for (uint256 i; i < numberPurchases; i++) { + // Mint asset + mockERC721.mint(makerUser, i); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, // Fixed + itemId: i // (0, 1, etc.) + }); + + if (i == 1) { + makerAsks[i].currency = address(mockERC20); + } + + // Sign order + signatures[i] = _signMakerOrder(makerAsks[i], makerUserPK); + + takerBids[i] = _genericTakerOrder(); + } + + // Other execution parameters + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + vm.prank(takerUser); + vm.expectRevert(CurrencyInvalid.selector); + looksRareProtocol.executeMultipleTakerBids{ value: price }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + isAtomic + ); + } +} diff --git a/contracts/test/foundry/marketplace/NonceInvalidation.t.sol b/contracts/test/foundry/marketplace/NonceInvalidation.t.sol new file mode 100644 index 00000000..594ec13a --- /dev/null +++ b/contracts/test/foundry/marketplace/NonceInvalidation.t.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries, interfaces, errors +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { INonceManager } from "@hypercerts/marketplace/interfaces/INonceManager.sol"; +import { LengthsInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { INVALID_USER_GLOBAL_BID_NONCE, INVALID_USER_GLOBAL_ASK_NONCE, USER_SUBSET_NONCE_CANCELLED, USER_ORDER_NONCE_IN_EXECUTION_WITH_OTHER_HASH, USER_ORDER_NONCE_EXECUTED_OR_CANCELLED } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Other utils and tests +import { StrategyTestMultiFillCollectionOrder } from "./utils/StrategyTestMultiFillCollectionOrder.sol"; +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract NonceInvalidationTest is INonceManager, ProtocolBase { + uint256 private constant price = 1 ether; // Fixed price of sale + + function setUp() public { + _setUp(); + } + + /** + * Cannot execute an order if subset nonce is used + */ + function testCannotExecuteOrderIfSubsetNonceIsUsed(uint256 subsetNonce) public { + uint256 itemId = 420; + + // Mint asset + mockERC721.mint(makerUser, itemId); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.subsetNonce = subsetNonce; + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + uint256[] memory subsetNonces = new uint256[](1); + subsetNonces[0] = subsetNonce; + + vm.prank(makerUser); + vm.expectEmit(false, false, false, true); + emit SubsetNoncesCancelled(makerUser, subsetNonces); + looksRareProtocol.cancelSubsetNonces(subsetNonces); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, USER_SUBSET_NONCE_CANCELLED); + + vm.deal(takerUser, price); + + // Execute taker bid transaction + // Taker user actions + vm.prank(takerUser); + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + /** + * Cannot execute an order if maker is at a different global ask nonce than signed + */ + function testCannotExecuteOrderIfInvalidUserGlobalAskNonce(uint256 userGlobalAskNonce) public { + uint256 quasiRandomNumber = 54570651553685478358117286254199992264; + vm.assume(userGlobalAskNonce < quasiRandomNumber); + // Change block number + vm.roll(1); + assertEq(quasiRandomNumber, uint256(blockhash(block.number - 1) >> 128)); + + uint256 newAskNonce = 0 + quasiRandomNumber; + + vm.prank(makerUser); + vm.expectEmit(false, false, false, true); + emit NewBidAskNonces(makerUser, 0, newAskNonce); + looksRareProtocol.incrementBidAskNonces(false, true); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, INVALID_USER_GLOBAL_ASK_NONCE); + + vm.deal(takerUser, price); + + // Execute taker bid transaction + // Taker user actions + vm.prank(takerUser); + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + /** + * Cannot execute an order if maker is at a different global bid nonce than signed + */ + function testCannotExecuteOrderIfInvalidUserGlobalBidNonce(uint256 userGlobalBidNonce) public { + uint256 quasiRandomNumber = 54570651553685478358117286254199992264; + vm.assume(userGlobalBidNonce < quasiRandomNumber); + // Change block number + vm.roll(1); + assertEq(quasiRandomNumber, uint256(blockhash(block.number - 1) >> 128)); + + uint256 newBidNonce = 0 + quasiRandomNumber; + + vm.prank(makerUser); + vm.expectEmit(false, false, false, true); + emit NewBidAskNonces(makerUser, newBidNonce, 0); + looksRareProtocol.incrementBidAskNonces(true, false); + + uint256 itemId = 420; + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: userGlobalBidNonce, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, INVALID_USER_GLOBAL_BID_NONCE); + + // Mint asset + mockERC721.mint(takerUser, itemId); + + // Execute taker ask transaction + // Taker user actions + vm.prank(takerUser); + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBid, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + /** + * Cannot execute an order twice + */ + function testCannotExecuteAnOrderTwice() public { + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Taker user actions + vm.startPrank(takerUser); + + { + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertMakerOrderReturnValidationCode(makerBid, signature, USER_ORDER_NONCE_EXECUTED_OR_CANCELLED); + + // Second one fails + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + vm.stopPrank(); + } + + /** + * Cannot execute an order sharing the same order nonce as another that is being partially filled + */ + function testCannotExecuteAnotherOrderAtNonceIfExecutionIsInProgress(uint256 orderNonce) public { + _setUpUsers(); + + // 0. Add the new strategy + bytes4 selector = StrategyTestMultiFillCollectionOrder.executeStrategyWithTakerAsk.selector; + + StrategyTestMultiFillCollectionOrder strategyMultiFillCollectionOrder = new StrategyTestMultiFillCollectionOrder( + address(looksRareProtocol) + ); + + vm.prank(_owner); + _addStrategy(address(strategyMultiFillCollectionOrder), selector, true); + + // 1. Maker signs a message and execute a partial fill on it + uint256 amountsToFill = 4; + + uint256[] memory itemIds = new uint256[](0); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountsToFill; + + // Prepare the first order + OrderStructs.Maker memory makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, // Multi-fill bid offer + collectionType: CollectionType.ERC721, + orderNonce: orderNonce, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemIds: itemIds, + amounts: amounts + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // First taker user actions + { + itemIds = new uint256[](1); + amounts = new uint256[](1); + itemIds[0] = 0; + amounts[0] = 1; + + mockERC721.mint(takerUser, itemIds[0]); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(itemIds, amounts)); + + vm.prank(takerUser); + + // Execute taker ask transaction + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + // 2. Second maker order is signed sharing the same order nonce as the first one + { + uint256 itemId = 420; + + itemIds = new uint256[](1); + amounts = new uint256[](1); + itemIds[0] = itemId; + amounts[0] = 1; + + // Prepare the second order + makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: orderNonce, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemIds: itemIds, + amounts: amounts + }); + + // Sign order + signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, USER_ORDER_NONCE_IN_EXECUTION_WITH_OTHER_HASH); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker( + takerUser, + abi.encode(new uint256[](0), new uint256[](0)) + ); + + vm.prank(takerUser); + + // Second one fails when a taker user tries to execute + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + } + + function testCancelOrderNonces(uint256 nonceOne, uint256 nonceTwo) public asPrankedUser(makerUser) { + assertEq(looksRareProtocol.userOrderNonce(makerUser, nonceOne), bytes32(0)); + assertEq(looksRareProtocol.userOrderNonce(makerUser, nonceTwo), bytes32(0)); + + uint256[] memory orderNonces = new uint256[](2); + orderNonces[0] = nonceOne; + orderNonces[1] = nonceTwo; + vm.expectEmit(true, false, false, true); + emit OrderNoncesCancelled(makerUser, orderNonces); + looksRareProtocol.cancelOrderNonces(orderNonces); + + assertEq(looksRareProtocol.userOrderNonce(makerUser, nonceOne), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + assertEq(looksRareProtocol.userOrderNonce(makerUser, nonceTwo), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + /** + * Cannot execute an order if its nonce has been cancelled + */ + function testCannotExecuteAnOrderWhoseNonceIsCancelled(uint256 orderNonce) public { + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + uint256 itemId = 0; + + uint256[] memory orderNonces = new uint256[](1); + orderNonces[0] = orderNonce; + vm.prank(makerUser); + looksRareProtocol.cancelOrderNonces(orderNonces); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: orderNonce, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertMakerOrderReturnValidationCode(makerBid, signature, USER_ORDER_NONCE_EXECUTED_OR_CANCELLED); + + // Mint asset + mockERC721.mint(takerUser, itemId); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker( + takerUser, + abi.encode(new uint256[](0), new uint256[](0)) + ); + + vm.prank(takerUser); + vm.expectRevert(NoncesInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCancelNoncesRevertIfEmptyArrays() public { + uint256[] memory nonces = new uint256[](0); + + vm.expectRevert(LengthsInvalid.selector); + looksRareProtocol.cancelSubsetNonces(nonces); + + vm.expectRevert(LengthsInvalid.selector); + looksRareProtocol.cancelOrderNonces(nonces); + } +} diff --git a/contracts/test/foundry/marketplace/OrderValidatorV2A.t.sol b/contracts/test/foundry/marketplace/OrderValidatorV2A.t.sol new file mode 100644 index 00000000..604c9006 --- /dev/null +++ b/contracts/test/foundry/marketplace/OrderValidatorV2A.t.sol @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { LooksRareProtocol } from "@hypercerts/marketplace/LooksRareProtocol.sol"; +import { TransferManager } from "@hypercerts/marketplace/TransferManager.sol"; +import { CreatorFeeManagerWithRoyalties } from "@hypercerts/marketplace/CreatorFeeManagerWithRoyalties.sol"; + +import { OrderValidatorV2A } from "@hypercerts/marketplace/helpers/OrderValidatorV2A.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Shared errors +import { ERC20_APPROVAL_INFERIOR_TO_PRICE, ERC721_ITEM_ID_NOT_IN_BALANCE, ERC721_NO_APPROVAL_FOR_ALL_OR_ITEM_ID, ERC1155_BALANCE_OF_DOES_NOT_EXIST, ERC1155_BALANCE_OF_ITEM_ID_INFERIOR_TO_AMOUNT, ERC1155_IS_APPROVED_FOR_ALL_DOES_NOT_EXIST, ERC1155_NO_APPROVAL_FOR_ALL, MAKER_ORDER_INVALID_STANDARD_SALE, MISSING_IS_VALID_SIGNATURE_FUNCTION_EIP1271, POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC721, POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC1155, STRATEGY_NOT_IMPLEMENTED, TRANSFER_MANAGER_APPROVAL_REVOKED_BY_OWNER_FOR_EXCHANGE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Utils +import { TestParameters } from "./utils/TestParameters.sol"; + +// Mocks +import { MockRoyaltyFeeRegistry } from "../../mock/MockRoyaltyFeeRegistry.sol"; +import { MockERC721 } from "../../mock/MockERC721.sol"; +import { MockERC1155 } from "../../mock/MockERC1155.sol"; +import { MockERC1155WithoutBalanceOfBatch } from "../../mock/MockERC1155WithoutBalanceOfBatch.sol"; +import { MockERC1155WithoutAnyBalanceOf } from "../../mock/MockERC1155WithoutAnyBalanceOf.sol"; +import { MockERC1155WithoutIsApprovedForAll } from "../../mock/MockERC1155WithoutIsApprovedForAll.sol"; +import { MockERC721SupportsNoInterface } from "../../mock/MockERC721SupportsNoInterface.sol"; +import { MockERC1155SupportsNoInterface } from "../../mock/MockERC1155SupportsNoInterface.sol"; +import { MockERC20 } from "../../mock/MockERC20.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +/** + * @dev Not everything is tested in this file. Most tests live in other files + * with the assert functions living in ProtocolBase.t.sol. + */ +contract OrderValidatorV2ATest is TestParameters { + CreatorFeeManagerWithRoyalties private creatorFeeManager; + LooksRareProtocol private looksRareProtocol; + MockRoyaltyFeeRegistry private royaltyFeeRegistry; + OrderValidatorV2A private orderValidator; + TransferManager private transferManager; + + function setUp() public { + transferManager = new TransferManager(address(this)); + looksRareProtocol = new LooksRareProtocol( + address(this), + address(this), + address(transferManager), + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ); + royaltyFeeRegistry = new MockRoyaltyFeeRegistry(address(this), 9_500); + creatorFeeManager = new CreatorFeeManagerWithRoyalties(address(royaltyFeeRegistry)); + looksRareProtocol.updateCreatorFeeManager(address(creatorFeeManager)); + looksRareProtocol.updateCurrencyStatus(ETH, true); + orderValidator = new OrderValidatorV2A(address(looksRareProtocol)); + } + + function testDeriveProtocolParameters() public { + orderValidator.deriveProtocolParameters(); + assertEq( + orderValidator.domainSeparator(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("LooksRareProtocol"), + keccak256(bytes("2")), + block.chainid, + address(looksRareProtocol) + ) + ) + ); + + assertEq(address(orderValidator.creatorFeeManager()), address(creatorFeeManager)); + assertEq(orderValidator.maxCreatorFeeBp(), 1_000); + } + + function testMakerAskStrategyNotImplemented() public { + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.strategyId = 1; + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[0], STRATEGY_NOT_IMPLEMENTED); + } + + function testMakerBidStrategyNotImplemented() public { + OrderStructs.Maker memory makerBid; + makerBid.quoteType = QuoteType.Bid; + address currency = address(1); // it cannot be 0 + looksRareProtocol.updateCurrencyStatus(currency, true); + makerBid.currency = currency; + makerBid.strategyId = 1; + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[0], STRATEGY_NOT_IMPLEMENTED); + } + + function testMakerAskLooksRareProtocolIsNotAWhitelistedOperator() public { + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC721; + makerAsk.collection = address(new MockERC721()); + + address[] memory operators = new address[](1); + operators[0] = address(orderValidator.looksRareProtocol()); + + transferManager.allowOperator(operators[0]); + + vm.prank(makerUser); + transferManager.grantApprovals(operators); + + transferManager.removeOperator(operators[0]); + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[7], TRANSFER_MANAGER_APPROVAL_REVOKED_BY_OWNER_FOR_EXCHANGE); + } + + function testMakerAskWrongCollectionTypeERC721() public { + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC721; + makerAsk.collection = address(new MockERC721SupportsNoInterface()); + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[6], POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC721); + } + + function testMakerBidWrongCollectionTypeERC721() public { + OrderStructs.Maker memory makerBid; + makerBid.quoteType = QuoteType.Bid; + makerBid.collectionType = CollectionType.ERC721; + makerBid.collection = address(new MockERC721SupportsNoInterface()); + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[6], POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC721); + } + + function testMakerBidZeroAmount() public { + _testMakerBidERC721InvalidAmount(0); + } + + function testMakerBidERC721AmountNotEqualToOne() public { + _testMakerBidERC721InvalidAmount(2); + } + + function _testMakerBidERC721InvalidAmount(uint256 amount) public { + OrderStructs.Maker memory makerBid; + makerBid.quoteType = QuoteType.Bid; + makerBid.collectionType = CollectionType.ERC721; + makerBid.collection = address(new MockERC721()); + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = amount; + makerBid.itemIds = itemIds; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; + makerBid.amounts = amounts; + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[1], MAKER_ORDER_INVALID_STANDARD_SALE); + } + + function testMakerBidMissingIsValidSignature() public { + OrderStructs.Maker memory makerBid; + // This contract does not have isValidSignature implemented + makerBid.quoteType = QuoteType.Bid; + makerBid.signer = address(this); + makerBid.collectionType = CollectionType.ERC721; + makerBid.collection = address(new MockERC721()); + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[3], MISSING_IS_VALID_SIGNATURE_FUNCTION_EIP1271); + } + + function testMakerAskWrongCollectionTypeERC1155() public { + OrderStructs.Maker memory makerAsk; + makerAsk.collectionType = CollectionType.ERC1155; + makerAsk.collection = address(new MockERC1155SupportsNoInterface()); + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[6], POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC1155); + } + + function testMakerBidWrongCollectionTypeERC1155() public { + OrderStructs.Maker memory makerBid; + makerBid.quoteType = QuoteType.Bid; + makerBid.collectionType = CollectionType.ERC1155; + makerBid.collection = address(new MockERC1155SupportsNoInterface()); + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[6], POTENTIAL_INVALID_COLLECTION_TYPE_SHOULD_BE_ERC1155); + } + + function testMakerBidInsufficientERC20Allowance() public { + OrderStructs.Maker memory makerBid; + makerBid.quoteType = QuoteType.Bid; + MockERC20 mockERC20 = new MockERC20(); + makerBid.collectionType = CollectionType.ERC721; + makerBid.collection = address(new MockERC721()); + makerBid.signer = makerUser; + makerBid.currency = address(mockERC20); + makerBid.collectionType = CollectionType.ERC721; + makerBid.price = 1 ether; + + mockERC20.mint(makerUser, 1 ether); + + vm.startPrank(makerUser); + mockERC20.approve(address(orderValidator.looksRareProtocol()), makerBid.price - 1 wei); + vm.stopPrank(); + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerBid, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC20_APPROVAL_INFERIOR_TO_PRICE); + } + + function testMakerAskERC721NotAllTokensAreApproved() public { + MockERC721 mockERC721 = new MockERC721(); + mockERC721.mint(makerUser, 0); + mockERC721.mint(makerUser, 1); + + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC721; + makerAsk.collection = address(mockERC721); + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC721; + + // Only approve token 0 but not token 1, this is to test the loop + vm.prank(makerUser); + mockERC721.approve(address(transferManager), 0); + + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = 0; + itemIds[1] = 1; + makerAsk.itemIds = itemIds; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1; + amounts[1] = 1; + makerAsk.amounts = amounts; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC721_NO_APPROVAL_FOR_ALL_OR_ITEM_ID); + } + + function testMakerAskDoesNotOwnERC721() public { + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC721; + MockERC721 mockERC721 = new MockERC721(); + mockERC721.mint(address(this), 0); + makerAsk.collection = address(mockERC721); + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC721; + uint256[] memory itemIds = new uint256[](1); + makerAsk.itemIds = itemIds; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + + assertEq(validationCodes[5], ERC721_ITEM_ID_NOT_IN_BALANCE); + } + + function testMakerAskERC1155BalanceInferiorToAmountThroughBalanceOfBatch() public { + _testMakerAskERC1155BalanceInferiorToAmount(true); + } + + function testMakerAskERC1155BalanceInferiorToAmountThroughBalanceOf() public { + _testMakerAskERC1155BalanceInferiorToAmount(false); + } + + function _testMakerAskERC1155BalanceInferiorToAmount(bool revertBalanceOfBatch) public { + address collection; + if (revertBalanceOfBatch) { + MockERC1155WithoutBalanceOfBatch mockERC1155 = new MockERC1155WithoutBalanceOfBatch(); + collection = address(mockERC1155); + } else { + MockERC1155 mockERC1155 = new MockERC1155(); + collection = address(mockERC1155); + } + + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC1155; + makerAsk.collection = collection; + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC1155; + uint256[] memory itemIds = new uint256[](1); + makerAsk.itemIds = itemIds; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + makerAsk.amounts = amounts; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC1155_BALANCE_OF_ITEM_ID_INFERIOR_TO_AMOUNT); + } + + function testMakerAskERC1155BalanceOfDoesNotExist() public { + MockERC1155WithoutAnyBalanceOf mockERC1155 = new MockERC1155WithoutAnyBalanceOf(); + + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC1155; + makerAsk.collection = address(mockERC1155); + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC1155; + uint256[] memory itemIds = new uint256[](1); + makerAsk.itemIds = itemIds; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + makerAsk.amounts = amounts; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC1155_BALANCE_OF_DOES_NOT_EXIST); + } + + function testMakerAskERC1155IsApprovedForAllDoesNotExist() public { + MockERC1155WithoutIsApprovedForAll mockERC1155 = new MockERC1155WithoutIsApprovedForAll(); + mockERC1155.mint({ to: makerUser, tokenId: 0, amount: 1 }); + + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC1155; + makerAsk.collection = address(mockERC1155); + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC1155; + uint256[] memory itemIds = new uint256[](1); + makerAsk.itemIds = itemIds; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + makerAsk.amounts = amounts; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC1155_IS_APPROVED_FOR_ALL_DOES_NOT_EXIST); + } + + function testMakerAskERC1155IsApprovedForAllReturnsFalse() public { + MockERC1155 mockERC1155 = new MockERC1155(); + mockERC1155.mint({ to: makerUser, tokenId: 0, amount: 1 }); + + OrderStructs.Maker memory makerAsk; + makerAsk.quoteType = QuoteType.Ask; + makerAsk.collectionType = CollectionType.ERC1155; + makerAsk.collection = address(mockERC1155); + makerAsk.signer = makerUser; + makerAsk.collectionType = CollectionType.ERC1155; + uint256[] memory itemIds = new uint256[](1); + makerAsk.itemIds = itemIds; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + makerAsk.amounts = amounts; + + uint256[9] memory validationCodes = orderValidator.checkMakerOrderValidity( + makerAsk, + new bytes(65), + _EMPTY_MERKLE_TREE + ); + assertEq(validationCodes[5], ERC1155_NO_APPROVAL_FOR_ALL); + } +} diff --git a/contracts/test/foundry/marketplace/ProtocolBase.t.sol b/contracts/test/foundry/marketplace/ProtocolBase.t.sol new file mode 100644 index 00000000..92b090fd --- /dev/null +++ b/contracts/test/foundry/marketplace/ProtocolBase.t.sol @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +// WETH +import { WETH } from "solmate/src/tokens/WETH.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Core contracts +import { LooksRareProtocol, ILooksRareProtocol } from "@hypercerts/marketplace/LooksRareProtocol.sol"; +import { TransferManager } from "@hypercerts/marketplace/TransferManager.sol"; +import { ProtocolFeeRecipient } from "@hypercerts/marketplace/ProtocolFeeRecipient.sol"; + +// Other contracts +import { OrderValidatorV2A } from "@hypercerts/marketplace/helpers/OrderValidatorV2A.sol"; + +// Mock files +import { MockERC20 } from "../../mock/MockERC20.sol"; +import { MockERC721 } from "../../mock/MockERC721.sol"; +import { MockERC721WithRoyalties } from "../../mock/MockERC721WithRoyalties.sol"; +import { MockERC1155 } from "../../mock/MockERC1155.sol"; +import { MockRoyaltyFeeRegistry } from "../../mock/MockRoyaltyFeeRegistry.sol"; + +// Utils +import { MockOrderGenerator } from "./utils/MockOrderGenerator.sol"; + +contract ProtocolBase is MockOrderGenerator, ILooksRareProtocol { + address[] public operators; + + MockERC20 public looksRareToken; + MockERC721WithRoyalties public mockERC721WithRoyalties; + MockERC721 public mockERC721; + MockERC1155 public mockERC1155; + + ProtocolFeeRecipient public protocolFeeRecipient; + LooksRareProtocol public looksRareProtocol; + TransferManager public transferManager; + MockRoyaltyFeeRegistry public royaltyFeeRegistry; + OrderValidatorV2A public orderValidator; + + WETH public weth; + + function _assertMakerOrderReturnValidationCode( + OrderStructs.Maker memory makerOrder, + bytes memory signature, + uint256 expectedValidationCode + ) internal { + _assertMakerOrderReturnValidationCode(makerOrder, signature, _EMPTY_MERKLE_TREE, expectedValidationCode); + } + + function _assertMakerOrderReturnValidationCodeWithMerkleTree( + OrderStructs.Maker memory makerOrder, + bytes memory signature, + OrderStructs.MerkleTree memory merkleTree, + uint256 expectedValidationCode + ) internal { + _assertMakerOrderReturnValidationCode(makerOrder, signature, merkleTree, expectedValidationCode); + } + + function _assertMakerOrderReturnValidationCode( + OrderStructs.Maker memory makerOrder, + bytes memory signature, + OrderStructs.MerkleTree memory merkleTree, + uint256 expectedValidationCode + ) private { + OrderStructs.Maker[] memory makerOrders = new OrderStructs.Maker[](1); + makerOrders[0] = makerOrder; + + bytes[] memory signatures = new bytes[](1); + signatures[0] = signature; + + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](1); + merkleTrees[0] = merkleTree; + + uint256[9][] memory validationCodes = orderValidator.checkMultipleMakerOrderValidities( + makerOrders, + signatures, + merkleTrees + ); + + uint256 index = expectedValidationCode / 100; + assertEq(validationCodes[0][index - 1], expectedValidationCode); + } + + function _assertValidMakerOrder(OrderStructs.Maker memory makerOrder, bytes memory signature) internal { + _assertValidMakerOrder(makerOrder, signature, _EMPTY_MERKLE_TREE); + } + + function _assertValidMakerOrderWithMerkleTree( + OrderStructs.Maker memory makerOrder, + bytes memory signature, + OrderStructs.MerkleTree memory merkleTree + ) internal { + _assertValidMakerOrder(makerOrder, signature, merkleTree); + } + + function _assertValidMakerOrder( + OrderStructs.Maker memory makerOrder, + bytes memory signature, + OrderStructs.MerkleTree memory merkleTree + ) private { + OrderStructs.Maker[] memory makerOrders = new OrderStructs.Maker[](1); + makerOrders[0] = makerOrder; + + bytes[] memory signatures = new bytes[](1); + signatures[0] = signature; + + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](1); + merkleTrees[0] = merkleTree; + + uint256[9][] memory validationCodes = orderValidator.checkMultipleMakerOrderValidities( + makerOrders, + signatures, + merkleTrees + ); + + _assertValidationCodesAllZeroes(validationCodes); + } + + function _assertValidationCodesAllZeroes(uint256[9][] memory validationCodes) private { + for (uint256 i; i < validationCodes.length; i++) { + for (uint256 j; j < 9; j++) { + assertEq(validationCodes[i][j], 0); + } + } + } + + function _setUpUser(address user) internal asPrankedUser(user) { + // Do approvals for collections and WETH + mockERC721.setApprovalForAll(address(transferManager), true); + mockERC1155.setApprovalForAll(address(transferManager), true); + mockERC721WithRoyalties.setApprovalForAll(address(transferManager), true); + weth.approve(address(looksRareProtocol), type(uint256).max); + + // Grant approvals for transfer manager + transferManager.grantApprovals(operators); + + // Receive ETH and WETH + vm.deal(user, _initialETHBalanceUser + _initialWETHBalanceUser); + weth.deposit{ value: _initialWETHBalanceUser }(); + } + + function _setUpUsers() internal { + _setUpUser(makerUser); + _setUpUser(takerUser); + } + + function _setupRegistryRoyalties(address collection, uint256 standardRoyaltyFee) internal { + vm.prank(royaltyFeeRegistry.owner()); + royaltyFeeRegistry.updateRoyaltyInfoForCollection( + collection, + _royaltyRecipient, + _royaltyRecipient, + standardRoyaltyFee + ); + } + + function _setUp() internal { + vm.startPrank(_owner); + weth = new WETH(); + looksRareToken = new MockERC20(); + mockERC721 = new MockERC721(); + mockERC1155 = new MockERC1155(); + + transferManager = new TransferManager(_owner); + royaltyFeeRegistry = new MockRoyaltyFeeRegistry(_owner, 9500); + protocolFeeRecipient = new ProtocolFeeRecipient(0x5924A28caAF1cc016617874a2f0C3710d881f3c1, address(weth)); + looksRareProtocol = new LooksRareProtocol( + _owner, + address(protocolFeeRecipient), + address(transferManager), + address(weth) + ); + mockERC721WithRoyalties = new MockERC721WithRoyalties(_royaltyRecipient, _standardRoyaltyFee); + + // Operations + transferManager.allowOperator(address(looksRareProtocol)); + looksRareProtocol.updateCurrencyStatus(ETH, true); + looksRareProtocol.updateCurrencyStatus(address(weth), true); + + // Fetch domain separator and store it as one of the operators + _domainSeparator = looksRareProtocol.domainSeparator(); + operators.push(address(looksRareProtocol)); + + // Deploy order validator contract + orderValidator = new OrderValidatorV2A(address(looksRareProtocol)); + + // Distribute ETH and WETH to protocol owner + vm.deal(_owner, _initialETHBalanceOwner + _initialWETHBalanceOwner); + weth.deposit{ value: _initialWETHBalanceOwner }(); + vm.stopPrank(); + + // Distribute ETH and WETH to royalty recipient + vm.deal(_royaltyRecipient, _initialETHBalanceRoyaltyRecipient + _initialWETHBalanceRoyaltyRecipient); + vm.startPrank(_royaltyRecipient); + weth.deposit{ value: _initialWETHBalanceRoyaltyRecipient }(); + vm.stopPrank(); + } + + function _genericTakerOrder() internal pure returns (OrderStructs.Taker memory takerOrder) { + takerOrder = OrderStructs.Taker(takerUser, abi.encode()); + } + + function _addStrategy(address strategy, bytes4 selector, bool isMakerBid) internal { + looksRareProtocol.addStrategy( + _standardProtocolFeeBp, + _minTotalFeeBp, + _maxProtocolFeeBp, + selector, + isMakerBid, + strategy + ); + } + + function _assertStrategyAttributes( + address expectedStrategyAddress, + bytes4 expectedSelector, + bool expectedIsMakerBid + ) internal { + ( + bool strategyIsActive, + uint16 strategyStandardProtocolFee, + uint16 strategyMinTotalFee, + uint16 strategyMaxProtocolFee, + bytes4 strategySelector, + bool strategyIsMakerBid, + address strategyImplementation + ) = looksRareProtocol.strategyInfo(1); + + assertTrue(strategyIsActive); + assertEq(strategyStandardProtocolFee, _standardProtocolFeeBp); + assertEq(strategyMinTotalFee, _minTotalFeeBp); + assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); + assertEq(strategySelector, expectedSelector); + assertEq(strategyIsMakerBid, expectedIsMakerBid); + assertEq(strategyImplementation, expectedStrategyAddress); + } + + function _assertMockERC721Ownership(uint256[] memory itemIds, address owner) internal { + for (uint256 i; i < itemIds.length; i++) { + assertEq(mockERC721.ownerOf(itemIds[i]), owner); + } + } + + /** + * NOTE: It inherits from ILooksRareProtocol, so it + * needs to at least define the functions below. + */ + function executeTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external {} + + function executeTakerBid( + OrderStructs.Taker calldata takerBid, + OrderStructs.Maker calldata makerAsk, + bytes calldata makerSignature, + OrderStructs.MerkleTree calldata merkleTree, + address affiliate + ) external payable {} + + function executeMultipleTakerBids( + OrderStructs.Taker[] calldata takerBids, + OrderStructs.Maker[] calldata makerAsks, + bytes[] calldata makerSignatures, + OrderStructs.MerkleTree[] calldata merkleTrees, + address affiliate, + bool isAtomic + ) external payable {} +} diff --git a/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol b/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol new file mode 100644 index 00000000..5074a83d --- /dev/null +++ b/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC20 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC20.sol"; +import { IWETH } from "@looksrare/contracts-libs/contracts/interfaces/generic/IWETH.sol"; + +// Core contracts +import { ProtocolFeeRecipient } from "@hypercerts/marketplace/ProtocolFeeRecipient.sol"; + +// Other mocks and utils +import { MockERC20 } from "../../mock/MockERC20.sol"; +import { TestParameters } from "./utils/TestParameters.sol"; + +contract ProtocolFeeRecipientTest is TestParameters { + ProtocolFeeRecipient private protocolFeeRecipient; + uint256 private feeSharingSetterInitialWETHBalance; + + address private constant FEE_SHARING_SETTER = 0x5924A28caAF1cc016617874a2f0C3710d881f3c1; + uint256 private constant DUST = 0.69420 ether; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("mainnet")); + protocolFeeRecipient = new ProtocolFeeRecipient(FEE_SHARING_SETTER, WETH_MAINNET); + feeSharingSetterInitialWETHBalance = IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER); + + vm.deal(address(protocolFeeRecipient), 0); + deal(WETH_MAINNET, address(protocolFeeRecipient), 0); + } + + function test_TransferETH_NoWETHBalance_WithETHBalance() public { + _sendETHToProtocolFeeRecipient(); + + protocolFeeRecipient.transferETH(); + + assertEq(address(protocolFeeRecipient).balance, 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + 1 ether); + } + + function test_TransferETH_WithWETHBalance_WithETHBalance() public { + _sendETHToProtocolFeeRecipient(); + _sendWETHToProtocolFeeRecipient(); + + protocolFeeRecipient.transferETH(); + + assertEq(address(protocolFeeRecipient).balance, 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); + assertEq( + IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), + feeSharingSetterInitialWETHBalance + 1 ether + DUST + ); + } + + function test_TransferETH_WithWETHBalance_NoETHBalance() public { + _sendWETHToProtocolFeeRecipient(); + + protocolFeeRecipient.transferETH(); + + assertEq(address(protocolFeeRecipient).balance, 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + DUST); + } + + function test_TransferETH_RevertIf_NothingToTransfer() public { + vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); + protocolFeeRecipient.transferETH(); + } + + function test_TransferWETH() public { + _sendWETHToProtocolFeeRecipient(); + + protocolFeeRecipient.transferERC20(WETH_MAINNET); + + assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); + assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + DUST); + } + + function test_TransferWETH_RevertIf_NothingToTransfer() public { + vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); + protocolFeeRecipient.transferERC20(WETH_MAINNET); + } + + function test_TransferERC20() public { + MockERC20 mockERC20 = new MockERC20(); + mockERC20.mint(address(protocolFeeRecipient), DUST); + + protocolFeeRecipient.transferERC20(address(mockERC20)); + + assertEq(mockERC20.balanceOf(address(protocolFeeRecipient)), 0); + assertEq(mockERC20.balanceOf(FEE_SHARING_SETTER), DUST); + } + + function test_TransferERC20_RevertIf_NothingToTransfer() public { + MockERC20 mockERC20 = new MockERC20(); + vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); + protocolFeeRecipient.transferERC20(address(mockERC20)); + } + + function _sendETHToProtocolFeeRecipient() private { + (bool success, ) = address(protocolFeeRecipient).call{ value: 1 ether }(""); + assertTrue(success); + assertEq(address(protocolFeeRecipient).balance, 1 ether); + } + + function _sendWETHToProtocolFeeRecipient() private { + IWETH(WETH_MAINNET).deposit{ value: DUST }(); + IERC20(WETH_MAINNET).transfer(address(protocolFeeRecipient), DUST); + assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), DUST); + } +} diff --git a/contracts/test/foundry/marketplace/Sandbox.t.sol b/contracts/test/foundry/marketplace/Sandbox.t.sol new file mode 100644 index 00000000..6e2a3e36 --- /dev/null +++ b/contracts/test/foundry/marketplace/Sandbox.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IERC721 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC721.sol"; +import { IERC1155 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC1155.sol"; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract SandboxTest is ProtocolBase { + error ERC721TransferFromFail(); + + // Fixed price of sale + uint256 private constant price = 1 ether; + + // Sandbox on Ethereum mainnet + address private constant SANDBOX = 0xa342f5D851E866E18ff98F351f2c6637f4478dB5; + + // Forked block number to run the tests + uint256 private constant FORKED_BLOCK_NUMBER = 16268000; + + function _transferItemIdToUser(address user) private returns (uint256 itemId) { + // @dev This user had 23 of the itemId at the forked block number + address ownerOfItemId = 0x7A9fe22691c811ea339D9B73150e6911a5343DcA; + itemId = 55464657044963196816950587289035428064568320970692304673817341489688428423171; + vm.prank(ownerOfItemId); + IERC1155(SANDBOX).safeTransferFrom(ownerOfItemId, user, itemId, 23, ""); + } + + function _setUpApprovalsForSandbox(address user) internal { + vm.prank(user); + IERC1155(SANDBOX).setApprovalForAll(address(transferManager), true); + } + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("mainnet"), FORKED_BLOCK_NUMBER); + _setUp(); + _setUpUsers(); + } + + /** + * @notice Sandbox implements both ERC721 and ERC1155 interfaceIds. + * This test verifies that only collectionType = 1 works. + * It is for taker ask (matching maker bid). + */ + function testTakerAskCannotTransferSandboxWithERC721CollectionTypeButERC1155CollectionTypeWorks() public { + // Taker user is the one selling the item + _setUpApprovalsForSandbox(takerUser); + uint256 itemId = _transferItemIdToUser(takerUser); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, // it should be ERC1155 + orderNonce: 0, + collection: SANDBOX, + currency: address(weth), + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = _genericTakerOrder(); + + // It should fail with collectionType = 0 + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFromFail.selector)); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Adjust the collection type and sign order again + makerBid.collectionType = CollectionType.ERC1155; + signature = _signMakerOrder(makerBid, makerUserPK); + + // It shouldn't fail with collectionType = 0 + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Maker user has received the Sandbox asset + assertEq(IERC1155(SANDBOX).balanceOf(makerUser, itemId), makerBid.amounts[0]); + } + + /** + * @notice Sandbox implements both ERC721 and ERC1155 interfaceIds. + * This test verifies that only collectionType = 1 works. + * It is for taker bid (matching maker ask). + */ + function testTakerBidCannotTransferSandboxWithERC721CollectionTypeButERC1155CollectionTypeWorks() public { + // Maker user is the one selling the item + _setUpApprovalsForSandbox(makerUser); + uint256 itemId = _transferItemIdToUser(makerUser); + + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, // it should be ERC1155 + orderNonce: 0, + collection: SANDBOX, + currency: ETH, + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Prepare the taker bid + OrderStructs.Taker memory takerBid = _genericTakerOrder(); + + // It should fail with collectionType = 0 + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFromFail.selector)); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Adjust the collection type and sign order again + makerAsk.collectionType = CollectionType.ERC1155; + signature = _signMakerOrder(makerAsk, makerUserPK); + + // It shouldn't fail with collectionType = 0 + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Taker user has received the Sandbox asset + assertEq(IERC1155(SANDBOX).balanceOf(takerUser, itemId), makerAsk.amounts[0]); + } +} diff --git a/contracts/test/foundry/marketplace/SignaturesEIP2098.t.sol b/contracts/test/foundry/marketplace/SignaturesEIP2098.t.sol new file mode 100644 index 00000000..9a480b0f --- /dev/null +++ b/contracts/test/foundry/marketplace/SignaturesEIP2098.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries, interfaces, errors +import "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract SignaturesEIP2098Test is ProtocolBase { + function setUp() public { + _setUp(); + } + + function testCanSignValidMakerAskEIP2098(uint256 price, uint256 itemId) public { + vm.assume(price <= 2 ether); + + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + (OrderStructs.Maker memory makerAsk, ) = _createMockMakerAskAndTakerBid(address(mockERC721)); + makerAsk.price = price; + makerAsk.itemIds[0] = itemId; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // Adjust the signature + signature = _eip2098Signature(signature); + + // Verify validity of maker ask order + _assertValidMakerOrder(makerAsk, signature); + } + + function testCanSignValidMakerBidEIP2098(uint256 price, uint256 itemId) public { + vm.assume(price <= 2 ether); + + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + + (OrderStructs.Maker memory makerBid, ) = _createMockMakerBidAndTakerAsk(address(mockERC721), address(weth)); + makerBid.price = price; + makerBid.itemIds[0] = itemId; + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Adjust the signature + signature = _eip2098Signature(signature); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Verify validity of maker bid order + _assertValidMakerOrder(makerBid, signature); + } +} diff --git a/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC1155.t.sol b/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC1155.t.sol new file mode 100644 index 00000000..60ef310b --- /dev/null +++ b/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC1155.t.sol @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { IReentrancyGuard } from "@looksrare/contracts-libs/contracts/interfaces/IReentrancyGuard.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Mocks and other utils +import { ERC1271Wallet } from "./utils/ERC1271Wallet.sol"; +import { MaliciousERC1271Wallet } from "./utils/MaliciousERC1271Wallet.sol"; +import { MaliciousOnERC1155ReceivedERC1271Wallet } from "./utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol"; +import { MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet } from "./utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol"; +import { MaliciousIsValidSignatureERC1271Wallet } from "./utils/MaliciousIsValidSignatureERC1271Wallet.sol"; + +// Errors +import { SignatureERC1271Invalid } from "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; +import { ERC1155SafeTransferFromFail, ERC1155SafeBatchTransferFromFail } from "@looksrare/contracts-libs/contracts/errors/LowLevelErrors.sol"; +import { SIGNATURE_INVALID_EIP1271 } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +/** + * @dev ERC1271Wallet recovers a signature's signer using ECDSA. If it matches the mock wallet's + * owner, it returns the magic value. Otherwise it returns an empty bytes4 value. + */ +contract SignaturesERC1271WalletForERC1155Test is ProtocolBase { + uint256 private constant price = 1 ether; // Fixed price of sale + uint256 private constant itemId = 0; + bytes private constant _EMPTY_SIGNATURE = new bytes(0); + + function setUp() public { + _setUp(); + _setUpUser(takerUser); + _setupRegistryRoyalties(address(mockERC1155), _standardRoyaltyFee); + } + + function testTakerBid() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup(address(wallet)); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + vm.startPrank(address(wallet)); + mockERC1155.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + _assertValidMakerOrder(makerAsk, signature); + + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + assertEq(mockERC1155.balanceOf(takerUser, itemId), 1); + } + + function testTakerBidInvalidSignature() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup(address(wallet)); + + // Signed by a different private key + bytes memory signature = _signMakerOrder(makerAsk, takerUserPK); + + vm.startPrank(address(wallet)); + mockERC1155.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, SIGNATURE_INVALID_EIP1271); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testTakerBidIsInvalidSignatureReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerBid); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup( + address(maliciousERC1271Wallet) + ); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + _EMPTY_SIGNATURE, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testTakerAsk() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup(address(wallet)); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(wallet), price); + vm.prank(address(wallet)); + weth.approve(address(looksRareProtocol), price); + + _assertValidMakerOrder(makerBid, signature); + + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + assertEq(mockERC1155.balanceOf(address(wallet), itemId), 1); + } + + function testTakerAskInvalidSignature() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup(address(wallet)); + + // Signed by a different private key + bytes memory signature = _signMakerOrder(makerBid, takerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(wallet), price); + vm.prank(address(wallet)); + weth.approve(address(looksRareProtocol), price); + + _assertMakerOrderReturnValidationCode(makerBid, signature, SIGNATURE_INVALID_EIP1271); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskIsValidSignatureReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerAsk); + + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup( + address(maliciousERC1271Wallet) + ); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, _EMPTY_SIGNATURE, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskOnERC1155ReceivedReentrancy() public { + MaliciousOnERC1155ReceivedERC1271Wallet maliciousERC1271Wallet = new MaliciousOnERC1155ReceivedERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup( + address(maliciousERC1271Wallet) + ); + + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerAsk); + + vm.expectRevert(ERC1155SafeTransferFromFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, _EMPTY_SIGNATURE, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testBatchTakerAsk() public { + ERC1271Wallet wallet = new ERC1271Wallet(makerUser); + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _batchTakerAskSetup(address(wallet)); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(wallet), price); + vm.prank(address(wallet)); + weth.approve(address(looksRareProtocol), price); + + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + for (uint256 i; i < 10; i++) { + assertEq(mockERC1155.balanceOf(address(wallet), i), 1); + } + } + + function testBatchTakerAskOnERC1155BatchReceivedReentrancy() public { + MaliciousOnERC1155ReceivedERC1271Wallet maliciousERC1271Wallet = new MaliciousOnERC1155ReceivedERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _batchTakerAskSetup( + address(maliciousERC1271Wallet) + ); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(maliciousERC1271Wallet), price); + vm.prank(address(maliciousERC1271Wallet)); + weth.approve(address(looksRareProtocol), price); + + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerAsk); + + vm.expectRevert(ERC1155SafeBatchTransferFromFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + uint256 private constant numberOfPurchases = 3; + + function testExecuteMultipleTakerBids() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(wallet)); + + vm.startPrank(address(wallet)); + mockERC1155.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberOfPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + for (uint256 i; i < numberOfPurchases; i++) { + assertEq(mockERC1155.balanceOf(takerUser, i), 1); + } + } + + function testExecuteMultipleTakerBidsInvalidSignatures() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(wallet)); + + // Signed by a different private key + for (uint256 i; i < signatures.length; i++) { + signatures[i] = _signMakerOrder(makerAsks[i], takerUserPK); + } + + vm.startPrank(address(wallet)); + mockERC1155.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberOfPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + } + + function testExecuteMultipleTakerBidsIsValidSignatureReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteMultipleTakerBids); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(maliciousERC1271Wallet)); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberOfPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + for (uint256 i; i < numberOfPurchases - 1; i++) { + assertEq(mockERC1155.balanceOf(takerUser, i), 0); + } + } + + function testExecuteMultipleTakerBidsOnERC1155ReceivedReentrancyOnlyInTheLastCall() public { + MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet maliciousERC1271Wallet = new MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet( + takerUser + ); + _setUpUser(makerUser); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteMultipleTakerBids); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(makerUser); + + // Set the NFT recipient as the ERC1271 wallet to trigger onERC1155Received + for (uint256 i; i < numberOfPurchases; i++) { + takerBids[i].recipient = address(maliciousERC1271Wallet); + } + + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberOfPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + // First 2 purchases should go through, but the last one fails silently + for (uint256 i; i < numberOfPurchases - 1; i++) { + assertEq(mockERC1155.balanceOf(address(maliciousERC1271Wallet), i), 1); + } + assertEq(mockERC1155.balanceOf(address(maliciousERC1271Wallet), numberOfPurchases - 1), 0); + } + + function _takerBidSetup( + address signer + ) private returns (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) { + // Mint asset + mockERC1155.mint(signer, itemId, 1); + + makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC1155, + orderNonce: 0, + collection: address(mockERC1155), + currency: ETH, + signer: signer, + price: price, + itemId: itemId + }); + + // Prepare the taker bid + takerBid = _genericTakerOrder(); + } + + function _takerAskSetup( + address signer + ) private returns (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) { + makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC1155, + orderNonce: 0, + collection: address(mockERC1155), + currency: address(weth), + signer: signer, + price: price, + itemId: itemId + }); + + // Mint asset + mockERC1155.mint(takerUser, itemId, 1); + + // Prepare the taker ask + takerAsk = _genericTakerOrder(); + } + + function _batchTakerAskSetup( + address signer + ) private returns (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) { + uint256[] memory itemIds = new uint256[](10); + uint256[] memory amounts = new uint256[](10); + + for (uint256 i; i < 10; i++) { + itemIds[i] = i; + amounts[i] = 1; + + // Mint asset + mockERC1155.mint(takerUser, i, 1); + } + + // Prepare the first order + makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC1155, + orderNonce: 0, + collection: address(mockERC1155), + currency: address(weth), + signer: signer, + price: price, + itemIds: itemIds, + amounts: amounts + }); + + // Prepare the taker ask + takerAsk = _genericTakerOrder(); + } + + function _multipleTakerBidsSetup( + address signer + ) + private + returns ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) + { + makerAsks = new OrderStructs.Maker[](numberOfPurchases); + takerBids = new OrderStructs.Taker[](numberOfPurchases); + signatures = new bytes[](numberOfPurchases); + + for (uint256 i; i < numberOfPurchases; i++) { + // Mint asset + mockERC1155.mint(signer, i, 1); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC1155, + orderNonce: i, + collection: address(mockERC1155), + currency: ETH, + signer: signer, + price: price, + itemId: i // 0, 1, etc. + }); + + signatures[i] = _signMakerOrder(makerAsks[i], makerUserPK); + + takerBids[i] = _genericTakerOrder(); + } + + // Other execution parameters + merkleTrees = new OrderStructs.MerkleTree[](numberOfPurchases); + } +} diff --git a/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC721.t.sol b/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC721.t.sol new file mode 100644 index 00000000..61df2186 --- /dev/null +++ b/contracts/test/foundry/marketplace/SignaturesERC1271WalletForERC721.t.sol @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { IReentrancyGuard } from "@looksrare/contracts-libs/contracts/interfaces/IReentrancyGuard.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Mocks and other utils +import { ERC1271Wallet } from "./utils/ERC1271Wallet.sol"; +import { MaliciousERC1271Wallet } from "./utils/MaliciousERC1271Wallet.sol"; +import { MaliciousIsValidSignatureERC1271Wallet } from "./utils/MaliciousIsValidSignatureERC1271Wallet.sol"; + +// Errors +import { SignatureERC1271Invalid } from "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; +import { SIGNATURE_INVALID_EIP1271 } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +/** + * @dev ERC1271Wallet recovers a signature's signer using ECDSA. If it matches the mock wallet's + * owner, it returns the magic value. Otherwise it returns an empty bytes4 value. + */ +contract SignaturesERC1271WalletForERC721Test is ProtocolBase { + uint256 private constant price = 1 ether; // Fixed price of sale + bytes private constant _EMPTY_SIGNATURE = new bytes(0); + + function setUp() public { + _setUp(); + _setUpUser(takerUser); + _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); + } + + function testTakerBid() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup(address(wallet)); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + vm.startPrank(address(wallet)); + mockERC721.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + _assertValidMakerOrder(makerAsk, signature); + + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + assertEq(mockERC721.ownerOf(makerAsk.itemIds[0]), takerUser); + } + + function testTakerBidInvalidSignature() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup(address(wallet)); + + // Signed by a different private key + bytes memory signature = _signMakerOrder(makerAsk, takerUserPK); + + vm.startPrank(address(wallet)); + mockERC721.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, SIGNATURE_INVALID_EIP1271); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testTakerBidReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerBid); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _takerBidSetup( + address(maliciousERC1271Wallet) + ); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + _EMPTY_SIGNATURE, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testTakerAsk() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup(address(wallet)); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(wallet), price); + vm.prank(address(wallet)); + weth.approve(address(looksRareProtocol), price); + + _assertValidMakerOrder(makerBid, signature); + + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + assertEq(mockERC721.ownerOf(makerBid.itemIds[0]), address(wallet)); + } + + function testTakerAskInvalidSignature() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup(address(wallet)); + + // Signed by a different private key + bytes memory signature = _signMakerOrder(makerBid, takerUserPK); + + // Wallet needs to hold WETH and have given WETH approval + deal(address(weth), address(wallet), price); + vm.prank(address(wallet)); + weth.approve(address(looksRareProtocol), price); + + _assertMakerOrderReturnValidationCode(makerBid, signature, SIGNATURE_INVALID_EIP1271); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteTakerAsk); + + (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) = _takerAskSetup( + address(maliciousERC1271Wallet) + ); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, _EMPTY_SIGNATURE, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + uint256 private constant numberPurchases = 3; + + function testExecuteMultipleTakerBids() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(wallet)); + + vm.startPrank(address(wallet)); + mockERC721.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + for (uint256 i; i < numberPurchases; i++) { + assertEq(mockERC721.ownerOf(i), takerUser); + } + } + + function testExecuteMultipleTakerBidsInvalidSignatures() public { + ERC1271Wallet wallet = new ERC1271Wallet(address(makerUser)); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(wallet)); + + // Signed by a different private key + for (uint256 i; i < signatures.length; i++) { + signatures[i] = _signMakerOrder(makerAsks[i], takerUserPK); + } + + vm.startPrank(address(wallet)); + mockERC721.setApprovalForAll(address(transferManager), true); + transferManager.grantApprovals(operators); + vm.stopPrank(); + + vm.expectRevert(SignatureERC1271Invalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + } + + function testExecuteMultipleTakerBidsReentrancy() public { + MaliciousIsValidSignatureERC1271Wallet maliciousERC1271Wallet = new MaliciousIsValidSignatureERC1271Wallet( + address(looksRareProtocol) + ); + _setUpUser(address(maliciousERC1271Wallet)); + maliciousERC1271Wallet.setFunctionToReenter(MaliciousERC1271Wallet.FunctionToReenter.ExecuteMultipleTakerBids); + + ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) = _multipleTakerBidsSetup(address(maliciousERC1271Wallet)); + + vm.expectRevert(IReentrancyGuard.ReentrancyFail.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + } + + function _takerBidSetup( + address signer + ) private returns (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) { + (makerAsk, takerBid) = _createMockMakerAskAndTakerBid(address(mockERC721)); + makerAsk.signer = signer; + // Mint asset + mockERC721.mint(signer, makerAsk.itemIds[0]); + } + + function _takerAskSetup( + address signer + ) private returns (OrderStructs.Taker memory takerAsk, OrderStructs.Maker memory makerBid) { + (makerBid, takerAsk) = _createMockMakerBidAndTakerAsk(address(mockERC721), address(weth)); + makerBid.signer = signer; + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + } + + function _multipleTakerBidsSetup( + address signer + ) + private + returns ( + OrderStructs.Maker[] memory makerAsks, + OrderStructs.Taker[] memory takerBids, + OrderStructs.MerkleTree[] memory merkleTrees, + bytes[] memory signatures + ) + { + makerAsks = new OrderStructs.Maker[](numberPurchases); + takerBids = new OrderStructs.Taker[](numberPurchases); + signatures = new bytes[](numberPurchases); + + for (uint256 i; i < numberPurchases; i++) { + // Mint asset + mockERC721.mint(signer, i); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, + collection: address(mockERC721), + currency: ETH, + signer: signer, + price: price, + itemId: i // 0, 1, etc. + }); + + signatures[i] = _signMakerOrder(makerAsks[i], makerUserPK); + + takerBids[i] = _genericTakerOrder(); + } + + // Other execution parameters + merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + } +} diff --git a/contracts/test/foundry/marketplace/SignaturesRevertions.t.sol b/contracts/test/foundry/marketplace/SignaturesRevertions.t.sol new file mode 100644 index 00000000..1861c480 --- /dev/null +++ b/contracts/test/foundry/marketplace/SignaturesRevertions.t.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries, interfaces, errors +import { SignatureParameterVInvalid, SignatureParameterSInvalid, SignatureEOAInvalid, NullSignerAddress, SignatureLengthInvalid } from "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; +import { INVALID_S_PARAMETER_EOA, INVALID_V_PARAMETER_EOA, NULL_SIGNER_EOA, INVALID_SIGNATURE_LENGTH, INVALID_SIGNER_EOA } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract SignaturesRevertionsTest is ProtocolBase { + uint256 internal constant _MAX_PRIVATE_KEY = + 115792089237316195423570985008687907852837564279074904382605163141518161494337; + + function setUp() public { + _setUp(); + } + + function testRevertIfSignatureEOAInvalid(uint256 itemId, uint256 price, uint256 randomPK) public { + // @dev Private keys 1 and 2 are used for maker/taker users + vm.assume(randomPK > 2 && randomPK < _MAX_PRIVATE_KEY); + + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, + itemId: itemId + }); + + address randomUser = vm.addr(randomPK); + _setUpUser(randomUser); + bytes memory signature = _signMakerOrder(makerAsk, randomPK); + _assertMakerOrderReturnValidationCode(makerAsk, signature, INVALID_SIGNER_EOA); + + vm.expectRevert(SignatureEOAInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid( + _genericTakerOrder(), + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testRevertIfInvalidVParameter(uint256 itemId, uint256 price, uint8 v) public { + vm.assume(v != 27 && v != 28); + + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign but replace v by the fuzzed v + bytes32 orderHash = _computeOrderHash(makerAsk); + (, bytes32 r, bytes32 s) = vm.sign( + makerUserPK, + keccak256(abi.encodePacked("\x19\x01", _domainSeparator, orderHash)) + ); + bytes memory signature = abi.encodePacked(r, s, v); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, INVALID_V_PARAMETER_EOA); + + vm.expectRevert(abi.encodeWithSelector(SignatureParameterVInvalid.selector, v)); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid( + _genericTakerOrder(), + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testRevertIfInvalidSParameter(uint256 itemId, uint256 price, bytes32 s) public { + vm.assume(uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0); + + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign but replace s by the fuzzed s + bytes32 orderHash = _computeOrderHash(makerAsk); + (uint8 v, bytes32 r, ) = vm.sign( + makerUserPK, + keccak256(abi.encodePacked("\x19\x01", _domainSeparator, orderHash)) + ); + bytes memory signature = abi.encodePacked(r, s, v); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, INVALID_S_PARAMETER_EOA); + + vm.expectRevert(abi.encodeWithSelector(SignatureParameterSInvalid.selector)); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid( + _genericTakerOrder(), + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testRevertIfRecoveredSignerIsNullAddress(uint256 itemId, uint256 price) public { + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, + itemId: itemId + }); + + // Sign but replace r by empty bytes32 + bytes32 orderHash = _computeOrderHash(makerAsk); + (uint8 v, , bytes32 s) = vm.sign( + makerUserPK, + keccak256(abi.encodePacked("\x19\x01", _domainSeparator, orderHash)) + ); + + bytes32 r; + bytes memory signature = abi.encodePacked(r, s, v); + + _assertMakerOrderReturnValidationCode(makerAsk, signature, NULL_SIGNER_EOA); + + vm.expectRevert(abi.encodeWithSelector(NullSignerAddress.selector)); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid( + _genericTakerOrder(), + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testRevertIfInvalidSignatureLength(uint256 itemId, uint256 price, uint256 length) public { + // @dev Getting OutOfGas starting from 16,776,985, probably due to memory cost + vm.assume(length != 64 && length != 65 && length < 16_776_985); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.itemIds[0] = itemId; + makerAsk.price = price; + + bytes memory signature = new bytes(length); + _assertMakerOrderReturnValidationCode(makerAsk, signature, INVALID_SIGNATURE_LENGTH); + + vm.expectRevert(abi.encodeWithSelector(SignatureLengthInvalid.selector, length)); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } +} diff --git a/contracts/test/foundry/marketplace/StandardTransactions.t.sol b/contracts/test/foundry/marketplace/StandardTransactions.t.sol new file mode 100644 index 00000000..3bc01b8e --- /dev/null +++ b/contracts/test/foundry/marketplace/StandardTransactions.t.sol @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries, interfaces, errors +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { LengthsInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; + +import { CreatorFeeManagerWithRoyalties } from "@hypercerts/marketplace/CreatorFeeManagerWithRoyalties.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract StandardTransactionsTest is ProtocolBase { + error ERC721TransferFromFail(); + + uint256 private constant itemId = 420; + uint16 private constant NEW_ROYALTY_FEE = uint16(50); + + function setUp() public { + _setUp(); + CreatorFeeManagerWithRoyalties creatorFeeManager = new CreatorFeeManagerWithRoyalties( + address(royaltyFeeRegistry) + ); + vm.prank(_owner); + looksRareProtocol.updateCreatorFeeManager(address(creatorFeeManager)); + } + + /** + * One ERC721 (where royalties come from the registry) is sold through a taker bid + */ + function testTakerBidERC721WithRoyaltiesFromRegistry(uint256 price) public { + vm.assume(price <= 2 ether); + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), NEW_ROYALTY_FEE); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.price = price; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // Verify validity of maker ask order + _assertValidMakerOrder(makerAsk, signature); + + // Arrays for events + uint256[3] memory expectedFees = _calculateExpectedFees({ price: price, royaltyFeeBp: NEW_ROYALTY_FEE }); + address[2] memory expectedRecipients; + + expectedRecipients[0] = makerUser; + expectedRecipients[1] = _royaltyRecipient; + + // Execute taker bid transaction + vm.prank(takerUser); + vm.expectEmit(true, false, false, true); + + emit TakerBid( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerAsk), + orderNonce: makerAsk.orderNonce, + isNonceInvalidated: true + }), + takerUser, + takerUser, + makerAsk.strategyId, + makerAsk.currency, + makerAsk.collection, + makerAsk.itemIds, + makerAsk.amounts, + expectedRecipients, + expectedFees + ); + + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + _assertSuccessfulExecutionThroughETH(takerUser, makerUser, price, expectedFees); + + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerAsk.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + /** + * One ERC721 is sold through taker bid. Address zero is specified as the recipient in the taker struct. + */ + function testTakerBidERC721WithAddressZeroSpecifiedAsRecipient(uint256 price) public { + vm.assume(price <= 2 ether); + _setUpUsers(); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( + address(mockERC721) + ); + makerAsk.price = price; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Mint asset + mockERC721.mint(makerUser, makerAsk.itemIds[0]); + + // Adjustment + takerBid.recipient = address(0); + + // Verify validity of maker ask order + _assertValidMakerOrder(makerAsk, signature); + + // Arrays for events + uint256[3] memory expectedFees = _calculateExpectedFees({ price: price, royaltyFeeBp: 0 }); + address[2] memory expectedRecipients; + + expectedRecipients[0] = makerUser; + expectedRecipients[1] = address(0); // No royalties + + // Execute taker bid transaction + vm.prank(takerUser); + vm.expectEmit(true, false, false, true); + + emit TakerBid( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerAsk), + orderNonce: makerAsk.orderNonce, + isNonceInvalidated: true + }), + takerUser, + takerUser, + makerAsk.strategyId, + makerAsk.currency, + makerAsk.collection, + makerAsk.itemIds, + makerAsk.amounts, + expectedRecipients, + expectedFees + ); + + looksRareProtocol.executeTakerBid{ value: price }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + _assertSuccessfulExecutionThroughETH(takerUser, makerUser, price, expectedFees); + } + + /** + * One ERC721 (where royalties come from the registry) is sold through a taker ask using WETH + */ + function testTakerAskERC721WithRoyaltiesFromRegistry(uint256 price) public { + vm.assume(price <= 2 ether); + + _setUpUsers(); + _setupRegistryRoyalties(address(mockERC721), NEW_ROYALTY_FEE); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + makerBid.price = price; + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify maker bid order + _assertValidMakerOrder(makerBid, signature); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Arrays for events + uint256[3] memory expectedFees = _calculateExpectedFees({ price: price, royaltyFeeBp: NEW_ROYALTY_FEE }); + address[2] memory expectedRecipients; + + expectedRecipients[0] = takerUser; + expectedRecipients[1] = _royaltyRecipient; + + // Execute taker ask transaction + vm.prank(takerUser); + + vm.expectEmit(true, false, false, true); + + emit TakerAsk( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerBid), + orderNonce: makerBid.orderNonce, + isNonceInvalidated: true + }), + takerUser, + makerUser, + makerBid.strategyId, + makerBid.currency, + makerBid.collection, + makerBid.itemIds, + makerBid.amounts, + expectedRecipients, + expectedFees + ); + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertSuccessfulExecutionThroughWETH(makerUser, takerUser, price, expectedFees); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + /** + * One ERC721 is sold through a taker ask using WETH. Address zero is specified as the recipient in the taker struct. + */ + function testTakerAskERC721WithAddressZeroSpecifiedAsRecipient(uint256 price) public { + vm.assume(price <= 2 ether); + _setUpUsers(); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + makerBid.price = price; + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Verify maker bid order + _assertValidMakerOrder(makerBid, signature); + + // Adjustment + takerAsk.recipient = address(0); + + // Mint asset + mockERC721.mint(takerUser, makerBid.itemIds[0]); + + // Arrays for events + uint256[3] memory expectedFees = _calculateExpectedFees({ price: price, royaltyFeeBp: 0 }); + address[2] memory expectedRecipients; + + expectedRecipients[0] = takerUser; + expectedRecipients[1] = address(0); // No royalties + + // Execute taker ask transaction + vm.prank(takerUser); + vm.expectEmit(true, false, false, true); + + emit TakerAsk( + NonceInvalidationParameters({ + orderHash: _computeOrderHash(makerBid), + orderNonce: makerBid.orderNonce, + isNonceInvalidated: true + }), + takerUser, + makerUser, + makerBid.strategyId, + makerBid.currency, + makerBid.collection, + makerBid.itemIds, + makerBid.amounts, + expectedRecipients, + expectedFees + ); + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertSuccessfulExecutionThroughWETH(makerUser, takerUser, price, expectedFees); + } + + /** + * Three ERC721 are sold through 3 taker bids in one transaction with non-atomicity. + */ + function testThreeTakerBidsERC721() public { + uint256 price = 0.015 ether; + _setUpUsers(); + + uint256 numberPurchases = 3; + + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](numberPurchases); + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](numberPurchases); + bytes[] memory signatures = new bytes[](numberPurchases); + + for (uint256 i; i < numberPurchases; i++) { + // Mint asset + mockERC721.mint(makerUser, i); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: price, // Fixed + itemId: i // (0, 1, etc.) + }); + + // Sign order + signatures[i] = _signMakerOrder(makerAsks[i], makerUserPK); + + takerBids[i] = _genericTakerOrder(); + } + + // Other execution parameters + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + for (uint256 i; i < numberPurchases; i++) { + // Taker user has received the asset + assertEq(mockERC721.ownerOf(i), takerUser); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, i), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - (numberPurchases * price)); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + + ((price * _sellerProceedBpWithStandardProtocolFeeBp) * numberPurchases) / + ONE_HUNDRED_PERCENT_IN_BP + ); + // No leftover in the balance of the contract + assertEq(address(looksRareProtocol).balance, 0); + } + + /** + * Transaction cannot go through if atomic, goes through if non-atomic (fund returns to buyer). + */ + function testThreeTakerBidsERC721OneFails() public { + _setUpUsers(); + + uint256 numberPurchases = 3; + uint256 faultyTokenId = numberPurchases - 1; + + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](numberPurchases); + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](numberPurchases); + bytes[] memory signatures = new bytes[](numberPurchases); + + for (uint256 i; i < numberPurchases; i++) { + // Mint asset + mockERC721.mint(makerUser, i); + + makerAsks[i] = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: CollectionType.ERC721, + orderNonce: i, + collection: address(mockERC721), + currency: ETH, + signer: makerUser, + price: 1.4 ether, // Fixed + itemId: i // (0, 1, etc.) + }); + + // Sign order + signatures[i] = _signMakerOrder(makerAsks[i], makerUserPK); + + takerBids[i] = _genericTakerOrder(); + } + + // Transfer tokenId = 2 to random user + address randomUser = address(55); + vm.prank(makerUser); + mockERC721.transferFrom(makerUser, randomUser, faultyTokenId); + + /** + * 1. The whole purchase fails if execution is atomic + */ + { + // Other execution parameters + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFromFail.selector)); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: 1.4 ether * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + true + ); + } + + /** + * 2. The whole purchase doesn't fail if execution is not-atomic + */ + { + // Other execution parameters + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + vm.prank(takerUser); + // Execute taker bid transaction + looksRareProtocol.executeMultipleTakerBids{ value: 1.4 ether * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + } + + for (uint256 i; i < faultyTokenId; i++) { + // Taker user has received the first two assets + assertEq(mockERC721.ownerOf(i), takerUser); + // Verify the first two nonces are marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, i), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + // Taker user has not received the asset + assertEq(mockERC721.ownerOf(faultyTokenId), randomUser); + // Verify the nonce is NOT marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, faultyTokenId), bytes32(0)); + // Taker bid user pays the whole price + assertEq(address(takerUser).balance, _initialETHBalanceUser - 1 - ((numberPurchases - 1) * 1.4 ether)); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + address(makerUser).balance, + _initialETHBalanceUser + + ((1.4 ether * _sellerProceedBpWithStandardProtocolFeeBp) * (numberPurchases - 1)) / + ONE_HUNDRED_PERCENT_IN_BP + ); + // 1 wei left in the balance of the contract + assertEq(address(looksRareProtocol).balance, 1); + } + + function testThreeTakerBidsERC721LengthsInvalid() public { + _setUpUsers(); + + uint256 price = 1.12121111111 ether; + uint256 numberPurchases = 3; + + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](numberPurchases); + bytes[] memory signatures = new bytes[](numberPurchases); + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](numberPurchases); + + // 1. Invalid maker asks length + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](numberPurchases - 1); + + vm.expectRevert(LengthsInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + // 2. Invalid signatures length + makerAsks = new OrderStructs.Maker[](numberPurchases); + signatures = new bytes[](numberPurchases - 1); + + vm.expectRevert(LengthsInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + + // 3. Invalid merkle trees length + signatures = new bytes[](numberPurchases); + merkleTrees = new OrderStructs.MerkleTree[](numberPurchases - 1); + + vm.expectRevert(LengthsInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeMultipleTakerBids{ value: price * numberPurchases }( + takerBids, + makerAsks, + signatures, + merkleTrees, + _EMPTY_AFFILIATE, + false + ); + } + + function _calculateExpectedFees( + uint256 price, + uint256 royaltyFeeBp + ) private pure returns (uint256[3] memory expectedFees) { + expectedFees[2] = (price * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + expectedFees[1] = (price * royaltyFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + if (expectedFees[2] + expectedFees[1] < ((price * _minTotalFeeBp) / ONE_HUNDRED_PERCENT_IN_BP)) { + expectedFees[2] = ((price * _minTotalFeeBp) / ONE_HUNDRED_PERCENT_IN_BP) - expectedFees[1]; + } + expectedFees[0] = price - (expectedFees[1] + expectedFees[2]); + } + + function _assertSuccessfulExecutionThroughWETH( + address buyer, + address seller, + uint256 price, + uint256[3] memory expectedFees + ) private { + // Buyer has received the asset + assertEq(mockERC721.ownerOf(itemId), buyer); + // Buyer pays the whole price + assertEq(weth.balanceOf(buyer), _initialWETHBalanceUser - price); + // Seller receives 99.5% of the whole price + assertEq(weth.balanceOf(seller), _initialWETHBalanceUser + expectedFees[0]); + assertEq( + weth.balanceOf(address(protocolFeeRecipient)), + expectedFees[2], + "ProtocolFeeRecipient should receive 1.5% of the whole price" + ); + // Royalty recipient receives 0.5% of the whole price + assertEq(weth.balanceOf(_royaltyRecipient), _initialWETHBalanceRoyaltyRecipient + expectedFees[1]); + } + + function _assertSuccessfulExecutionThroughETH( + address buyer, + address seller, + uint256 price, + uint256[3] memory expectedFees + ) private { + assertEq(mockERC721.ownerOf(itemId), buyer); + // Buyer pays the whole price + assertEq(address(buyer).balance, _initialETHBalanceUser - price); + // Seller receives 99.5% of the whole price + assertEq(address(seller).balance, _initialETHBalanceUser + expectedFees[0]); + // Royalty recipient receives 0.5% of the whole price + assertEq(address(_royaltyRecipient).balance, _initialETHBalanceRoyaltyRecipient + expectedFees[1]); + } +} diff --git a/contracts/test/foundry/marketplace/StrategyManager.t.sol b/contracts/test/foundry/marketplace/StrategyManager.t.sol new file mode 100644 index 00000000..4d18eba3 --- /dev/null +++ b/contracts/test/foundry/marketplace/StrategyManager.t.sol @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Interfaces +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; +import { IStrategy } from "@hypercerts/marketplace/interfaces/IStrategy.sol"; + +// Random strategy +import { StrategyCollectionOffer } from "@hypercerts/marketplace/executionStrategies/StrategyCollectionOffer.sol"; + +// Base test +import { ProtocolBase } from "./ProtocolBase.t.sol"; + +contract FalseBaseStrategy is IStrategy { + /** + * @inheritdoc IStrategy + */ + function isMakerOrderValid( + OrderStructs.Maker calldata, + bytes4 + ) external view override returns (bool isValid, bytes4 errorSelector) { + // + } + + /** + * @inheritdoc IStrategy + */ + function isLooksRareV2Strategy() external pure override returns (bool) { + return false; + } +} + +contract StrategyManagerTest is ProtocolBase, IStrategyManager { + function setUp() public { + _setUp(); + } + + /** + * Owner can discontinue strategy + */ + function testOwnerCanDiscontinueStrategy() public asPrankedUser(_owner) { + uint256 strategyId = 0; + uint16 standardProtocolFeeBp = 100; + uint16 minTotalFeeBp = 200; + bool isActive = false; + + vm.expectEmit(false, false, false, true); + emit StrategyUpdated(strategyId, isActive, standardProtocolFeeBp, minTotalFeeBp); + looksRareProtocol.updateStrategy(strategyId, isActive, standardProtocolFeeBp, minTotalFeeBp); + + ( + bool strategyIsActive, + uint16 strategyStandardProtocolFee, + uint16 strategyMinTotalFee, + uint16 strategyMaxProtocolFee, + bytes4 strategySelector, + bool strategyIsMakerBid, + address strategyImplementation + ) = looksRareProtocol.strategyInfo(strategyId); + + assertFalse(strategyIsActive); + assertEq(strategyStandardProtocolFee, standardProtocolFeeBp); + assertEq(strategyMinTotalFee, minTotalFeeBp); + assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); + assertEq(strategySelector, _EMPTY_BYTES4); + assertFalse(strategyIsMakerBid); + assertEq(strategyImplementation, address(0)); + } + + function testNewStrategyEventIsEmitted() public asPrankedUser(_owner) { + StrategyCollectionOffer strategy = new StrategyCollectionOffer(); + + uint256 strategyId = 1; + bytes4 selector = StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector; + bool isMakerBid = true; + address implementation = address(strategy); + + vm.expectEmit(true, false, false, true); + emit NewStrategy( + strategyId, + _standardProtocolFeeBp, + _minTotalFeeBp, + _maxProtocolFeeBp, + selector, + isMakerBid, + implementation + ); + + _addStrategy(implementation, selector, isMakerBid); + } + + /** + * Owner can change protocol fee information + */ + function testOwnerCanChangeStrategyProtocolFees() public asPrankedUser(_owner) { + uint256 strategyId = 0; + uint16 newStandardProtocolFeeBp = 100; + uint16 newMinTotalFeeBp = 200; + bool isActive = true; + + vm.expectEmit(false, false, false, true); + emit StrategyUpdated(strategyId, isActive, newStandardProtocolFeeBp, newMinTotalFeeBp); + looksRareProtocol.updateStrategy(strategyId, isActive, newStandardProtocolFeeBp, newMinTotalFeeBp); + + ( + bool strategyIsActive, + uint16 strategyStandardProtocolFee, + uint16 strategyMinTotalFee, + uint16 strategyMaxProtocolFee, + bytes4 strategySelector, + bool strategyIsMakerBid, + address strategyImplementation + ) = looksRareProtocol.strategyInfo(strategyId); + + assertTrue(strategyIsActive); + assertEq(strategyStandardProtocolFee, newStandardProtocolFeeBp); + assertEq(strategyMinTotalFee, newMinTotalFeeBp); + assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); + assertEq(strategySelector, _EMPTY_BYTES4); + assertFalse(strategyIsMakerBid); + assertEq(strategyImplementation, address(0)); + } + + /** + * Owner functions for strategy updates revert as expected under multiple revertion scenarios + */ + function testOwnerRevertionsForInvalidParametersUpdateStrategy() public asPrankedUser(_owner) { + ( + , + uint16 currentStandardProtocolFee, + uint16 currentMinTotalFee, + uint16 maxProtocolFeeBp, + , + , + + ) = looksRareProtocol.strategyInfo(0); + + // 1. newStandardProtocolFee is higher than maxProtocolFeeBp + uint16 newStandardProtocolFee = maxProtocolFeeBp + 1; + uint16 newMinTotalFee = currentMinTotalFee; + vm.expectRevert(StrategyProtocolFeeTooHigh.selector); + looksRareProtocol.updateStrategy(0, true, newStandardProtocolFee, newMinTotalFee); + + // 2. newMinTotalFee is higher than maxProtocolFeeBp + newStandardProtocolFee = currentStandardProtocolFee; + newMinTotalFee = maxProtocolFeeBp + 1; + vm.expectRevert(StrategyProtocolFeeTooHigh.selector); + looksRareProtocol.updateStrategy(0, true, newStandardProtocolFee, newMinTotalFee); + + // 3. It reverts if strategy doesn't exist + vm.expectRevert(StrategyNotUsed.selector); + looksRareProtocol.updateStrategy(1, true, currentStandardProtocolFee, currentMinTotalFee); + } + + /** + * Owner functions for strategy additions revert as expected under multiple revertion scenarios + */ + function testOwnerRevertionsForInvalidParametersAddStrategy() public asPrankedUser(_owner) { + uint16 standardProtocolFeeBp = 250; + uint16 minTotalFeeBp = 300; + uint16 maxProtocolFeeBp = 300; + address implementation = address(0); + + // 1. standardProtocolFeeBp is higher than maxProtocolFeeBp + maxProtocolFeeBp = standardProtocolFeeBp - 1; + vm.expectRevert(abi.encodeWithSelector(IStrategyManager.StrategyProtocolFeeTooHigh.selector)); + looksRareProtocol.addStrategy( + standardProtocolFeeBp, + minTotalFeeBp, + maxProtocolFeeBp, + _EMPTY_BYTES4, + true, + implementation + ); + + // 2. minTotalFeeBp is higher than maxProtocolFeeBp + maxProtocolFeeBp = minTotalFeeBp - 1; + vm.expectRevert(abi.encodeWithSelector(IStrategyManager.StrategyProtocolFeeTooHigh.selector)); + looksRareProtocol.addStrategy( + standardProtocolFeeBp, + minTotalFeeBp, + maxProtocolFeeBp, + _EMPTY_BYTES4, + true, + implementation + ); + + // 3. maxProtocolFeeBp is higher than _MAX_PROTOCOL_FEE + maxProtocolFeeBp = 500 + 1; + vm.expectRevert(abi.encodeWithSelector(IStrategyManager.StrategyProtocolFeeTooHigh.selector)); + looksRareProtocol.addStrategy( + standardProtocolFeeBp, + minTotalFeeBp, + maxProtocolFeeBp, + _EMPTY_BYTES4, + true, + implementation + ); + } + + function testAddStrategyNoSelector() public asPrankedUser(_owner) { + vm.expectRevert(IStrategyManager.StrategyHasNoSelector.selector); + _addStrategy(address(0), _EMPTY_BYTES4, true); + } + + function testAddStrategyNotV2Strategy() public asPrankedUser(_owner) { + bytes4 randomSelector = StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector; + + // 1. EOA + vm.expectRevert(); + _addStrategy(address(0), randomSelector, true); + + // 2. Invalid contract (e.g. LooksRareProtocol) + vm.expectRevert(); + _addStrategy(address(looksRareProtocol), randomSelector, true); + + // 3. Contract that implements the function but returns false + FalseBaseStrategy falseStrategy = new FalseBaseStrategy(); + + vm.expectRevert(NotV2Strategy.selector); + _addStrategy(address(falseStrategy), randomSelector, true); + } + + function testAddStrategyNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + _addStrategy(address(0), _EMPTY_BYTES4, true); + } + + function testUpdateStrategyNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + looksRareProtocol.updateStrategy(0, false, 299, 100); + } +} diff --git a/contracts/test/foundry/marketplace/TransferManager.t.sol b/contracts/test/foundry/marketplace/TransferManager.t.sol new file mode 100644 index 00000000..6d7f4643 --- /dev/null +++ b/contracts/test/foundry/marketplace/TransferManager.t.sol @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { IOwnableTwoSteps } from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Core contracts +import { LooksRareProtocol } from "@hypercerts/marketplace/LooksRareProtocol.sol"; +import { ITransferManager, TransferManager } from "@hypercerts/marketplace/TransferManager.sol"; +import { AmountInvalid, LengthsInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; + +// Mocks and other utils +import { MockERC721 } from "../../mock/MockERC721.sol"; +import { MockERC1155 } from "../../mock/MockERC1155.sol"; +import { TestHelpers } from "./utils/TestHelpers.sol"; +import { TestParameters } from "./utils/TestParameters.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; + +contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters { + address[] public operators; + MockERC721 public mockERC721; + MockERC1155 public mockERC1155; + TransferManager public transferManager; + + uint256 private constant tokenIdERC721 = 55; + uint256 private constant tokenId1ERC1155 = 1; + uint256 private constant amount1ERC1155 = 2; + uint256 private constant tokenId2ERC1155 = 2; + uint256 private constant amount2ERC1155 = 5; + + /** + * 0. Internal helper functions + */ + + function _grantApprovals(address user) private asPrankedUser(user) { + mockERC721.setApprovalForAll(address(transferManager), true); + mockERC1155.setApprovalForAll(address(transferManager), true); + address[] memory approvedOperators = new address[](1); + approvedOperators[0] = _transferrer; + + vm.expectEmit(true, false, false, true); + emit ApprovalsGranted(user, approvedOperators); + transferManager.grantApprovals(approvedOperators); + } + + function _allowOperator(address transferrer) private { + vm.prank(_owner); + vm.expectEmit(true, false, false, true); + emit OperatorAllowed(transferrer); + transferManager.allowOperator(transferrer); + } + + function setUp() public asPrankedUser(_owner) { + transferManager = new TransferManager(_owner); + mockERC721 = new MockERC721(); + mockERC1155 = new MockERC1155(); + operators.push(_transferrer); + + vm.deal(_transferrer, 100 ether); + vm.deal(_sender, 100 ether); + } + + /** + * 1. Happy cases + */ + + function testTransferSingleItemERC721() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 itemId = 500; + + vm.prank(_sender); + mockERC721.mint(_sender, itemId); + + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + + vm.prank(_transferrer); + transferManager.transferItemsERC721(address(mockERC721), _sender, _recipient, itemIds, amounts); + + assertEq(mockERC721.ownerOf(itemId), _recipient); + } + + function testTransferSingleItemERC1155() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 itemId = 1; + uint256 amount = 2; + + mockERC1155.mint(_sender, itemId, amount); + + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; + + vm.prank(_transferrer); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + + assertEq(mockERC1155.balanceOf(_recipient, itemId), amount); + } + + function testTransferBatchItemsERC721() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 tokenId1 = 1; + uint256 tokenId2 = 2; + + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = tokenId1; + itemIds[1] = tokenId2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1; + amounts[1] = 1; + + mockERC721.batchMint(_sender, itemIds); + + vm.prank(_transferrer); + transferManager.transferItemsERC721(address(mockERC721), _sender, _recipient, itemIds, amounts); + + assertEq(mockERC721.ownerOf(tokenId1), _recipient); + assertEq(mockERC721.ownerOf(tokenId2), _recipient); + } + + function testTransferBatchItemsERC1155() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 tokenId1 = 1; + uint256 amount1 = 2; + uint256 tokenId2 = 2; + uint256 amount2 = 5; + + mockERC1155.mint(_sender, tokenId1, amount1); + mockERC1155.mint(_sender, tokenId2, amount2); + + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = tokenId1; + itemIds[1] = tokenId2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount1; + amounts[1] = amount2; + + vm.prank(_transferrer); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + + assertEq(mockERC1155.balanceOf(_recipient, tokenId1), amount1); + assertEq(mockERC1155.balanceOf(_recipient, tokenId2), amount2); + } + + function testTransferBatchItemsAcrossCollectionERC721AndERC1155() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + + vm.prank(_transferrer); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + + assertEq(mockERC721.ownerOf(tokenIdERC721), _recipient); + assertEq(mockERC1155.balanceOf(_recipient, tokenId1ERC1155), amount1ERC1155); + assertEq(mockERC1155.balanceOf(_recipient, tokenId2ERC1155), amount2ERC1155); + } + + function testTransferBatchItemsAcrossCollectionERC721AndERC1155ByOwner() public asPrankedUser(_sender) { + mockERC721.setApprovalForAll(address(transferManager), true); + mockERC1155.setApprovalForAll(address(transferManager), true); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + + assertEq(mockERC721.ownerOf(tokenIdERC721), _recipient); + assertEq(mockERC1155.balanceOf(_recipient, tokenId1ERC1155), amount1ERC1155); + assertEq(mockERC1155.balanceOf(_recipient, tokenId2ERC1155), amount2ERC1155); + } + + /** + * 2. Revertion patterns + */ + function testTransferItemsERC721AmountIsNotOne(uint256 amount) public { + vm.assume(amount != 1); + + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 itemId = 500; + + mockERC721.mint(_sender, itemId); + + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; + + vm.expectRevert(AmountInvalid.selector); + vm.prank(_transferrer); + transferManager.transferItemsERC721(address(mockERC721), _sender, _recipient, itemIds, amounts); + } + + function testTransferSingleItemERC1155AmountIsZero() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 itemId = 500; + + mockERC1155.mint(_sender, itemId, 1); + + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + + vm.expectRevert(AmountInvalid.selector); + vm.prank(_transferrer); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + } + + function testTransferMultipleItemsERC1155AmountIsZero() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + uint256 itemIdOne = 500; + uint256 itemIdTwo = 501; + + mockERC1155.mint(_sender, itemIdOne, 1); + mockERC1155.mint(_sender, itemIdTwo, 1); + + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = itemIdOne; + itemIds[1] = itemIdTwo; + uint256[] memory amounts = new uint256[](2); + amounts[0] = 0; + amounts[1] = 0; + + vm.expectRevert(AmountInvalid.selector); + vm.prank(_transferrer); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + } + + function testTransferBatchItemsAcrossCollectionZeroLength() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + ITransferManager.BatchTransferItem[] memory items = new ITransferManager.BatchTransferItem[](0); + + vm.expectRevert(LengthsInvalid.selector); + vm.prank(_transferrer); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + } + + function testCannotBatchTransferIfERC721AmountIsNotOne(uint256 amount) public { + vm.assume(amount != 1); + + _allowOperator(_transferrer); + _grantApprovals(_sender); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + items[1].amounts[0] = amount; + + vm.expectRevert(AmountInvalid.selector); + vm.prank(_sender); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + } + + function testCannotBatchTransferIfERC1155AmountIsZero() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + items[0].amounts[0] = 0; + + vm.expectRevert(AmountInvalid.selector); + vm.prank(_sender); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + } + + function testTransferBatchItemsAcrossCollectionPerCollectionItemIdsLengthZero() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + items[0].itemIds = new uint256[](0); + items[0].amounts = new uint256[](0); + + vm.prank(_transferrer); + vm.expectRevert(LengthsInvalid.selector); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + } + + function testCannotTransferERC721IfOperatorApprovalsRevokedByUserOrOperatorRemovedByOwner() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + // 1. User revokes the operator + vm.prank(_sender); + vm.expectEmit(false, false, false, true); + emit ApprovalsRemoved(_sender, operators); + transferManager.revokeApprovals(operators); + + uint256 itemId = 500; + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferItemsERC721(address(mockERC721), _sender, _recipient, itemIds, amounts); + + // 2. Sender grants again approvals but owner removes the operators + _grantApprovals(_sender); + vm.prank(_owner); + vm.expectEmit(false, false, false, true); + emit OperatorRemoved(_transferrer); + transferManager.removeOperator(_transferrer); + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferItemsERC721(address(mockERC721), _sender, _recipient, itemIds, amounts); + } + + function testCannotTransferERC1155IfOperatorApprovalsRevokedByUserOrOperatorRemovedByOwner() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + // 1. User revokes the operator + vm.prank(_sender); + vm.expectEmit(false, false, false, true); + emit ApprovalsRemoved(_sender, operators); + transferManager.revokeApprovals(operators); + + uint256 itemId = 500; + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 5; + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + + // 2. Sender grants again approvals but owner removes the operators + _grantApprovals(_sender); + vm.prank(_owner); + vm.expectEmit(false, false, false, true); + emit OperatorRemoved(_transferrer); + transferManager.removeOperator(_transferrer); + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + } + + function testCannotBatchTransferIfOperatorApprovalsRevoked() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + // 1. User revokes the operator + vm.prank(_sender); + vm.expectEmit(false, false, false, true); + emit ApprovalsRemoved(_sender, operators); + transferManager.revokeApprovals(operators); + + ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems(); + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + + // 2. Sender grants again approvals but owner removes the operators + _grantApprovals(_sender); + vm.prank(_owner); + vm.expectEmit(false, false, false, true); + emit OperatorRemoved(_transferrer); + transferManager.removeOperator(_transferrer); + + vm.prank(_transferrer); + vm.expectRevert(ITransferManager.TransferCallerInvalid.selector); + transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient); + } + + function testCannotTransferERC721OrERC1155IfArrayLengthIs0() public { + uint256[] memory emptyArrayUint256 = new uint256[](0); + + // 1. ERC721 + vm.expectRevert(LengthsInvalid.selector); + transferManager.transferItemsERC721( + address(mockERC721), + _sender, + _recipient, + emptyArrayUint256, + emptyArrayUint256 + ); + + // 2. ERC1155 length is 0 + vm.expectRevert(LengthsInvalid.selector); + transferManager.transferItemsERC1155( + address(mockERC1155), + _sender, + _recipient, + emptyArrayUint256, + emptyArrayUint256 + ); + } + + function testCannotTransferERC1155IfArrayLengthDiffers() public { + uint256[] memory itemIds = new uint256[](2); + uint256[] memory amounts = new uint256[](3); + + vm.expectRevert(LengthsInvalid.selector); + transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts); + } + + function testUserCannotGrantOrRevokeApprovalsIfArrayLengthIs0() public { + address[] memory emptyArrayAddresses = new address[](0); + + // 1. Grant approvals + vm.expectRevert(LengthsInvalid.selector); + transferManager.grantApprovals(emptyArrayAddresses); + + // 2. Revoke approvals + vm.expectRevert(LengthsInvalid.selector); + transferManager.revokeApprovals(emptyArrayAddresses); + } + + function testUserCannotGrantApprovalIfOperatorOperatorNotAllowed() public asPrankedUser(_owner) { + address randomOperator = address(420); + transferManager.allowOperator(randomOperator); + vm.expectRevert(ITransferManager.OperatorAlreadyAllowed.selector); + transferManager.allowOperator(randomOperator); + } + + function testAllowOperatorNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + transferManager.allowOperator(address(0)); + } + + function testOwnerCannotallowOperatorIfOperatorAlreadyAllowed() public asPrankedUser(_owner) { + address randomOperator = address(420); + transferManager.allowOperator(randomOperator); + vm.expectRevert(ITransferManager.OperatorAlreadyAllowed.selector); + transferManager.allowOperator(randomOperator); + } + + function testOwnerCannotRemoveOperatorIfOperatorNotAllowed() public asPrankedUser(_owner) { + address notOperator = address(420); + vm.expectRevert(ITransferManager.OperatorNotAllowed.selector); + transferManager.removeOperator(notOperator); + } + + function testUserCannotGrantApprovalsIfOperatorNotAllowed() public { + address[] memory approvedOperators = new address[](1); + approvedOperators[0] = _transferrer; + + vm.expectRevert(ITransferManager.OperatorNotAllowed.selector); + vm.prank(_sender); + transferManager.grantApprovals(approvedOperators); + } + + function testUserCannotGrantApprovalsIfOperatorAlreadyApprovedByUser() public { + _allowOperator(_transferrer); + _grantApprovals(_sender); + + address[] memory approvedOperators = new address[](1); + approvedOperators[0] = _transferrer; + + vm.expectRevert(ITransferManager.OperatorAlreadyApprovedByUser.selector); + vm.prank(_sender); + transferManager.grantApprovals(approvedOperators); + } + + function testUserCannotRevokeApprovalsIfOperatorNotApprovedByUser() public { + address[] memory approvedOperators = new address[](1); + approvedOperators[0] = _transferrer; + + vm.expectRevert(ITransferManager.OperatorNotApprovedByUser.selector); + vm.prank(_sender); + transferManager.revokeApprovals(approvedOperators); + } + + function testRemoveOperatorNotOwner() public { + vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); + transferManager.removeOperator(address(0)); + } + + function _generateValidBatchTransferItems() private returns (BatchTransferItem[] memory items) { + items = new ITransferManager.BatchTransferItem[](2); + + { + mockERC721.mint(_sender, tokenIdERC721); + mockERC1155.mint(_sender, tokenId1ERC1155, amount1ERC1155); + mockERC1155.mint(_sender, tokenId2ERC1155, amount2ERC1155); + + uint256[] memory tokenIdsERC1155 = new uint256[](2); + tokenIdsERC1155[0] = tokenId1ERC1155; + tokenIdsERC1155[1] = tokenId2ERC1155; + + uint256[] memory amountsERC1155 = new uint256[](2); + amountsERC1155[0] = amount1ERC1155; + amountsERC1155[1] = amount2ERC1155; + + uint256[] memory tokenIdsERC721 = new uint256[](1); + tokenIdsERC721[0] = tokenIdERC721; + + uint256[] memory amountsERC721 = new uint256[](1); + amountsERC721[0] = 1; + + items[0] = ITransferManager.BatchTransferItem({ + collection: address(mockERC1155), + collectionType: CollectionType.ERC1155, + itemIds: tokenIdsERC1155, + amounts: amountsERC1155 + }); + items[1] = ITransferManager.BatchTransferItem({ + collection: address(mockERC721), + collectionType: CollectionType.ERC721, + itemIds: tokenIdsERC721, + amounts: amountsERC721 + }); + } + } +} diff --git a/contracts/test/foundry/marketplace/assembly/VerifyOrderTimestampValidityEquivalence.t.sol b/contracts/test/foundry/marketplace/assembly/VerifyOrderTimestampValidityEquivalence.t.sol new file mode 100644 index 00000000..9a124211 --- /dev/null +++ b/contracts/test/foundry/marketplace/assembly/VerifyOrderTimestampValidityEquivalence.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { Test } from "forge-std/Test.sol"; + +// Assembly +import { OutsideOfTimeRange_error_selector, OutsideOfTimeRange_error_length, Error_selector_offset } from "@hypercerts/marketplace/constants/AssemblyConstants.sol"; + +contract NonAssemblyCode { + error OutsideOfTimeRange(); + + function run(uint256 startTime, uint256 endTime) external view returns (bool) { + if (startTime > block.timestamp || endTime < block.timestamp) revert OutsideOfTimeRange(); + return true; + } +} + +contract AssemblyCode { + function run(uint256 startTime, uint256 endTime) external view returns (bool) { + assembly { + if or(gt(startTime, timestamp()), lt(endTime, timestamp())) { + mstore(0x00, OutsideOfTimeRange_error_selector) + revert(Error_selector_offset, OutsideOfTimeRange_error_length) + } + } + return true; + } +} + +contract VerifyOrderTimestampValidityEquivalenceTest is Test { + AssemblyCode private assemblyCode; + NonAssemblyCode private nonAssemblyCode; + + function setUp() public { + assemblyCode = new AssemblyCode(); + nonAssemblyCode = new NonAssemblyCode(); + } + + /** + * @dev The gap between start and end time is always at least + * 3 seconds so that we can test the 2 boundaries as well + * as the 2 timestamps inside the boundaries + */ + function testEquivalenceWithinBoundaries(uint256 startTime, uint256 endTime) public { + vm.assume(endTime > 3 && startTime < endTime - 3); + + vm.warp(startTime); + assertTrue(assemblyCode.run(startTime, endTime)); + assertTrue(nonAssemblyCode.run(startTime, endTime)); + + vm.warp(startTime + 1); + assertTrue(assemblyCode.run(startTime, endTime)); + assertTrue(nonAssemblyCode.run(startTime, endTime)); + + vm.warp(endTime - 1); + assertTrue(assemblyCode.run(startTime, endTime)); + assertTrue(nonAssemblyCode.run(startTime, endTime)); + + vm.warp(endTime); + assertTrue(assemblyCode.run(startTime, endTime)); + assertTrue(nonAssemblyCode.run(startTime, endTime)); + } + + function testEquivalenceTooEarly(uint256 startTime, uint256 endTime) public { + vm.assume(startTime > 0 && startTime < endTime); + + vm.warp(startTime - 1); + + vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); + assemblyCode.run(startTime, endTime); + + vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); + nonAssemblyCode.run(startTime, endTime); + } + + function testEquivalenceTooLate(uint256 startTime, uint256 endTime) public { + vm.assume(endTime > 0 && endTime < type(uint256).max && startTime < endTime); + + vm.warp(endTime + 1); + + vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); + assemblyCode.run(startTime, endTime); + + vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); + nonAssemblyCode.run(startTime, endTime); + } +} diff --git a/contracts/test/foundry/marketplace/executionStrategies/Chainlink/USDDynamicAskOrders.t.sol b/contracts/test/foundry/marketplace/executionStrategies/Chainlink/USDDynamicAskOrders.t.sol new file mode 100644 index 00000000..df930c71 --- /dev/null +++ b/contracts/test/foundry/marketplace/executionStrategies/Chainlink/USDDynamicAskOrders.t.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Errors and constants +import { AmountInvalid, BidTooLow, OrderInvalid, CurrencyInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { ChainlinkPriceInvalid, PriceNotRecentEnough } from "@hypercerts/marketplace/errors/ChainlinkErrors.sol"; +import { MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE, MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE, STRATEGY_NOT_ACTIVE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Strategies +import { StrategyChainlinkUSDDynamicAsk } from "@hypercerts/marketplace/executionStrategies/Chainlink/StrategyChainlinkUSDDynamicAsk.sol"; + +// Mocks and other tests +import { MockChainlinkAggregator } from "../../../../mock/MockChainlinkAggregator.sol"; +import { MockERC20 } from "../../../../mock/MockERC20.sol"; +import { ProtocolBase } from "../../ProtocolBase.t.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract USDDynamicAskOrdersTest is ProtocolBase, IStrategyManager { + StrategyChainlinkUSDDynamicAsk public strategyUSDDynamicAsk; + bytes4 public selector = StrategyChainlinkUSDDynamicAsk.executeStrategyWithTakerBid.selector; + + // At block 15740567 + // roundId uint80 : 92233720368547793259 + // answer int256 : 126533075631 + // startedAt uint256 : 1665680123 + // updatedAt uint256 : 1665680123 + // answeredInRound uint80 : 92233720368547793259 + uint256 private constant CHAINLINK_PRICE_UPDATED_AT = 1665680123; + uint256 private constant FORKED_BLOCK_NUMBER = 15740567; + uint256 private constant LATEST_CHAINLINK_ANSWER_IN_WAD = 126533075631 * 1e10; + uint256 private constant MAXIMUM_LATENCY = 3_600 seconds; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("mainnet"), FORKED_BLOCK_NUMBER); + _setUp(); + _setUpUsers(); + _setUpNewStrategy(); + } + + function _setUpNewStrategy() private asPrankedUser(_owner) { + strategyUSDDynamicAsk = new StrategyChainlinkUSDDynamicAsk( + _owner, + address(weth), + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 // Mainnet address of the Chainlink price feed + ); + _addStrategy(address(strategyUSDDynamicAsk), selector, false); + } + + function _createMakerAskAndTakerBid( + uint256 numberOfItems, + uint256 numberOfAmounts, + uint256 desiredSalePriceInUSD + ) private returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { + uint256[] memory itemIds = new uint256[](numberOfItems); + for (uint256 i; i < numberOfItems; ) { + mockERC721.mint(makerUser, i + 1); + itemIds[i] = i + 1; + unchecked { + ++i; + } + } + + uint256[] memory amounts = new uint256[](numberOfAmounts); + for (uint256 i; i < numberOfAmounts; ) { + amounts[i] = 1; + unchecked { + ++i; + } + } + + newMakerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 0.99 ether, + itemId: 1 + }); + + newMakerAsk.itemIds = itemIds; + newMakerAsk.amounts = amounts; + newMakerAsk.additionalParameters = abi.encode(desiredSalePriceInUSD); + + newTakerBid = OrderStructs.Taker(takerUser, abi.encode(1 ether)); + } + + function testNewStrategy() public { + _assertStrategyAttributes(address(strategyUSDDynamicAsk), selector, false); + } + + function testMaxLatency() public { + assertEq(strategyUSDDynamicAsk.maxLatency(), 3_600); + } + + function testUSDDynamicAskChainlinkPriceInvalid() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + MockChainlinkAggregator priceFeed = new MockChainlinkAggregator(); + vm.etch(CHAINLINK_ETH_USD_PRICE_FEED, address(priceFeed).code); + + MockChainlinkAggregator(CHAINLINK_ETH_USD_PRICE_FEED).setAnswer(-1); + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, ChainlinkPriceInvalid.selector); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + MockChainlinkAggregator(CHAINLINK_ETH_USD_PRICE_FEED).setAnswer(0); + (isValid, errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, ChainlinkPriceInvalid.selector); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testUSDDynamicAskUSDValueGreaterThanOrEqualToMinAcceptedEthValue() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(1), takerUser); + // Taker bid user pays the whole price + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 1 ether); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 0.995 ether); + } + + function testUSDDynamicAskUSDValueLessThanMinAcceptedEthValue() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: (LATEST_CHAINLINK_ANSWER_IN_WAD * 98) / 100 + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(1), takerUser); + + // Taker bid user pays the whole price + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 0.99 ether); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 0.98505 ether); + } + + // This tests that we can handle fractions + function testUSDDynamicAskUSDValueLessThanOneETH() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD / 2 + }); + + makerAsk.price = 0.49 ether; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(1), takerUser); + + // Taker bid user pays the whole price + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 0.5 ether); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 0.4975 ether); + } + + function testUSDDynamicAskBidderOverpaid() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + makerAsk.currency = ETH; + // Bidder overpays by 0.1 ETH + uint256 maxPrice = 1.1 ether; + takerBid.additionalParameters = abi.encode(maxPrice); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + uint256 initialETHBalanceTakerUser = address(takerUser).balance; + uint256 initialETHBalanceMakerUser = address(makerUser).balance; + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid{ value: maxPrice }( + takerBid, + makerAsk, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(1), takerUser); + // Taker bid user pays the whole price, but without overpaying + assertEq(address(takerUser).balance, initialETHBalanceTakerUser - 1 ether - 1); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq(address(makerUser).balance, initialETHBalanceMakerUser + 0.995 ether); + } + + function testOraclePriceNotRecentEnough() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + makerAsk.startTime = CHAINLINK_PRICE_UPDATED_AT; + uint256 latencyViolationTimestamp = CHAINLINK_PRICE_UPDATED_AT + MAXIMUM_LATENCY + 1 seconds; + makerAsk.endTime = latencyViolationTimestamp; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + vm.warp(latencyViolationTimestamp); + + bytes4 errorSelector = PriceNotRecentEnough.selector; + + _assertOrderIsInvalid(makerAsk, errorSelector); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testCannotExecuteIfNotWETHOrETH() public { + MockERC20 fakeCurrency = new MockERC20(); + vm.prank(_owner); + looksRareProtocol.updateCurrencyStatus(address(fakeCurrency), true); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + // Adjust the currency to something creative + makerAsk.currency = address(fakeCurrency); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, CurrencyInvalid.selector); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testZeroItemIdsLength() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 0, + numberOfAmounts: 0, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, OrderInvalid.selector); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testItemIdsAndAmountsLengthMismatch() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 2, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, OrderInvalid.selector); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testWrongQuoteType() public { + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerBid, selector); + + assertFalse(orderIsValid); + assertEq(errorSelector, QuoteTypeInvalid.selector); + } + + function testZeroAmount() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + makerAsk.amounts = amounts; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Valid, taker struct validation only happens during execution + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, OrderInvalid.selector); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testAmountGreaterThanOneForERC721() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 2; + makerAsk.amounts = amounts; + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsInvalid(makerAsk, OrderInvalid.selector); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerBidTooLow() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + takerBid.additionalParameters = abi.encode(0.99 ether); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerAsk); + _assertValidMakerOrder(makerAsk, signature); + + vm.expectRevert(BidTooLow.selector); + vm.prank(takerUser); + + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInactiveStrategy() public { + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + desiredSalePriceInUSD: LATEST_CHAINLINK_ANSWER_IN_WAD + }); + + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + vm.prank(_owner); + looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); + + _assertOrderIsValid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, STRATEGY_NOT_ACTIVE); + + vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); + vm.prank(takerUser); + + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInvalidSelector() public { + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, bytes4(0)); + assertFalse(orderIsValid); + assertEq(errorSelector, FunctionSelectorInvalid.selector); + } + + function _assertOrderIsInvalid(OrderStructs.Maker memory makerAsk, bytes4 expectedErrorSelector) private { + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, expectedErrorSelector); + } + + function _assertOrderIsValid(OrderStructs.Maker memory makerAsk) private { + (bool isValid, bytes4 errorSelector) = strategyUSDDynamicAsk.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + } +} diff --git a/contracts/test/foundry/marketplace/executionStrategies/CollectionOffers.t.sol b/contracts/test/foundry/marketplace/executionStrategies/CollectionOffers.t.sol new file mode 100644 index 00000000..429bee91 --- /dev/null +++ b/contracts/test/foundry/marketplace/executionStrategies/CollectionOffers.t.sol @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Murky (third-party) library is used to compute Merkle trees in Solidity +import { Merkle } from "murky/Merkle.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Shared errors +import { AmountInvalid, OrderInvalid, FunctionSelectorInvalid, MerkleProofInvalid, QuoteTypeInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Strategies +import { StrategyCollectionOffer } from "@hypercerts/marketplace/executionStrategies/StrategyCollectionOffer.sol"; + +// Base test +import { ProtocolBase } from "../ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract CollectionOrdersTest is ProtocolBase { + StrategyCollectionOffer public strategyCollectionOffer; + bytes4 public selectorNoProof = strategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector; + bytes4 public selectorWithProof = strategyCollectionOffer.executeCollectionStrategyWithTakerAskWithProof.selector; + + uint256 private constant price = 1 ether; // Fixed price of sale + bytes32 private constant mockMerkleRoot = bytes32(keccak256("Mock")); // Mock merkle root + + function setUp() public { + _setUp(); + _setUpNewStrategies(); + } + + function _setUpNewStrategies() private asPrankedUser(_owner) { + strategyCollectionOffer = new StrategyCollectionOffer(); + _addStrategy(address(strategyCollectionOffer), selectorNoProof, true); + _addStrategy(address(strategyCollectionOffer), selectorWithProof, true); + } + + function testNewStrategies() public { + _assertStrategyAttributes(address(strategyCollectionOffer), selectorNoProof, true); + + ( + bool strategyIsActive, + uint16 strategyStandardProtocolFee, + uint16 strategyMinTotalFee, + uint16 strategyMaxProtocolFee, + bytes4 strategySelector, + bool strategyIsMakerBid, + address strategyImplementation + ) = looksRareProtocol.strategyInfo(2); + + assertTrue(strategyIsActive); + assertEq(strategyStandardProtocolFee, _standardProtocolFeeBp); + assertEq(strategyMinTotalFee, _minTotalFeeBp); + assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); + assertEq(strategySelector, selectorWithProof); + assertTrue(strategyIsMakerBid); + assertEq(strategyImplementation, address(strategyCollectionOffer)); + } + + function testMakerBidAmountsLengthNotOne() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + // Adjust strategy for collection order and sign order + // Change array to make it bigger than expected + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1; + makerBid.strategyId = 1; + makerBid.amounts = amounts; + takerAsk.additionalParameters = abi.encode(1, 1); + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsInvalid(makerBid, false); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // With proof + makerBid.strategyId = 2; + makerBid.additionalParameters = abi.encode(mockMerkleRoot); + signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsInvalid(makerBid, true); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testZeroAmount() public { + _setUpUsers(); + + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMockMakerBidAndTakerAsk( + address(mockERC721), + address(weth) + ); + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + makerBid.amounts = amounts; + makerBid.strategyId = 1; + makerBid.additionalParameters = abi.encode(mockMerkleRoot); + takerAsk.additionalParameters = abi.encode(1, 1); + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsInvalid(makerBid, false); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + /** + * Any itemId for ERC721 (where royalties come from the registry) is sold through a collection taker ask using WETH. + * We use fuzzing to generate the tokenId that is sold. + */ + function testTakerAskCollectionOrderERC721(uint256 tokenId) public { + _setUpUsers(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 // Not used + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Mint asset + mockERC721.mint(takerUser, tokenId); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(tokenId, 1)); + + _assertOrderIsValid(makerBid, false); + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertSuccessfulTakerAsk(makerBid, tokenId); + } + + /** + * A collection offer with merkle tree criteria + */ + function testTakerAskCollectionOrderWithMerkleTreeERC721() public { + _setUpUsers(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 // Not used + }); + + uint256 itemIdInMerkleTree = 2; + (bytes32 merkleRoot, bytes32[] memory proof) = _mintNFTsToOwnerAndGetMerkleRootAndProof({ + owner: takerUser, + numberOfItemsInMerkleTree: 5, + itemIdInMerkleTree: itemIdInMerkleTree + }); + + makerBid.additionalParameters = abi.encode(merkleRoot); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(itemIdInMerkleTree, proof)); + + // Verify validity of maker bid order + _assertOrderIsValid(makerBid, true); + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + _assertSuccessfulTakerAsk(makerBid, itemIdInMerkleTree); + } + + function testTakerAskCannotExecuteWithInvalidProof(uint256 itemIdSold) public { + vm.assume(itemIdSold > 5); + _setUpUsers(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 // Not used + }); + + (bytes32 merkleRoot, bytes32[] memory proof) = _mintNFTsToOwnerAndGetMerkleRootAndProof({ + owner: takerUser, + numberOfItemsInMerkleTree: 5, + // Doesn't matter what itemIdInMerkleTree is as we are are going to tamper with the proof + itemIdInMerkleTree: 4 + }); + makerBid.additionalParameters = abi.encode(merkleRoot); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Prepare the taker ask + proof[0] = bytes32(0); // Tamper with the proof + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(itemIdSold, proof)); + + // Verify validity of maker bid order + _assertOrderIsValid(makerBid, true); + _assertValidMakerOrder(makerBid, signature); + + vm.prank(takerUser); + vm.expectRevert(MerkleProofInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInvalidAmounts() public { + _setUpUsers(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 + }); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(5)); + + // 1. Amount is 0 (without merkle proof) + makerBid.amounts[0] = 0; + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + _assertOrderIsInvalid(makerBid, false); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 2. Amount is too high for ERC721 (without merkle proof) + makerBid.amounts[0] = 2; + signature = _signMakerOrder(makerBid, makerUserPK); + _assertOrderIsInvalid(makerBid, false); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 3. Amount is 0 (with merkle proof) + makerBid.strategyId = 2; + uint256 itemIdInMerkleTree = 5; + (bytes32 merkleRoot, bytes32[] memory proof) = _mintNFTsToOwnerAndGetMerkleRootAndProof({ + owner: takerUser, + numberOfItemsInMerkleTree: 6, + itemIdInMerkleTree: itemIdInMerkleTree + }); + + makerBid.additionalParameters = abi.encode(merkleRoot); + makerBid.amounts[0] = 0; + signature = _signMakerOrder(makerBid, makerUserPK); + + takerAsk.additionalParameters = abi.encode(itemIdInMerkleTree, proof); + + _assertOrderIsInvalid(makerBid, true); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 4. Amount is too high for ERC721 (with merkle proof) + makerBid.amounts[0] = 2; + signature = _signMakerOrder(makerBid, makerUserPK); + _assertOrderIsInvalid(makerBid, true); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testMerkleRootLengthIsNot32() public { + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 + }); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsInvalid(makerBid, true); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(); // It should revert without data (since the root cannot be extracted since the additionalParameters length is 0) + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBid, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + + function testInvalidSelector() public { + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 3, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyCollectionOffer.isMakerOrderValid(makerBid, bytes4(0)); + assertFalse(orderIsValid); + assertEq(errorSelector, FunctionSelectorInvalid.selector); + } + + function testWrongQuoteType() public { + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemId: 0 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyCollectionOffer.isMakerOrderValid( + makerAsk, + selectorNoProof + ); + + assertFalse(orderIsValid); + assertEq(errorSelector, QuoteTypeInvalid.selector); + } + + function _assertOrderIsValid(OrderStructs.Maker memory makerBid, bool withProof) private { + (bool orderIsValid, bytes4 errorSelector) = strategyCollectionOffer.isMakerOrderValid( + makerBid, + withProof ? selectorWithProof : selectorNoProof + ); + assertTrue(orderIsValid); + assertEq(errorSelector, _EMPTY_BYTES4); + } + + function _assertOrderIsInvalid(OrderStructs.Maker memory makerBid, bool withProof) private { + (bool orderIsValid, bytes4 errorSelector) = strategyCollectionOffer.isMakerOrderValid( + makerBid, + withProof ? selectorWithProof : selectorNoProof + ); + + assertFalse(orderIsValid); + assertEq(errorSelector, OrderInvalid.selector); + } + + function _mintNFTsToOwnerAndGetMerkleRootAndProof( + address owner, + uint256 numberOfItemsInMerkleTree, + uint256 itemIdInMerkleTree + ) private returns (bytes32 merkleRoot, bytes32[] memory proof) { + require(itemIdInMerkleTree < numberOfItemsInMerkleTree, "Invalid itemIdInMerkleTree"); + + // Initialize Merkle Tree + Merkle m = new Merkle(); + + bytes32[] memory merkleTreeIds = new bytes32[](numberOfItemsInMerkleTree); + for (uint256 i; i < numberOfItemsInMerkleTree; i++) { + mockERC721.mint(owner, i); + merkleTreeIds[i] = keccak256(abi.encodePacked(i)); + } + + // Compute merkle root + merkleRoot = m.getRoot(merkleTreeIds); + proof = m.getProof(merkleTreeIds, itemIdInMerkleTree); + + assertTrue(m.verifyProof(merkleRoot, proof, merkleTreeIds[itemIdInMerkleTree])); + } + + function _assertSuccessfulTakerAsk(OrderStructs.Maker memory makerBid, uint256 tokenId) private { + // Taker user has received the asset + assertEq(mockERC721.ownerOf(tokenId), makerUser); + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Taker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } +} diff --git a/contracts/test/foundry/marketplace/executionStrategies/DutchAuctionOrders.t.sol b/contracts/test/foundry/marketplace/executionStrategies/DutchAuctionOrders.t.sol new file mode 100644 index 00000000..dd8b05c6 --- /dev/null +++ b/contracts/test/foundry/marketplace/executionStrategies/DutchAuctionOrders.t.sol @@ -0,0 +1,436 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Shared errors +import { AmountInvalid, BidTooLow, OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { STRATEGY_NOT_ACTIVE, MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Strategies +import { StrategyDutchAuction } from "@hypercerts/marketplace/executionStrategies/StrategyDutchAuction.sol"; + +// Other tests +import { ProtocolBase } from "../ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract DutchAuctionOrdersTest is ProtocolBase, IStrategyManager { + StrategyDutchAuction public strategyDutchAuction; + bytes4 public selector = StrategyDutchAuction.executeStrategyWithTakerBid.selector; + + function setUp() public { + _setUp(); + } + + function _setUpNewStrategy() private asPrankedUser(_owner) { + strategyDutchAuction = new StrategyDutchAuction(); + _addStrategy(address(strategyDutchAuction), selector, false); + } + + function _createMakerAskAndTakerBid( + uint256 numberOfItems, + uint256 numberOfAmounts, + uint256 startPrice, + uint256 endPrice, + uint256 endTime + ) private returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { + uint256[] memory itemIds = new uint256[](numberOfItems); + for (uint256 i; i < numberOfItems; ) { + mockERC721.mint(makerUser, i + 1); + itemIds[i] = i + 1; + unchecked { + ++i; + } + } + + uint256[] memory amounts = new uint256[](numberOfAmounts); + for (uint256 i; i < numberOfAmounts; ) { + amounts[i] = 1; + unchecked { + ++i; + } + } + + newMakerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: endPrice, + itemId: 1 + }); + + newMakerAsk.itemIds = itemIds; + newMakerAsk.amounts = amounts; + + newMakerAsk.endTime = endTime; + newMakerAsk.additionalParameters = abi.encode(startPrice); + + // Using startPrice as the maxPrice + newTakerBid = OrderStructs.Taker(takerUser, abi.encode(startPrice)); + } + + function testNewStrategy() public { + _setUpNewStrategy(); + _assertStrategyAttributes(address(strategyDutchAuction), selector, false); + } + + function _fuzzAssumptions( + uint256 _startPrice, + uint256 _duration, + uint256 _decayPerSecond, + uint256 _elapsedTime + ) private returns (uint256 startPrice, uint256 duration, uint256 decayPerSecond, uint256 elapsedTime) { + // Bound instead of assume to handle too many rejections + // These limits should be realistically way more than enough + // vm.assume(duration > 0 && duration <= 31_536_000); + // Assume the NFT is worth at least 0.01 USD at today's ETH price (2023-01-13 18:00:00 UTC) + // vm.assume(startPrice > 1e12 && startPrice <= 100_000 ether); + // vm.assume(decayPerSecond > 0 && decayPerSecond < startPrice); + // vm.assume(elapsedTime <= duration && startPrice > decayPerSecond * duration); + + duration = bound(_duration, 1, 31_536_600); + startPrice = bound(_startPrice, 1e12, 100_000 ether); + decayPerSecond = bound(_decayPerSecond, 1, startPrice); + + vm.assume(_elapsedTime <= duration && startPrice > decayPerSecond * duration); + elapsedTime = _elapsedTime; + } + + function _calculatePrices( + uint256 startPrice, + uint256 duration, + uint256 decayPerSecond, + uint256 elapsedTime + ) private pure returns (uint256 endPrice, uint256 executionPrice) { + endPrice = startPrice - decayPerSecond * duration; + uint256 discount = decayPerSecond * elapsedTime; + executionPrice = startPrice - discount; + } + + function testDutchAuction( + uint256 _startPrice, + uint256 _duration, + uint256 _decayPerSecond, + uint256 _elapsedTime + ) public { + (uint256 startPrice, uint256 duration, uint256 decayPerSecond, uint256 elapsedTime) = _fuzzAssumptions( + _startPrice, + _duration, + _decayPerSecond, + _elapsedTime + ); + _setUpUsers(); + _setUpNewStrategy(); + + (uint256 endPrice, uint256 executionPrice) = _calculatePrices( + startPrice, + duration, + decayPerSecond, + elapsedTime + ); + + deal(address(weth), takerUser, executionPrice); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + startPrice: startPrice, + endPrice: endPrice, + endTime: block.timestamp + duration + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertOrderIsValid(makerAsk); + _assertValidMakerOrder(makerAsk, signature); + + vm.warp(block.timestamp + elapsedTime); + + // Execute taker bid transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(1), takerUser); + + // Taker bid user pays the whole price + assertEq(weth.balanceOf(takerUser), 0, "taker balance incorrect"); + // Maker ask user receives 99.5% of the whole price (0.5% protocol) + uint256 protocolFee = (executionPrice * _standardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP; + assertEq( + weth.balanceOf(makerUser), + _initialWETHBalanceUser + (executionPrice - protocolFee), + "maker balance incorrect" + ); + } + + function testStartPriceTooLow( + uint256 _startPrice, + uint256 _duration, + uint256 _decayPerSecond, + uint256 _elapsedTime + ) public { + (uint256 startPrice, uint256 duration, uint256 decayPerSecond, uint256 elapsedTime) = _fuzzAssumptions( + _startPrice, + _duration, + _decayPerSecond, + _elapsedTime + ); + _setUpUsers(); + _setUpNewStrategy(); + + (uint256 endPrice, uint256 executionPrice) = _calculatePrices( + startPrice, + duration, + decayPerSecond, + elapsedTime + ); + deal(address(weth), takerUser, executionPrice); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + startPrice: startPrice, + endPrice: endPrice, + endTime: block.timestamp + duration + }); + + makerAsk.price = startPrice + 1 wei; + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerBidTooLow( + uint256 _startPrice, + uint256 _duration, + uint256 _decayPerSecond, + uint256 _elapsedTime + ) public { + (uint256 startPrice, uint256 duration, uint256 decayPerSecond, uint256 elapsedTime) = _fuzzAssumptions( + _startPrice, + _duration, + _decayPerSecond, + _elapsedTime + ); + _setUpUsers(); + _setUpNewStrategy(); + + (uint256 endPrice, uint256 executionPrice) = _calculatePrices( + startPrice, + duration, + decayPerSecond, + elapsedTime + ); + deal(address(weth), takerUser, executionPrice); + + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + startPrice: startPrice, + endPrice: endPrice, + endTime: block.timestamp + duration + }); + + takerBid.additionalParameters = abi.encode(executionPrice - 1 wei); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerAsk); + _assertValidMakerOrder(makerAsk, signature); + + vm.expectRevert(BidTooLow.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInactiveStrategy() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + startPrice: 10 ether, + endPrice: 1 ether, + endTime: block.timestamp + 1 hours + }); + + vm.prank(_owner); + looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + _assertOrderIsValid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, STRATEGY_NOT_ACTIVE); + + vm.prank(takerUser); + vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testZeroItemIdsLength() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 0, + numberOfAmounts: 0, + startPrice: 10 ether, + endPrice: 1 ether, + endTime: block.timestamp + 1 hours + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testItemIdsAndAmountsLengthMismatch() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 2, + startPrice: 10 ether, + endPrice: 1 ether, + endTime: block.timestamp + 1 hours + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInvalidAmounts() public { + _setUpUsers(); + _setUpNewStrategy(); + + // 1. Amount = 0 + (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ + numberOfItems: 1, + numberOfAmounts: 1, + startPrice: 10 ether, + endPrice: 1 ether, + endTime: block.timestamp + 1 hours + }); + + makerAsk.amounts[0] = 0; + + // Sign order + bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(AmountInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // 2. ERC721 amount > 1 + makerAsk.amounts[0] = 2; + signature = _signMakerOrder(makerAsk, makerUserPK); + + errorSelector = _assertOrderIsInvalid(makerAsk); + _assertMakerOrderReturnValidationCode(makerAsk, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.prank(takerUser); + vm.expectRevert(AmountInvalid.selector); + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testWrongQuoteType() public { + _setUpNewStrategy(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyDutchAuction.isMakerOrderValid(makerBid, selector); + + assertFalse(orderIsValid); + assertEq(errorSelector, QuoteTypeInvalid.selector); + } + + function testInvalidSelector() public { + _setUpNewStrategy(); + + OrderStructs.Maker memory makerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyDutchAuction.isMakerOrderValid(makerAsk, bytes4(0)); + assertFalse(orderIsValid); + assertEq(errorSelector, FunctionSelectorInvalid.selector); + } + + function _assertOrderIsValid(OrderStructs.Maker memory makerAsk) private { + (bool isValid, bytes4 errorSelector) = strategyDutchAuction.isMakerOrderValid(makerAsk, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + } + + function _assertOrderIsInvalid(OrderStructs.Maker memory makerAsk) private returns (bytes4) { + (bool isValid, bytes4 errorSelector) = strategyDutchAuction.isMakerOrderValid(makerAsk, selector); + assertFalse(isValid); + assertEq(errorSelector, OrderInvalid.selector); + + return errorSelector; + } +} diff --git a/contracts/test/foundry/marketplace/executionStrategies/ItemIdsRangeOrders.t.sol b/contracts/test/foundry/marketplace/executionStrategies/ItemIdsRangeOrders.t.sol new file mode 100644 index 00000000..714595d8 --- /dev/null +++ b/contracts/test/foundry/marketplace/executionStrategies/ItemIdsRangeOrders.t.sol @@ -0,0 +1,482 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries and interfaces +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Shared errors +import { OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; +import { STRATEGY_NOT_ACTIVE, MAKER_ORDER_TEMPORARILY_INVALID_NON_STANDARD_SALE, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE } from "@hypercerts/marketplace/constants/ValidationCodeConstants.sol"; + +// Strategies +import { StrategyItemIdsRange } from "@hypercerts/marketplace/executionStrategies/StrategyItemIdsRange.sol"; + +// Base test +import { ProtocolBase } from "../ProtocolBase.t.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract ItemIdsRangeOrdersTest is ProtocolBase, IStrategyManager { + StrategyItemIdsRange public strategyItemIdsRange; + bytes4 public selector = StrategyItemIdsRange.executeStrategyWithTakerAsk.selector; + + function setUp() public { + _setUp(); + } + + function _setUpNewStrategy() private asPrankedUser(_owner) { + strategyItemIdsRange = new StrategyItemIdsRange(); + _addStrategy(address(strategyItemIdsRange), selector, true); + } + + function _offeredAmounts(uint256 length, uint256 amount) private pure returns (uint256[] memory offeredAmounts) { + offeredAmounts = new uint256[](length); + for (uint256 i; i < length; i++) { + offeredAmounts[i] = amount; + } + } + + function _createMakerBidAndTakerAsk( + uint256 lowerBound, + uint256 upperBound + ) private returns (OrderStructs.Maker memory newMakerBid, OrderStructs.Taker memory newTakerAsk) { + uint256 mid = (lowerBound + upperBound) / 2; + + newMakerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemIds: new uint256[](0), + amounts: new uint256[](0) + }); + + newMakerBid.additionalParameters = abi.encode(lowerBound, upperBound, 3); + + // This way, we can test + // 1. lower bound is 0 + // 2. lower bound is > 0, and 0 is excluded + if (lowerBound > 0) { + mockERC721.mint(takerUser, lowerBound - 1); + } + + mockERC721.mint(takerUser, lowerBound); + mockERC721.mint(takerUser, mid); + mockERC721.mint(takerUser, upperBound); + mockERC721.mint(takerUser, upperBound + 1); + + uint256[] memory takerAskItemIds = new uint256[](3); + takerAskItemIds[0] = lowerBound; + takerAskItemIds[1] = mid; + takerAskItemIds[2] = upperBound; + + newTakerAsk = OrderStructs.Taker({ + recipient: takerUser, + additionalParameters: abi.encode(takerAskItemIds, _offeredAmounts({ length: 3, amount: 1 })) + }); + } + + function testNewStrategy() public { + _setUpNewStrategy(); + _assertStrategyAttributes(address(strategyItemIdsRange), selector, true); + } + + function testTokenIdsRangeERC721(uint256 lowerBound, uint256 upperBound) public { + vm.assume(lowerBound < type(uint128).max && upperBound < type(uint128).max && lowerBound + 1 < upperBound); + + uint256 mid = (lowerBound + upperBound) / 2; + + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk( + lowerBound, + upperBound + ); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Maker user has received the asset + assertEq(mockERC721.ownerOf(lowerBound), makerUser); + assertEq(mockERC721.ownerOf(mid), makerUser); + assertEq(mockERC721.ownerOf(upperBound), makerUser); + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - 1 ether); + // Taker ask user receives 99.5% of the whole price (0.5% protocol fee) + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + 0.995 ether); + } + + function testTokenIdsRangeERC1155(uint256 lowerBound, uint256 upperBound) public { + vm.assume(lowerBound < type(uint128).max && upperBound < type(uint128).max && lowerBound + 1 < upperBound); + + uint256 mid = (lowerBound + upperBound) / 2; + + _setUpUsers(); + _setUpNewStrategy(); + + OrderStructs.Maker memory makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, + collectionType: CollectionType.ERC1155, + orderNonce: 0, + collection: address(mockERC1155), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemIds: new uint256[](0), + amounts: new uint256[](0) + }); + + makerBid.additionalParameters = abi.encode(lowerBound, upperBound, 6); + + mockERC1155.mint(takerUser, lowerBound, 2); + mockERC1155.mint(takerUser, mid, 2); + mockERC1155.mint(takerUser, upperBound, 2); + + uint256[] memory takerAskItemIds = new uint256[](3); + takerAskItemIds[0] = lowerBound; + takerAskItemIds[1] = mid; + takerAskItemIds[2] = upperBound; + + OrderStructs.Taker memory takerAsk = OrderStructs.Taker({ + recipient: takerUser, + additionalParameters: abi.encode(takerAskItemIds, _offeredAmounts({ length: 3, amount: 2 })) + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + // Execute taker ask transaction + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Maker user has received the asset + assertEq(mockERC1155.balanceOf(makerUser, lowerBound), 2); + assertEq(mockERC1155.balanceOf(makerUser, mid), 2); + assertEq(mockERC1155.balanceOf(makerUser, upperBound), 2); + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - 1 ether); + // Taker ask user receives 99.5% of the whole price (0.5% protocol fee) + assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + 0.995 ether); + } + + function testInvalidMakerBidAdditionalParameters() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + makerBid.additionalParameters = abi.encode(6, 9); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + vm.expectRevert(); // EVM revert + strategyItemIdsRange.isMakerOrderValid(makerBid, selector); + + vm.expectRevert(); // EVM revert + orderValidator.checkMakerOrderValidity(makerBid, signature, _EMPTY_MERKLE_TREE); + + vm.expectRevert(); // EVM revert + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testZeroDesiredAmount() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + makerBid.additionalParameters = abi.encode(5, 10, 0); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerBid); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testWrongQuoteType() public { + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, ) = _createMakerBidAndTakerAsk(5, 10); + makerBid.quoteType = QuoteType.Ask; + + (bool isValid, bytes4 errorSelector) = strategyItemIdsRange.isMakerOrderValid(makerBid, selector); + + assertFalse(isValid); + assertEq(errorSelector, QuoteTypeInvalid.selector); + } + + function testTakerAskItemIdsAmountsLengthMismatch() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + uint256[] memory takerAskItemIds = new uint256[](3); + takerAskItemIds[0] = 5; + takerAskItemIds[1] = 7; + takerAskItemIds[2] = 10; + takerAsk.additionalParameters = abi.encode(takerAskItemIds, _offeredAmounts({ length: 4, amount: 1 })); + + vm.prank(takerUser); + vm.expectRevert(OrderInvalid.selector); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskRevertIfAmountIsZeroOrGreaterThanOneERC721() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + uint256[] memory takerAskItemIds = new uint256[](3); + takerAskItemIds[0] = 5; + takerAskItemIds[1] = 7; + takerAskItemIds[2] = 10; + + uint256[] memory invalidAmounts = new uint256[](3); + invalidAmounts[0] = 1; + invalidAmounts[1] = 2; + invalidAmounts[2] = 2; + + takerAsk.additionalParameters = abi.encode(takerAskItemIds, invalidAmounts); + + // The maker bid order is still valid since the error comes from the taker ask amounts + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + // It fails at 2nd item in the array (greater than 1) + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Re-adjust the amounts + invalidAmounts[0] = 0; + invalidAmounts[1] = 1; + invalidAmounts[2] = 1; + + takerAsk.additionalParameters = abi.encode(takerAskItemIds, invalidAmounts); + + // It now fails at 1st item in the array (equal to 0) + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testMakerBidItemIdsLowerBandHigherThanOrEqualToUpperBand() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + // lower band > upper band + makerBid.additionalParameters = abi.encode(5, 4, 1); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + bytes4 errorSelector = _assertOrderIsInvalid(makerBid); + _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); + + vm.expectRevert(errorSelector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // lower band == upper band + makerBid.additionalParameters = abi.encode(5, 5, 1); + + // Sign order + signature = _signMakerOrder(makerBid, makerUserPK); + + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskDuplicatedItemIds() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + uint256[] memory invalidItemIds = new uint256[](3); + invalidItemIds[0] = 5; + invalidItemIds[1] = 7; + invalidItemIds[2] = 7; + + takerAsk.additionalParameters = abi.encode(invalidItemIds, _offeredAmounts({ length: 3, amount: 1 })); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskUnsortedItemIds() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + uint256[] memory invalidItemIds = new uint256[](3); + invalidItemIds[0] = 5; + invalidItemIds[1] = 10; + invalidItemIds[2] = 7; + + takerAsk.additionalParameters = abi.encode(invalidItemIds, _offeredAmounts({ length: 3, amount: 1 })); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskOfferedAmountNotEqualToDesiredAmount() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = 5; + itemIds[1] = 10; + + takerAsk.additionalParameters = abi.encode(itemIds, _offeredAmounts({ length: 2, amount: 1 })); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testTakerAskOfferedItemIdTooLow() public { + _testTakerAskOfferedItemIdOutOfRange(3, 4); + } + + function testTakerAskOfferedItemIdTooHigh() public { + _testTakerAskOfferedItemIdOutOfRange(11, 12); + } + + function _testTakerAskOfferedItemIdOutOfRange(uint256 itemIdOne, uint256 itemIdTwo) private { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + uint256[] memory itemIds = new uint256[](2); + itemIds[0] = itemIdOne; + itemIds[1] = itemIdTwo; + + takerAsk.additionalParameters = abi.encode(itemIds, _offeredAmounts({ length: 2, amount: 1 })); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + _assertOrderIsValid(makerBid); + _assertValidMakerOrder(makerBid, signature); + + vm.expectRevert(OrderInvalid.selector); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInactiveStrategy() public { + _setUpUsers(); + _setUpNewStrategy(); + (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk(5, 10); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + vm.prank(_owner); + looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); + + // Valid, taker struct validation only happens during execution + _assertOrderIsValid(makerBid); + // but... the OrderValidator catches this + _assertMakerOrderReturnValidationCode(makerBid, signature, STRATEGY_NOT_ACTIVE); + + vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + } + + function testInvalidSelector() public { + _setUpNewStrategy(); + + OrderStructs.Maker memory makerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 2, + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: 1 ether, + itemId: 0 + }); + + (bool orderIsValid, bytes4 errorSelector) = strategyItemIdsRange.isMakerOrderValid(makerBid, bytes4(0)); + assertFalse(orderIsValid); + assertEq(errorSelector, FunctionSelectorInvalid.selector); + } + + function _assertOrderIsValid(OrderStructs.Maker memory makerBid) private { + (bool isValid, bytes4 errorSelector) = strategyItemIdsRange.isMakerOrderValid(makerBid, selector); + assertTrue(isValid); + assertEq(errorSelector, _EMPTY_BYTES4); + } + + function _assertOrderIsInvalid(OrderStructs.Maker memory makerBid) private returns (bytes4) { + (bool isValid, bytes4 errorSelector) = strategyItemIdsRange.isMakerOrderValid(makerBid, selector); + assertFalse(isValid); + assertEq(errorSelector, OrderInvalid.selector); + return errorSelector; + } +} diff --git a/contracts/test/foundry/marketplace/executionStrategies/MultiFillCollectionOrders.t.sol b/contracts/test/foundry/marketplace/executionStrategies/MultiFillCollectionOrders.t.sol new file mode 100644 index 00000000..4e8f9f37 --- /dev/null +++ b/contracts/test/foundry/marketplace/executionStrategies/MultiFillCollectionOrders.t.sol @@ -0,0 +1,194 @@ +/// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Interfaces +import { IExecutionManager } from "@hypercerts/marketplace/interfaces/IExecutionManager.sol"; +import { IStrategyManager } from "@hypercerts/marketplace/interfaces/IStrategyManager.sol"; + +// Mock files and other tests +import { StrategyTestMultiFillCollectionOrder } from "../utils/StrategyTestMultiFillCollectionOrder.sol"; +import { ProtocolBase } from "../ProtocolBase.t.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract MultiFillCollectionOrdersTest is ProtocolBase, IStrategyManager { + uint256 private constant price = 1 ether; // Fixed price of sale + + bytes4 public selector = StrategyTestMultiFillCollectionOrder.executeStrategyWithTakerAsk.selector; + + StrategyTestMultiFillCollectionOrder public strategyMultiFillCollectionOrder; + + function setUp() public { + _setUp(); + } + + function _setUpNewStrategy() private asPrankedUser(_owner) { + strategyMultiFillCollectionOrder = new StrategyTestMultiFillCollectionOrder(address(looksRareProtocol)); + _addStrategy(address(strategyMultiFillCollectionOrder), selector, true); + } + + function testNewStrategy() public { + _setUpNewStrategy(); + _assertStrategyAttributes(address(strategyMultiFillCollectionOrder), selector, true); + } + + /** + * Maker bid user wants to buy 4 ERC721 items in a collection. The order can be filled in multiple parts. + * First takerUser sells 1 item. + * Second takerUser sells 3 items. + */ + function testMultiFill() public { + _setUpUsers(); + _setUpNewStrategy(); + + uint256 amountsToFill = 4; + + uint256[] memory itemIds = new uint256[](0); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountsToFill; + + OrderStructs.Maker memory makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, // Multi-fill bid offer + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemIds: itemIds, + amounts: amounts + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + itemIds = new uint256[](1); + amounts = new uint256[](1); + itemIds[0] = 0; + amounts[0] = 1; + + mockERC721.mint(takerUser, itemIds[0]); + + // Prepare the taker ask + OrderStructs.Taker memory takerAsk = OrderStructs.Taker(takerUser, abi.encode(itemIds, amounts)); + + // Execute the first taker ask transaction by the first taker user + vm.prank(takerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the asset + assertEq(mockERC721.ownerOf(0), makerUser); + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - price); + // Taker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + weth.balanceOf(takerUser), + _initialWETHBalanceUser + (price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP + ); + // Verify the nonce is not marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), _computeOrderHash(makerBid)); + + // Second taker user actions + address secondTakerUser = address(420); + _setUpUser(secondTakerUser); + + itemIds = new uint256[](3); + amounts = new uint256[](3); + + itemIds[0] = 1; // tokenId = 1 + itemIds[1] = 2; // tokenId = 2 + itemIds[2] = 3; // tokenId = 3 + amounts[0] = 1; + amounts[1] = 1; + amounts[2] = 1; + + mockERC721.batchMint(secondTakerUser, itemIds); + + // Prepare the taker ask + takerAsk = OrderStructs.Taker(secondTakerUser, abi.encode(itemIds, amounts)); + + // Execute a second taker ask transaction from the second taker user + vm.prank(secondTakerUser); + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, _EMPTY_MERKLE_TREE, _EMPTY_AFFILIATE); + + // Taker user has received the 3 assets + assertEq(mockERC721.ownerOf(1), makerUser); + assertEq(mockERC721.ownerOf(2), makerUser); + assertEq(mockERC721.ownerOf(3), makerUser); + + // Maker bid user pays the whole price + assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - 4 * price); + // Taker ask user receives 99.5% of the whole price (0.5% protocol) + assertEq( + weth.balanceOf(secondTakerUser), + _initialWETHBalanceUser + + 3 * + ((price * _sellerProceedBpWithStandardProtocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BP) + ); + // Verify the nonce is now marked as executed + assertEq(looksRareProtocol.userOrderNonce(makerUser, makerBid.orderNonce), MAGIC_VALUE_ORDER_NONCE_EXECUTED); + } + + function testInactiveStrategy() public { + _setUpUsers(); + _setUpNewStrategy(); + + uint256 amountsToFill = 4; + + uint256[] memory itemIds = new uint256[](0); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountsToFill; + + OrderStructs.Maker memory makerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: 1, // Multi-fill bid offer + collectionType: CollectionType.ERC721, + orderNonce: 0, + collection: address(mockERC721), + currency: address(weth), + signer: makerUser, + price: price, + itemIds: itemIds, + amounts: amounts + }); + + // Sign order + bytes memory signature = _signMakerOrder(makerBid, makerUserPK); + + vm.prank(_owner); + looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); + + { + itemIds = new uint256[](1); + amounts = new uint256[](1); + itemIds[0] = 0; + amounts[0] = 1; + + mockERC721.mint(takerUser, itemIds[0]); + + // It should revert if strategy is not available + vm.prank(takerUser); + vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); + looksRareProtocol.executeTakerAsk( + _genericTakerOrder(), + makerBid, + signature, + _EMPTY_MERKLE_TREE, + _EMPTY_AFFILIATE + ); + } + } +} diff --git a/contracts/test/foundry/marketplace/utils/BytesLib.sol b/contracts/test/foundry/marketplace/utils/BytesLib.sol new file mode 100644 index 00000000..d4140def --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/BytesLib.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * @notice We only copied the `slice` function from the original https://github.com/GNSPS/solidity-bytes-utils + * as that's the only function needed. + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you slice bytes arrays in memory. + */ +library BytesLib { + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } +} diff --git a/contracts/test/foundry/marketplace/utils/EIP712MerkleTree.sol b/contracts/test/foundry/marketplace/utils/EIP712MerkleTree.sol new file mode 100644 index 00000000..a17c3275 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/EIP712MerkleTree.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Forge test +import { Test } from "forge-std/Test.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Core contracts +import { LooksRareProtocol } from "@hypercerts/marketplace/LooksRareProtocol.sol"; + +// Utils +import { MerkleWithPosition } from "./MerkleWithPosition.sol"; +import { MathLib } from "./MathLib.sol"; + +// Constants +import { MAX_CALLDATA_PROOF_LENGTH } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +contract EIP712MerkleTree is Test { + using OrderStructs for OrderStructs.Maker; + + LooksRareProtocol private looksRareProtocol; + + constructor(LooksRareProtocol _looksRareProtocol) { + looksRareProtocol = _looksRareProtocol; + } + + function sign( + uint256 privateKey, + OrderStructs.Maker[] memory makerOrders, + uint256 makerOrderIndex + ) external returns (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) { + uint256 bidCount = makerOrders.length; + uint256 treeHeight = MathLib.log2(bidCount); + if (2 ** treeHeight != bidCount || treeHeight == 0) { + treeHeight += 1; + } + bytes32 batchOrderTypehash = _getBatchOrderTypehash(treeHeight); + uint256 leafCount = 2 ** treeHeight; + OrderStructs.MerkleTreeNode[] memory leaves = new OrderStructs.MerkleTreeNode[](leafCount); + + for (uint256 i; i < bidCount; i++) { + leaves[i] = OrderStructs.MerkleTreeNode({ + value: makerOrders[i].hash(), + position: i % 2 == 0 + ? OrderStructs.MerkleTreeNodePosition.Left + : OrderStructs.MerkleTreeNodePosition.Right + }); + } + + bytes32 emptyMakerOrderHash = _emptyMakerOrderHash(); + for (uint256 i = bidCount; i < leafCount; i++) { + leaves[i] = OrderStructs.MerkleTreeNode({ + value: emptyMakerOrderHash, + position: i % 2 == 0 + ? OrderStructs.MerkleTreeNodePosition.Left + : OrderStructs.MerkleTreeNodePosition.Right + }); + } + + MerkleWithPosition merkle = new MerkleWithPosition(); + OrderStructs.MerkleTreeNode[] memory proof = merkle.getProof(leaves, makerOrderIndex); + bytes32 root = merkle.getRoot(leaves); + + signature = _sign(privateKey, batchOrderTypehash, root); + merkleTree = OrderStructs.MerkleTree({ root: root, proof: proof }); + } + + function _emptyMakerOrderHash() private pure returns (bytes32 makerOrderHash) { + OrderStructs.Maker memory makerOrder; + makerOrderHash = makerOrder.hash(); + } + + function _sign( + uint256 privateKey, + bytes32 batchOrderTypehash, + bytes32 root + ) private view returns (bytes memory signature) { + bytes32 digest = keccak256(abi.encode(batchOrderTypehash, root)); + + bytes32 domainSeparator = looksRareProtocol.domainSeparator(); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + privateKey, + keccak256(abi.encodePacked("\x19\x01", domainSeparator, digest)) + ); + + signature = abi.encodePacked(r, s, v); + } + + function _getBatchOrderTypehash(uint256 treeHeight) private pure returns (bytes32 batchOrderTypehash) { + if (treeHeight == 1) { + batchOrderTypehash = hex"9661287f7a4aa4867db46a2453ee15bebac4e8fc25667a58718da658f15de643"; + } else if (treeHeight == 2) { + batchOrderTypehash = hex"a54ab330ea9e1dfccee2b86f3666989e7fbd479704416c757c8de8e820142a08"; + } else if (treeHeight == 3) { + batchOrderTypehash = hex"93390f5d45ede9dea305f16aec86b2472af4f823851637f1b7019ad0775cea49"; + } else if (treeHeight == 4) { + batchOrderTypehash = hex"9dda2c8358da895e43d574bb15954ce5727b22e923a2d8f28261f297bce42f0b"; + } else if (treeHeight == 5) { + batchOrderTypehash = hex"92dc717124e161262f9d10c7079e7d54dc51271893fba54aa4a0f270fecdcc98"; + } else if (treeHeight == 6) { + batchOrderTypehash = hex"ce02aee5a7a35d40d974463c4c6e5534954fb07a7e7bc966fee268a15337bfd8"; + } else if (treeHeight == 7) { + batchOrderTypehash = hex"f7a65efd167a18f7091b2bb929d687dd94503cf0a43620487055ed7d6b727559"; + } else if (treeHeight == 8) { + batchOrderTypehash = hex"def24acacad1318b664520f7c10e8bc6d1e7f6f6f7c8b031e70624ceb42266a6"; + } else if (treeHeight == 9) { + batchOrderTypehash = hex"4cb4080dc4e7bae88b4dc4307ad5117fa4f26195998a1b5f40368809d7f4c7f2"; + } else if (treeHeight == 10) { + batchOrderTypehash = hex"f8b1f864164d8d6e0b45f1399bd711223117a4ab0b057a9c2d7779e86a7c88db"; + } else if (treeHeight == 11) { + batchOrderTypehash = hex"4787f505db237e03a7193c312d5159add8a5705278e1c7dcf92ab87126cbe490"; + } else if (treeHeight == 12) { + batchOrderTypehash = hex"7a6517e5a16c56b29947b57b748aa91736987376e1a366d948e7a802a9df3431"; + } else if (treeHeight == 13) { + batchOrderTypehash = hex"35806d347e9929042ce209d143da48f100f0ff0cbdb1fde68cf13af8059d79df"; + } + } +} diff --git a/contracts/test/foundry/marketplace/utils/ERC1271Wallet.sol b/contracts/test/foundry/marketplace/utils/ERC1271Wallet.sol new file mode 100644 index 00000000..ee76287f --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/ERC1271Wallet.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ERC1271WalletMock } from "openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol"; + +contract ERC1271Wallet is ERC1271WalletMock { + constructor(address originalOwner) ERC1271WalletMock(originalOwner) {} + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external pure returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} diff --git a/contracts/test/foundry/marketplace/utils/GasGriefer.sol b/contracts/test/foundry/marketplace/utils/GasGriefer.sol new file mode 100644 index 00000000..2589681c --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/GasGriefer.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +contract GasGriefer { + receive() external payable { + uint256 count; + while (true) { + count += 1; + } + } + + function isValidSignature(bytes32, bytes memory) external pure returns (bytes4 magicValue) { + magicValue = this.isValidSignature.selector; + } +} diff --git a/contracts/test/foundry/marketplace/utils/MaliciousERC1271Wallet.sol b/contracts/test/foundry/marketplace/utils/MaliciousERC1271Wallet.sol new file mode 100644 index 00000000..965149b3 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MaliciousERC1271Wallet.sol @@ -0,0 +1,68 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ILooksRareProtocol } from "@hypercerts/marketplace/interfaces/ILooksRareProtocol.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +abstract contract MaliciousERC1271Wallet { + enum FunctionToReenter { + None, + ExecuteTakerAsk, + ExecuteTakerBid, + ExecuteMultipleTakerBids + } + + ILooksRareProtocol internal immutable looksRareProtocol; + FunctionToReenter internal functionToReenter; + + constructor(address _looksRareProtocol) { + looksRareProtocol = ILooksRareProtocol(_looksRareProtocol); + } + + function setFunctionToReenter(FunctionToReenter _functionToReenter) external { + functionToReenter = _functionToReenter; + } + + function isValidSignature(bytes32, bytes calldata) external virtual returns (bytes4 magicValue) { + magicValue = this.isValidSignature.selector; + } + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) external virtual returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external virtual returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + function _executeTakerAsk(bytes memory signature) internal { + OrderStructs.Taker memory takerAsk; + OrderStructs.Maker memory makerBid; + OrderStructs.MerkleTree memory merkleTree; + + looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, merkleTree, address(this)); + } + + function _executeTakerBid(bytes memory signature) internal { + OrderStructs.Taker memory takerBid; + OrderStructs.Maker memory makerAsk; + OrderStructs.MerkleTree memory merkleTree; + + looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, merkleTree, address(this)); + } + + function _executeMultipleTakerBids() internal { + OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](2); + OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](2); + bytes[] memory signatures = new bytes[](2); + OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](2); + + looksRareProtocol.executeMultipleTakerBids(takerBids, makerAsks, signatures, merkleTrees, address(this), false); + } +} diff --git a/contracts/test/foundry/marketplace/utils/MaliciousIsValidSignatureERC1271Wallet.sol b/contracts/test/foundry/marketplace/utils/MaliciousIsValidSignatureERC1271Wallet.sol new file mode 100644 index 00000000..d8c557ff --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MaliciousIsValidSignatureERC1271Wallet.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { MaliciousERC1271Wallet } from "./MaliciousERC1271Wallet.sol"; + +contract MaliciousIsValidSignatureERC1271Wallet is MaliciousERC1271Wallet { + constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} + + function isValidSignature(bytes32, bytes calldata signature) external override returns (bytes4 magicValue) { + if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { + _executeTakerAsk(signature); + } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { + _executeTakerBid(signature); + } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { + _executeMultipleTakerBids(); + } + + magicValue = this.isValidSignature.selector; + } +} diff --git a/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol b/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol new file mode 100644 index 00000000..4881535a --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { MaliciousERC1271Wallet } from "./MaliciousERC1271Wallet.sol"; + +contract MaliciousOnERC1155ReceivedERC1271Wallet is MaliciousERC1271Wallet { + constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} + + function onERC1155Received(address, address, uint256, uint256, bytes memory) external override returns (bytes4) { + if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { + _executeTakerAsk(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { + _executeTakerBid(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { + _executeMultipleTakerBids(); + } + + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external override returns (bytes4) { + if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { + _executeTakerAsk(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { + _executeTakerBid(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { + _executeMultipleTakerBids(); + } + + return this.onERC1155BatchReceived.selector; + } +} diff --git a/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol b/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol new file mode 100644 index 00000000..544aeb28 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { MaliciousERC1271Wallet } from "./MaliciousERC1271Wallet.sol"; + +contract MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet is MaliciousERC1271Wallet { + uint256 private isValidSignatureEnterCount; + + constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} + + function onERC1155Received(address, address, uint256, uint256, bytes memory) external override returns (bytes4) { + if (++isValidSignatureEnterCount == 3) { + if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { + _executeTakerAsk(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { + _executeTakerBid(new bytes(0)); + } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { + _executeMultipleTakerBids(); + } + } + + return this.onERC1155Received.selector; + } +} diff --git a/contracts/test/foundry/marketplace/utils/MathLib.sol b/contracts/test/foundry/marketplace/utils/MathLib.sol new file mode 100644 index 00000000..f0897c30 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MathLib.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @author OpenZeppelin (last updated v4.8.0) (utils/math/Math.sol) + * @dev Standard math utilities missing in the Solidity language. + */ +library MathLib { + /** + * @dev Return the log in base 2, rounded down, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } +} diff --git a/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol b/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol new file mode 100644 index 00000000..38628707 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +/** + * @dev Modified from MurkyBase to add each node's position after hashing. + * hashLeafPair does not sort the nodes to match EIP-712. + */ +contract MerkleWithPosition { + /******************** + * PROOF GENERATION * + ********************/ + + function getRoot(OrderStructs.MerkleTreeNode[] memory data) public pure returns (bytes32) { + require(data.length > 1, "won't generate root for single leaf"); + while (data.length > 1) { + data = hashLevel(data); + } + return data[0].value; + } + + function getProof( + OrderStructs.MerkleTreeNode[] memory data, + uint256 node + ) public pure returns (OrderStructs.MerkleTreeNode[] memory result) { + require(data.length > 1, "won't generate proof for single leaf"); + // The size of the proof is equal to the ceiling of log2(numLeaves) + result = new OrderStructs.MerkleTreeNode[](log2ceilBitMagic(data.length)); + uint256 pos = 0; + + // Two overflow risks: node, pos + // node: max array size is 2**256-1. Largest index in the array will be 1 less than that. Also, + // for dynamic arrays, size is limited to 2**64-1 + // pos: pos is bounded by log2(data.length), which should be less than type(uint256).max + while (data.length > 1) { + unchecked { + if (node & 0x1 == 1) { + result[pos] = data[node - 1]; + } else if (node + 1 == data.length) { + result[pos] = OrderStructs.MerkleTreeNode({ + value: bytes32(0), + position: OrderStructs.MerkleTreeNodePosition.Left + }); + } else { + result[pos] = data[node + 1]; + } + ++pos; + node /= 2; + } + data = hashLevel(data); + } + return result; + } + + ///@dev function is private to prevent unsafe data from being passed + function hashLevel( + OrderStructs.MerkleTreeNode[] memory data + ) private pure returns (OrderStructs.MerkleTreeNode[] memory result) { + // Function is private, and all internal callers check that data.length >=2. + // Underflow is not possible as lowest possible value for data/result index is 1 + // overflow should be safe as length is / 2 always. + unchecked { + uint256 length = data.length; + if (length & 0x1 == 1) { + result = new OrderStructs.MerkleTreeNode[](length / 2 + 1); + bytes32 hashed = hashLeafPairs(data[length - 1].value, bytes32(0)); + result[result.length - 1] = OrderStructs.MerkleTreeNode({ + value: hashed, + position: OrderStructs.MerkleTreeNodePosition.Left + }); + } else { + result = new OrderStructs.MerkleTreeNode[](length / 2); + } + // pos is upper bounded by data.length / 2, so safe even if array is at max size + uint256 pos = 0; + bool nextIsLeft = true; + for (uint256 i = 0; i < length - 1; i += 2) { + bytes32 hashed = hashLeafPairs(data[i].value, data[i + 1].value); + result[pos] = OrderStructs.MerkleTreeNode({ + value: hashed, + position: nextIsLeft + ? OrderStructs.MerkleTreeNodePosition.Left + : OrderStructs.MerkleTreeNodePosition.Right + }); + nextIsLeft = !nextIsLeft; + ++pos; + } + } + return result; + } + + /****************** + * MATH "LIBRARY" * + ******************/ + + /// Original bitmagic adapted from https://github.com/paulrberg/prb-math/blob/main/contracts/PRBMath.sol + /// @dev Note that x assumed > 1 + function log2ceilBitMagic(uint256 x) public pure returns (uint256) { + if (x <= 1) { + return 0; + } + uint256 msb = 0; + uint256 _x = x; + if (x >= 2 ** 128) { + x >>= 128; + msb += 128; + } + if (x >= 2 ** 64) { + x >>= 64; + msb += 64; + } + if (x >= 2 ** 32) { + x >>= 32; + msb += 32; + } + if (x >= 2 ** 16) { + x >>= 16; + msb += 16; + } + if (x >= 2 ** 8) { + x >>= 8; + msb += 8; + } + if (x >= 2 ** 4) { + x >>= 4; + msb += 4; + } + if (x >= 2 ** 2) { + x >>= 2; + msb += 2; + } + if (x >= 2 ** 1) { + msb += 1; + } + + uint256 lsb = (~_x + 1) & _x; + if ((lsb == _x) && (msb > 0)) { + return msb; + } else { + return msb + 1; + } + } + + function hashLeafPairs(bytes32 left, bytes32 right) public pure returns (bytes32 _hash) { + assembly { + mstore(0x0, left) + mstore(0x20, right) + _hash := keccak256(0x0, 0x40) + } + } +} diff --git a/contracts/test/foundry/marketplace/utils/MockOrderGenerator.sol b/contracts/test/foundry/marketplace/utils/MockOrderGenerator.sol new file mode 100644 index 00000000..7f6fd135 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/MockOrderGenerator.sol @@ -0,0 +1,147 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Generic interfaces +import { IERC165 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC165.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Other helpers +import { ProtocolHelpers } from "../utils/ProtocolHelpers.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract MockOrderGenerator is ProtocolHelpers { + function _createMockMakerAskAndTakerBid( + address collection + ) internal view returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { + CollectionType collectionType = _getCollectionType(collection); + + newMakerAsk = _createSingleItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: collectionType, + orderNonce: 0, + collection: collection, + currency: ETH, + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + newTakerBid = OrderStructs.Taker(takerUser, abi.encode()); + } + + function _createMockMakerBidAndTakerAsk( + address collection, + address currency + ) internal view returns (OrderStructs.Maker memory newMakerBid, OrderStructs.Taker memory newTakerAsk) { + CollectionType collectionType = _getCollectionType(collection); + + newMakerBid = _createSingleItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: collectionType, + orderNonce: 0, + collection: collection, + currency: currency, + signer: makerUser, + price: 1 ether, + itemId: 420 + }); + + newTakerAsk = OrderStructs.Taker(takerUser, abi.encode()); + } + + function _createMockMakerAskAndTakerBidWithBundle( + address collection, + uint256 numberTokens + ) internal view returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { + CollectionType collectionType = _getCollectionType(collection); + + (uint256[] memory itemIds, uint256[] memory amounts) = _setBundleItemIdsAndAmounts( + collectionType, + numberTokens + ); + + newMakerAsk = _createMultiItemMakerOrder({ + quoteType: QuoteType.Ask, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: collectionType, + orderNonce: 0, + collection: collection, + currency: ETH, + signer: makerUser, + price: 1 ether, + itemIds: itemIds, + amounts: amounts + }); + + newTakerBid = OrderStructs.Taker(takerUser, abi.encode()); + } + + function _createMockMakerBidAndTakerAskWithBundle( + address collection, + address currency, + uint256 numberTokens + ) internal view returns (OrderStructs.Maker memory newMakerBid, OrderStructs.Taker memory newTakerAsk) { + CollectionType collectionType = _getCollectionType(collection); + + (uint256[] memory itemIds, uint256[] memory amounts) = _setBundleItemIdsAndAmounts( + collectionType, + numberTokens + ); + + newMakerBid = _createMultiItemMakerOrder({ + quoteType: QuoteType.Bid, + globalNonce: 0, + subsetNonce: 0, + strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, + collectionType: collectionType, + orderNonce: 0, + collection: collection, + currency: currency, + signer: makerUser, + price: 1 ether, + itemIds: itemIds, + amounts: amounts + }); + + newTakerAsk = OrderStructs.Taker(takerUser, abi.encode()); + } + + function _getCollectionType(address collection) private view returns (CollectionType collectionType) { + collectionType = CollectionType.ERC721; + + // If ERC1155, adjust the collection type + if (IERC165(collection).supportsInterface(0xd9b67a26)) { + collectionType = CollectionType.ERC1155; + } + } + + function _setBundleItemIdsAndAmounts( + CollectionType collectionType, + uint256 numberTokens + ) private pure returns (uint256[] memory itemIds, uint256[] memory amounts) { + itemIds = new uint256[](numberTokens); + amounts = new uint256[](numberTokens); + + for (uint256 i; i < itemIds.length; i++) { + itemIds[i] = i; + if (collectionType != CollectionType.ERC1155) { + amounts[i] = 1; + } else { + amounts[i] = 1 + i; + } + } + } +} diff --git a/contracts/test/foundry/marketplace/utils/ProtocolHelpers.sol b/contracts/test/foundry/marketplace/utils/ProtocolHelpers.sol new file mode 100644 index 00000000..066bf9b3 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/ProtocolHelpers.sol @@ -0,0 +1,109 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Dependencies +import { BatchOrderTypehashRegistry } from "@hypercerts/marketplace/BatchOrderTypehashRegistry.sol"; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Other tests +import { TestHelpers } from "./TestHelpers.sol"; +import { TestParameters } from "./TestParameters.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; +import { QuoteType } from "@hypercerts/marketplace/enums/QuoteType.sol"; + +contract ProtocolHelpers is TestHelpers, TestParameters { + using OrderStructs for OrderStructs.Maker; + + bytes32 internal _domainSeparator; + + receive() external payable {} + + function _createSingleItemMakerOrder( + QuoteType quoteType, + uint256 globalNonce, + uint256 subsetNonce, + uint256 strategyId, + CollectionType collectionType, + uint256 orderNonce, + address collection, + address currency, + address signer, + uint256 price, + uint256 itemId + ) internal view returns (OrderStructs.Maker memory makerOrder) { + uint256[] memory itemIds = new uint256[](1); + itemIds[0] = itemId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + + makerOrder = OrderStructs.Maker({ + quoteType: quoteType, + globalNonce: globalNonce, + subsetNonce: subsetNonce, + orderNonce: orderNonce, + strategyId: strategyId, + collectionType: collectionType, + collection: collection, + currency: currency, + signer: signer, + startTime: block.timestamp, + endTime: block.timestamp + 1, + price: price, + itemIds: itemIds, + amounts: amounts, + additionalParameters: abi.encode() + }); + } + + function _createMultiItemMakerOrder( + QuoteType quoteType, + uint256 globalNonce, + uint256 subsetNonce, + uint256 strategyId, + CollectionType collectionType, + uint256 orderNonce, + address collection, + address currency, + address signer, + uint256 price, + uint256[] memory itemIds, + uint256[] memory amounts + ) internal view returns (OrderStructs.Maker memory newMakerBid) { + newMakerBid = OrderStructs.Maker({ + quoteType: quoteType, + globalNonce: globalNonce, + subsetNonce: subsetNonce, + orderNonce: orderNonce, + strategyId: strategyId, + collectionType: collectionType, + collection: collection, + currency: currency, + signer: signer, + startTime: block.timestamp, + endTime: block.timestamp + 1, + price: price, + itemIds: itemIds, + amounts: amounts, + additionalParameters: abi.encode() + }); + } + + function _signMakerOrder(OrderStructs.Maker memory maker, uint256 signerKey) internal view returns (bytes memory) { + bytes32 orderHash = _computeOrderHash(maker); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + signerKey, + keccak256(abi.encodePacked("\x19\x01", _domainSeparator, orderHash)) + ); + + return abi.encodePacked(r, s, v); + } + + function _computeOrderHash(OrderStructs.Maker memory maker) internal pure returns (bytes32) { + return maker.hash(); + } +} diff --git a/contracts/test/foundry/marketplace/utils/StrategyTestMultiFillCollectionOrder.sol b/contracts/test/foundry/marketplace/utils/StrategyTestMultiFillCollectionOrder.sol new file mode 100644 index 00000000..ea76b14c --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/StrategyTestMultiFillCollectionOrder.sol @@ -0,0 +1,77 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// Libraries +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +// Custom errors +import { OrderInvalid } from "@hypercerts/marketplace/errors/SharedErrors.sol"; + +// Base strategy contracts +import { BaseStrategy, IStrategy } from "@hypercerts/marketplace/executionStrategies/BaseStrategy.sol"; + +// Enums +import { CollectionType } from "@hypercerts/marketplace/enums/CollectionType.sol"; + +contract StrategyTestMultiFillCollectionOrder is BaseStrategy { + using OrderStructs for OrderStructs.Maker; + + // Address of the protocol + address public immutable LOOKSRARE_PROTOCOL; + + // Tracks historical fills + mapping(bytes32 => uint256) internal countItemsFilledForOrderHash; + + /** + * @notice Constructor + * @param _looksRareProtocol Address of the LooksRare protocol + */ + constructor(address _looksRareProtocol) { + LOOKSRARE_PROTOCOL = _looksRareProtocol; + } + + /** + * @notice Execute collection strategy with taker ask order + * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) + * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) + */ + function executeStrategyWithTakerAsk( + OrderStructs.Taker calldata takerAsk, + OrderStructs.Maker calldata makerBid + ) external returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) { + if (msg.sender != LOOKSRARE_PROTOCOL) revert OrderInvalid(); + // Only available for ERC721 + if (makerBid.collectionType != CollectionType.ERC721) revert OrderInvalid(); + + bytes32 orderHash = makerBid.hash(); + uint256 countItemsFilled = countItemsFilledForOrderHash[orderHash]; + uint256 countItemsFillable = makerBid.amounts[0]; + + price = makerBid.price; + (itemIds, amounts) = abi.decode(takerAsk.additionalParameters, (uint256[], uint256[])); + uint256 countItemsToFill = amounts.length; + + if ( + countItemsToFill == 0 || + makerBid.amounts.length != 1 || + itemIds.length != countItemsToFill || + countItemsFillable < countItemsToFill + countItemsFilled + ) revert OrderInvalid(); + + price *= countItemsToFill; + + if (countItemsToFill + countItemsFilled == countItemsFillable) { + delete countItemsFilledForOrderHash[orderHash]; + isNonceInvalidated = true; + } else { + countItemsFilledForOrderHash[orderHash] += countItemsToFill; + } + } + + function isMakerOrderValid( + OrderStructs.Maker calldata, + bytes4 + ) external view override returns (bool isValid, bytes4 errorSelector) { + // + } +} diff --git a/contracts/test/foundry/marketplace/utils/TestHelpers.sol b/contracts/test/foundry/marketplace/utils/TestHelpers.sol new file mode 100644 index 00000000..32f9b9b6 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/TestHelpers.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { Test } from "forge-std/Test.sol"; +import { BytesLib } from "./BytesLib.sol"; + +abstract contract TestHelpers is Test { + using BytesLib for bytes; + + modifier asPrankedUser(address user) { + vm.startPrank(user); + _; + vm.stopPrank(); + } + + /** + * @dev Transforms a standard signature into an EIP2098 compliant signature + * @param signature The secp256k1 65-bytes signature + * @return eip2098Signature The 64-bytes EIP2098 compliant signature + */ + function _eip2098Signature(bytes memory signature) internal pure returns (bytes memory eip2098Signature) { + eip2098Signature = signature.slice(0, 64); + uint8 parityBit = uint8(eip2098Signature[32]) | ((uint8(signature[64]) % 27) << 7); + eip2098Signature[32] = bytes1(parityBit); + } +} diff --git a/contracts/test/foundry/marketplace/utils/TestParameters.sol b/contracts/test/foundry/marketplace/utils/TestParameters.sol new file mode 100644 index 00000000..cb073095 --- /dev/null +++ b/contracts/test/foundry/marketplace/utils/TestParameters.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { Test } from "forge-std/Test.sol"; +import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol"; + +abstract contract TestParameters is Test { + // Empty constants + OrderStructs.MerkleTree internal _EMPTY_MERKLE_TREE; + bytes4 internal constant _EMPTY_BYTES4 = bytes4(0); + address internal constant _EMPTY_AFFILIATE = address(0); + bytes32 public constant MAGIC_VALUE_ORDER_NONCE_EXECUTED = keccak256("ORDER_NONCE_EXECUTED"); + + // Addresses + address internal constant _owner = address(42); + address internal constant _sender = address(88); + address internal constant _recipient = address(90); + address internal constant _transferrer = address(100); + address internal constant _royaltyRecipient = address(22); + address internal constant _affiliate = address(2); + + // Currencies + address internal constant ETH = address(0); + address internal constant WETH_MAINNET = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + // Generic fee parameters + uint16 internal constant _standardProtocolFeeBp = uint16(50); + uint16 internal constant _minTotalFeeBp = uint16(50); + uint16 internal constant _maxProtocolFeeBp = uint16(200); + uint16 internal constant _standardRoyaltyFee = uint16(0); + + uint256 internal constant _sellerProceedBpWithStandardProtocolFeeBp = 9_950; + + // Public/Private keys for maker/taker user + uint256 internal constant makerUserPK = 1; + uint256 internal constant takerUserPK = 2; + // it is equal to vm.addr(makerUserPK) + address internal constant makerUser = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf; + // it is equal to vm.addr(takerUserPK) + address internal constant takerUser = 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF; + + // Initial balances + // @dev The balances are on purpose different across users to make sure the tests are properly checking the assertion + uint256 internal constant _initialETHBalanceUser = 100 ether; + uint256 internal constant _initialWETHBalanceUser = 10 ether; + uint256 internal constant _initialETHBalanceRoyaltyRecipient = 10 ether; + uint256 internal constant _initialWETHBalanceRoyaltyRecipient = 25 ether; + uint256 internal constant _initialETHBalanceOwner = 50 ether; + uint256 internal constant _initialWETHBalanceOwner = 15 ether; + uint256 internal constant _initialETHBalanceAffiliate = 30 ether; + uint256 internal constant _initialWETHBalanceAffiliate = 12 ether; + + // Chainlink ETH/USD price feed (Ethereum mainnet) + address internal constant CHAINLINK_ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; + + // Strategy id + uint256 internal constant STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY = 0; +} diff --git a/contracts/test/mock/MockChainlinkAggregator.sol b/contracts/test/mock/MockChainlinkAggregator.sol new file mode 100644 index 00000000..a102b079 --- /dev/null +++ b/contracts/test/mock/MockChainlinkAggregator.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.17; + +contract MockChainlinkAggregator { + int256 private _answer; + uint8 private _decimals = 18; + + function setDecimals(uint8 newDecimals) external { + _decimals = newDecimals; + } + + function decimals() external view returns (uint8) { + return _decimals; + } + + function setAnswer(int256 answer) external { + _answer = answer; + } + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + roundId = 1; + answer = _answer; + startedAt = block.timestamp; + updatedAt = block.timestamp; + answeredInRound = 1; + } +} diff --git a/contracts/test/mock/MockERC1155.sol b/contracts/test/mock/MockERC1155.sol new file mode 100644 index 00000000..d234cf30 --- /dev/null +++ b/contracts/test/mock/MockERC1155.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { ERC1155 } from "solmate/src/tokens/ERC1155.sol"; + +// LooksRare unopinionated libraries +import { IERC2981 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; + +contract MockERC1155 is ERC1155 { + function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts) public { + _batchMint(to, tokenIds, amounts, ""); + } + + function mint(address to, uint256 tokenId, uint256 amount) public { + _mint(to, tokenId, amount, ""); + } + + function uri(uint256) public pure override returns (string memory) { + return "uri"; + } + + function royaltyInfo(uint256, uint256) external pure returns (address, uint256) { + return (address(0), 0); + } + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/contracts/test/mock/MockERC1155SupportsNoInterface.sol b/contracts/test/mock/MockERC1155SupportsNoInterface.sol new file mode 100644 index 00000000..3a233d63 --- /dev/null +++ b/contracts/test/mock/MockERC1155SupportsNoInterface.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { MockERC1155 } from "./MockERC1155.sol"; + +contract MockERC1155SupportsNoInterface is MockERC1155 { + function supportsInterface(bytes4) public view virtual override returns (bool) { + return false; + } +} diff --git a/contracts/test/mock/MockERC1155WithoutAnyBalanceOf.sol b/contracts/test/mock/MockERC1155WithoutAnyBalanceOf.sol new file mode 100644 index 00000000..ed4b6286 --- /dev/null +++ b/contracts/test/mock/MockERC1155WithoutAnyBalanceOf.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +/** + * @dev This contract has to inherit from OZ instead of Solmate because + * Solmate's implementation defines balanceOf as a public mapping + * and it cannot be overridden. + */ +contract MockERC1155WithoutAnyBalanceOf is ERC1155 { + constructor() ERC1155("https://example.com") {} + + function balanceOf(address, uint256) public view virtual override returns (uint256) { + revert("Not implemented"); + } + + function balanceOfBatch(address[] memory, uint256[] memory) public pure override returns (uint256[] memory) { + revert("Not implemented"); + } +} diff --git a/contracts/test/mock/MockERC1155WithoutBalanceOfBatch.sol b/contracts/test/mock/MockERC1155WithoutBalanceOfBatch.sol new file mode 100644 index 00000000..c8fbccb2 --- /dev/null +++ b/contracts/test/mock/MockERC1155WithoutBalanceOfBatch.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { MockERC1155 } from "./MockERC1155.sol"; + +contract MockERC1155WithoutBalanceOfBatch is MockERC1155 { + function balanceOfBatch(address[] calldata, uint256[] calldata) public pure override returns (uint256[] memory) { + revert("Not implemented"); + } +} diff --git a/contracts/test/mock/MockERC1155WithoutIsApprovedForAll.sol b/contracts/test/mock/MockERC1155WithoutIsApprovedForAll.sol new file mode 100644 index 00000000..7f7630dc --- /dev/null +++ b/contracts/test/mock/MockERC1155WithoutIsApprovedForAll.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +/** + * @dev This contract has to inherit from OZ instead of Solmate because + * Solmate's implementation defines isApprovedForAll as a public mapping + * and it cannot be overridden. + */ +contract MockERC1155WithoutIsApprovedForAll is ERC1155 { + constructor() ERC1155("https://example.com") {} + + function mint(address to, uint256 tokenId, uint256 amount) public { + _mint(to, tokenId, amount, ""); + } + + function isApprovedForAll(address, address) public view virtual override returns (bool) { + revert("Not implemented"); + } +} diff --git a/contracts/test/mock/MockERC20.sol b/contracts/test/mock/MockERC20.sol new file mode 100644 index 00000000..8ec94827 --- /dev/null +++ b/contracts/test/mock/MockERC20.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; + +contract MockERC20 is ERC20("MockERC20", "MockERC20", 18) { + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} diff --git a/contracts/test/mock/MockERC721.sol b/contracts/test/mock/MockERC721.sol new file mode 100644 index 00000000..24c1bdc9 --- /dev/null +++ b/contracts/test/mock/MockERC721.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { ERC721 } from "solmate/src/tokens/ERC721.sol"; +import { IERC165 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC165.sol"; + +contract MockERC721 is ERC721("MockERC721", "MockERC721") { + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function batchMint(address to, uint256 amount) external { + for (uint256 i; i < amount; i++) { + _mint(to, i); + } + } + + function batchMint(address to, uint256[] memory tokenIds) public { + for (uint256 i; i < tokenIds.length; i++) { + _mint(to, tokenIds[i]); + } + } + + function tokenURI(uint256) public pure override returns (string memory) { + return "tokenURI"; + } + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/test/mock/MockERC721SupportsNoInterface.sol b/contracts/test/mock/MockERC721SupportsNoInterface.sol new file mode 100644 index 00000000..8cd86d36 --- /dev/null +++ b/contracts/test/mock/MockERC721SupportsNoInterface.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.7; + +import { MockERC721 } from "./MockERC721.sol"; + +contract MockERC721SupportsNoInterface is MockERC721 { + function supportsInterface(bytes4) public view virtual override returns (bool) { + return false; + } +} diff --git a/contracts/test/mock/MockERC721WithRoyalties.sol b/contracts/test/mock/MockERC721WithRoyalties.sol new file mode 100644 index 00000000..0083280b --- /dev/null +++ b/contracts/test/mock/MockERC721WithRoyalties.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { IERC165, IERC2981 } from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; +import { MockERC721 } from "./MockERC721.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +/** + * @dev This contract allows adding a royalty basis points higher than 10,000, which + * reverts during the royaltyInfo call. The purpose is to create a scenario where + * royaltyInfo returns the correct response for some token IDs and reverts for some + * other token IDs. This can potentially happen in a bundle transaction. + */ +contract MockERC721WithRoyalties is MockERC721, IERC2981 { + address public immutable DEFAULT_ROYALTY_RECIPIENT; + uint256 public immutable DEFAULT_ROYALTY_FEE; + + mapping(uint256 => uint256) internal _royaltyFeeForTokenId; + mapping(uint256 => address) internal _royaltyRecipientForTokenId; + + constructor(address _royaltyFeeRecipient, uint256 _royaltyFee) { + DEFAULT_ROYALTY_RECIPIENT = _royaltyFeeRecipient; + DEFAULT_ROYALTY_FEE = _royaltyFee; + } + + function addCustomRoyaltyInformationForTokenId( + uint256 tokenId, + address royaltyRecipient, + uint256 royaltyFee + ) external { + _royaltyRecipientForTokenId[tokenId] = royaltyRecipient; + _royaltyFeeForTokenId[tokenId] = royaltyFee; + } + + function royaltyInfo( + uint256 tokenId, + uint256 salePrice + ) external view override returns (address royaltyRecipient, uint256 royaltyAmount) { + royaltyRecipient = _royaltyRecipientForTokenId[tokenId] == address(0) + ? DEFAULT_ROYALTY_RECIPIENT + : _royaltyRecipientForTokenId[tokenId]; + uint256 _royaltyFee = _royaltyFeeForTokenId[tokenId]; + uint256 royaltyFee = _royaltyFee == 0 ? DEFAULT_ROYALTY_FEE : _royaltyFee; + require(royaltyFee <= ONE_HUNDRED_PERCENT_IN_BP, "Royalty too high"); + royaltyAmount = (royaltyFee * salePrice) / ONE_HUNDRED_PERCENT_IN_BP; + } + + function supportsInterface(bytes4 interfaceId) public view override(MockERC721, IERC165) returns (bool) { + return interfaceId == 0x2a55205a || super.supportsInterface(interfaceId); + } +} diff --git a/contracts/test/mock/MockRoyaltyFeeRegistry.sol b/contracts/test/mock/MockRoyaltyFeeRegistry.sol new file mode 100644 index 00000000..e4ec7a1f --- /dev/null +++ b/contracts/test/mock/MockRoyaltyFeeRegistry.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +// LooksRare unopinionated libraries +import { OwnableTwoSteps } from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; + +// Royalty Fee Registry interface +import { IRoyaltyFeeRegistry } from "@hypercerts/marketplace/interfaces/IRoyaltyFeeRegistry.sol"; + +// Constants +import { ONE_HUNDRED_PERCENT_IN_BP } from "@hypercerts/marketplace/constants/NumericConstants.sol"; + +/** + * @title MockRoyaltyFeeRegistry + * @notice It is a royalty fee registry for the LooksRare exchange. + * @dev The original one used the standard Ownable library from OpenZeppelin. + */ +contract MockRoyaltyFeeRegistry is IRoyaltyFeeRegistry, OwnableTwoSteps { + struct FeeInfo { + address setter; + address receiver; + uint256 fee; + } + + // Limit (if enforced for fee royalty in basis point (10,000 = 100%) + uint256 public royaltyFeeLimit; + + mapping(address => FeeInfo) private _royaltyFeeInfoCollection; + + event NewRoyaltyFeeLimit(uint256 royaltyFeeLimit); + event RoyaltyFeeUpdate(address indexed collection, address indexed setter, address indexed receiver, uint256 fee); + + /** + * @notice Constructor + * @param _owner Owner address + * @param _royaltyFeeLimit new royalty fee limit (500 = 5%, 1,000 = 10%) + */ + constructor(address _owner, uint256 _royaltyFeeLimit) OwnableTwoSteps(_owner) { + require(_royaltyFeeLimit <= 9_500, "Owner: Royalty fee limit too high"); + royaltyFeeLimit = _royaltyFeeLimit; + } + + /** + * @notice Update royalty info for collection + * @param _royaltyFeeLimit new royalty fee limit (500 = 5%, 1,000 = 10%) + */ + function updateRoyaltyFeeLimit(uint256 _royaltyFeeLimit) external onlyOwner { + require(_royaltyFeeLimit <= 9_500, "Owner: Royalty fee limit too high"); + royaltyFeeLimit = _royaltyFeeLimit; + + emit NewRoyaltyFeeLimit(_royaltyFeeLimit); + } + + /** + * @notice Update royalty info for collection + * @param collection address of the NFT contract + * @param setter address that sets the receiver + * @param receiver receiver for the royalty fee + * @param fee fee (500 = 5%, 1,000 = 10%) + */ + function updateRoyaltyInfoForCollection( + address collection, + address setter, + address receiver, + uint256 fee + ) external onlyOwner { + require(fee <= royaltyFeeLimit, "Registry: Royalty fee too high"); + _royaltyFeeInfoCollection[collection] = FeeInfo({ setter: setter, receiver: receiver, fee: fee }); + + emit RoyaltyFeeUpdate(collection, setter, receiver, fee); + } + + /** + * @notice Calculate royalty info for a collection address and a sale gross amount + * @param collection collection address + * @param amount amount + * @return receiver address and amount received by royalty recipient + */ + function royaltyInfo(address collection, uint256 amount) external view returns (address, uint256) { + return ( + _royaltyFeeInfoCollection[collection].receiver, + (amount * _royaltyFeeInfoCollection[collection].fee) / ONE_HUNDRED_PERCENT_IN_BP + ); + } + + /** + * @notice View royalty info for a collection address + * @param collection collection address + */ + function royaltyFeeInfoCollection(address collection) external view returns (address, address, uint256) { + return ( + _royaltyFeeInfoCollection[collection].setter, + _royaltyFeeInfoCollection[collection].receiver, + _royaltyFeeInfoCollection[collection].fee + ); + } +} diff --git a/contracts/test/mock/MockSmartWallet.sol b/contracts/test/mock/MockSmartWallet.sol new file mode 100644 index 00000000..6e30673b --- /dev/null +++ b/contracts/test/mock/MockSmartWallet.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.17; + +contract MockSmartWallet { + address public owner; + + bytes4 internal constant MAGICVALUE = bytes4(keccak256("isValidSignature(bytes32,bytes)")); + + modifier onlyOwner() { + if (msg.sender != owner) { + revert("Unauthorized"); + } + _; + } + + constructor() { + owner = msg.sender; + } + + receive() external payable {} + + function transferOwnership(address _newOwner) external onlyOwner { + owner = _newOwner; + } + + function executeBatch(address[] calldata dest, bytes[] calldata calldata_) external onlyOwner { + if (dest.length != calldata_.length) { + revert("Invalid array length"); + } + for (uint256 i = 0; i < dest.length; ++i) { + execute(dest[i], 0, calldata_[i]); + } + } + + function isValidSignature(bytes32 _hash, bytes memory _signature) external view returns (bytes4 magicValue) { + bytes32 r; + bytes32 s; + uint8 v; + + assembly { + r := mload(add(_signature, 0x20)) + s := mload(add(_signature, 0x40)) + v := byte(0, mload(add(_signature, 0x60))) + } + address recovered = ecrecover(_hash, v, r, s); + if (recovered == address(0)) { + revert("Invalid signature"); + } + if (owner != recovered) { + revert("Invalid signer"); + } + + return MAGICVALUE; + } + + function execute(address dest, uint256 value, bytes calldata calldata_) public onlyOwner { + (bool success, bytes memory result) = dest.call{ value: value }(calldata_); + + if (!success) { + assembly { + revert(add(result, 32), mload(result)) + } + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e178258..1ca9cd3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: contracts: devDependencies: + '@chainlink/contracts': + specifier: ^0.8.0 + version: 0.8.0(ethers@5.7.2) '@commitlint/cli': specifier: ^17.1.2 version: 17.8.1 @@ -51,6 +54,9 @@ importers: '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 + '@looksrare/contracts-libs': + specifier: ^3.4.0 + version: 3.4.0 '@nomicfoundation/hardhat-chai-matchers': specifier: ^1.0.5 version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3)(chai@4.3.10)(ethers@5.7.2)(hardhat@2.13.1) @@ -66,6 +72,9 @@ importers: '@nomiclabs/hardhat-etherscan': specifier: ^3.1.3 version: 3.1.7(hardhat@2.13.1) + '@openzeppelin/contracts': + specifier: <5.0.0 + version: 4.3.3 '@openzeppelin/hardhat-defender': specifier: ^1.8.2 version: 1.9.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.13.1) @@ -189,6 +198,9 @@ importers: solidity-coverage: specifier: ^0.8.2 version: 0.8.5(hardhat@2.13.1) + solmate: + specifier: ^6.2.0 + version: 6.2.0 ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) @@ -2640,6 +2652,19 @@ packages: - zod dev: false + /@chainlink/contracts@0.8.0(ethers@5.7.2): + resolution: {integrity: sha512-nUv1Uxw5Mn92wgLs2bgPYmo8hpdQ3s9jB/lcbdU0LmNOVu0hbfmouVnqwRLa28Ll50q6GczUA+eO0ikNIKLZsA==} + dependencies: + '@eth-optimism/contracts': 0.5.40(ethers@5.7.2) + '@openzeppelin/contracts': 4.3.3 + '@openzeppelin/contracts-upgradeable-4.7.3': /@openzeppelin/contracts-upgradeable@4.7.3 + '@openzeppelin/contracts-v0.7': /@openzeppelin/contracts@3.4.2 + transitivePeerDependencies: + - bufferutil + - ethers + - utf-8-validate + dev: true + /@chainsafe/as-sha256@0.3.1: resolution: {integrity: sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==} @@ -5106,6 +5131,44 @@ packages: resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@eth-optimism/contracts@0.5.40(ethers@5.7.2): + resolution: {integrity: sha512-MrzV0nvsymfO/fursTB7m/KunkPsCndltVgfdHaT1Aj5Vi6R/doKIGGkOofHX+8B6VMZpuZosKCMQ5lQuqjt8w==} + peerDependencies: + ethers: ^5.7.2 + dependencies: + '@eth-optimism/core-utils': 0.12.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + ethers: 5.7.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@eth-optimism/core-utils@0.12.0: + resolution: {integrity: sha512-qW+7LZYCz7i8dRa7SRlUKIo1VBU8lvN0HeXCxJR+z+xtMzMQpPds20XJNCMclszxYQHkXY00fOT6GvFw9ZL6nw==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/contracts': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/providers': 5.7.2 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 + bufio: 1.2.1 + chai: 4.3.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@ethereum-attestation-service/eas-contracts@0.27.1(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(ts-node@10.9.1)(typescript@4.9.5): resolution: {integrity: sha512-ly1N/jLbXJjACDL7dnMSkzViBxxuVc+aMZ3EB1kpFxeMWrXkb7nN6w9gxGTH+m3gJztaKvyMsyr/13pA0OYq6Q==} dependencies: @@ -8824,6 +8887,11 @@ packages: '@lit-labs/ssr-dom-shim': 1.1.2 dev: false + /@looksrare/contracts-libs@3.4.0: + resolution: {integrity: sha512-tRFHcz9D4J0PLQg4ETWlvlRqrHpEoZhKsCTbyqATDXmekXvxbgk8+sRtaZK9Zo4gHpt6x+TcOgdUdARCg2elWg==} + engines: {node: '>=8.3.0'} + dev: true + /@mdx-js/mdx@1.6.22: resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==} dependencies: @@ -10107,6 +10175,18 @@ packages: resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} dev: true + /@openzeppelin/contracts-upgradeable@4.7.3: + resolution: {integrity: sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==} + dev: true + + /@openzeppelin/contracts@3.4.2: + resolution: {integrity: sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==} + dev: true + + /@openzeppelin/contracts@4.3.3: + resolution: {integrity: sha512-tDBopO1c98Yk7Cv/PZlHqrvtVjlgK5R4J6jxLwoO7qxK4xqOiZG+zSkIvGFpPZ0ikc3QOED3plgdqjgNTnBc7g==} + dev: true + /@openzeppelin/defender-admin-client@1.49.0: resolution: {integrity: sha512-ka+GTbsnGO6j1R2AGj027uu29es/EBVs3VjJStb+7u/1lNhx1xSRS11JBD0a0GNhrwqsKU4czIemlIKMlUzhhQ==} dependencies: @@ -15472,6 +15552,11 @@ packages: dependencies: node-gyp-build: 4.6.1 + /bufio@1.2.1: + resolution: {integrity: sha512-9oR3zNdupcg/Ge2sSHQF3GX+kmvL/fTPvD0nd5AGLq8SjUYnTz+SlFjK/GXidndbZtIj+pVKXiWeR9w6e9wKCA==} + engines: {node: '>=14.0.0'} + dev: true + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -30601,6 +30686,10 @@ packages: - supports-color dev: true + /solmate@6.2.0: + resolution: {integrity: sha512-AM38ioQ2P8zRsA42zenb9or6OybRjOLXIu3lhIT8rhddUuduCt76pUEuLxOIg9GByGojGz+EbpFdCB6B+QZVVA==} + dev: true + /sonic-boom@2.8.0: resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} dependencies: From 2268e9617ea9ada4f8aeb9135ce0f7c366d81bff Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 11:00:13 +0200 Subject: [PATCH 05/15] chore(gha): run gha on push and develop-branches --- .github/workflows/ci-default.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci-default.yml b/.github/workflows/ci-default.yml index a7242d72..cbc79fa4 100644 --- a/.github/workflows/ci-default.yml +++ b/.github/workflows/ci-default.yml @@ -19,15 +19,11 @@ env: on: # A push occurs to one of the matched branches. push: - branches: - - main - - develop - # Or when a pull request event occurs for a pull request against one of the - # matched branches. pull_request: branches: - main - develop + - develop-contracts # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 7cc05d920c373ae06c7175e42b47a1515adb7caf Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 12:58:28 +0200 Subject: [PATCH 06/15] fix(build): build and lint errors --- contracts/.solhint.json | 7 +- contracts/hardhat.config.ts | 2 +- contracts/package.json | 2 +- .../CreatorFeeManagerWithRoyalties.sol | 12 +- .../interfaces/ILooksRareProtocol.sol | 12 +- .../marketplace/ProtocolFeeRecipient.t.sol | 2 +- .../marketplace/utils/MerkleWithPosition.sol | 12 +- .../foundry/protocol/HypercertMinter.t.sol | 4 +- graph/abis/HypercertTrader.json | 656 ------------ .../HypercertTrader/HypercertTrader.ts | 954 ------------------ graph/networks.json | 4 - graph/src/hypercert-trader.ts | 73 -- graph/src/utils.ts | 1 - graph/subgraph.yaml | 24 - graph/tests/hypercert-trader-utils.ts | 261 ----- graph/tests/hypercert-trader.test.ts | 80 -- 16 files changed, 31 insertions(+), 2075 deletions(-) delete mode 100644 graph/abis/HypercertTrader.json delete mode 100644 graph/generated/HypercertTrader/HypercertTrader.ts delete mode 100644 graph/src/hypercert-trader.ts delete mode 100644 graph/tests/hypercert-trader-utils.ts delete mode 100644 graph/tests/hypercert-trader.test.ts diff --git a/contracts/.solhint.json b/contracts/.solhint.json index d60b535e..fad41466 100644 --- a/contracts/.solhint.json +++ b/contracts/.solhint.json @@ -1,11 +1,10 @@ { - "plugins": ["prettier"], "extends": "solhint:recommended", "rules": { - "code-complexity": ["error", 8], - "compiler-version": ["error", "0.8.16"], + "code-complexity": ["error", 12], + "compiler-version": ["error", ">=0.8.16"], "func-visibility": ["error", { "ignoreConstructors": true }], - "max-line-length": ["warn", 120], + "max-line-length": ["warn", 200], "no-console": "off", "not-rely-on-time": "off", "reason-string": ["warn", { "maxLength": 64 }], diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 36d33635..87cd1dc7 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -152,7 +152,7 @@ const config = { }, paths: { cache: "./cache_hardhat", // Use a different cache for Hardhat than Foundry - sources: "./src", + sources: "./src/protocol", tests: "./test", }, preprocess: { diff --git a/contracts/package.json b/contracts/package.json index 21473b24..5d06aefd 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -107,7 +107,7 @@ "copy:contracts": "copyfiles -u 1 ./src/**/*.sol ./src/*.sol ./contracts", "docs": "hardhat dodoc", "lint": "pnpm lint:sol && pnpm prettier:check", - "lint:sol": "solhint -w 5 \"./{src,test}/**/*.sol\"", + "lint:sol": "solhint -w 60 \"./{src,test/protocol,test/marketplace}/**/*.sol\"", "prebuild": "pnpm clean", "prepublish": "pnpm build", "prettier": "prettier --config \"./.prettierrc.yml\" --write \"**/*.{json,md,sol,yml}\"", diff --git a/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol b/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol index a1185163..ac72682d 100644 --- a/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol +++ b/contracts/src/marketplace/CreatorFeeManagerWithRoyalties.sol @@ -30,16 +30,16 @@ contract CreatorFeeManagerWithRoyalties is ICreatorFeeManager { /** * @inheritdoc ICreatorFeeManager * @dev There are two on-chain sources for the royalty fee to distribute. - * 1. RoyaltyFeeRegistry: It is an on-chain registry where creator fee is defined - for all items of a collection. + * 1. RoyaltyFeeRegistry: It is an on-chain registry where creator fee is defined + * for all items of a collection. * 2. ERC2981: The NFT Royalty Standard where royalty fee is defined at a itemId level in a collection. - * The on-chain logic looks up the registry first. If it does not find anything, + * The on-chain logic looks up the registry first. If it does not find anything, * it checks if a collection is ERC2981. If so, it fetches the proper royalty information for the itemId. - * For a bundle that contains multiple itemIds (for a collection using ERC2981), if the royalty fee/recipient + * For a bundle that contains multiple itemIds (for a collection using ERC2981), if the royalty fee/recipient * differ among the itemIds part of the bundle, the trade reverts. - * This contract DOES NOT enforce any restriction for extremely high creator fee, + * This contract DOES NOT enforce any restriction for extremely high creator fee, * nor verifies the creator fee fetched is inferior to the total price. - * If any contract relies on it to build an on-chain royalty logic, + * If any contract relies on it to build an on-chain royalty logic, * it should implement protection against: * (1) high royalties * (2) potential unexpected royalty changes that can occur after the creation of the order. diff --git a/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol b/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol index b9305d03..09722537 100644 --- a/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol +++ b/contracts/src/marketplace/interfaces/ILooksRareProtocol.sol @@ -59,10 +59,12 @@ interface ILooksRareProtocol { * feeAmounts[1] Creator fee amount * feeAmounts[2] Protocol fee amount prior to adjustment for a potential affiliate payment */ + // maker (receives the NFT) event TakerAsk( + // taker (initiates the transaction) NonceInvalidationParameters nonceInvalidationParameters, - address askUser, // taker (initiates the transaction) - address bidUser, // maker (receives the NFT) + address askUser, + address bidUser, uint256 strategyId, address currency, address collection, @@ -90,10 +92,12 @@ interface ILooksRareProtocol { * feeAmounts[1] Creator fee amount * feeAmounts[2] Protocol fee amount prior to adjustment for a potential affiliate payment */ + // taker (receives the NFT) event TakerBid( + // taker (initiates the transaction) NonceInvalidationParameters nonceInvalidationParameters, - address bidUser, // taker (initiates the transaction) - address bidRecipient, // taker (receives the NFT) + address bidUser, + address bidRecipient, uint256 strategyId, address currency, address collection, diff --git a/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol b/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol index 5074a83d..36f370df 100644 --- a/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol +++ b/contracts/test/foundry/marketplace/ProtocolFeeRecipient.t.sol @@ -17,7 +17,7 @@ contract ProtocolFeeRecipientTest is TestParameters { uint256 private feeSharingSetterInitialWETHBalance; address private constant FEE_SHARING_SETTER = 0x5924A28caAF1cc016617874a2f0C3710d881f3c1; - uint256 private constant DUST = 0.69420 ether; + uint256 private constant DUST = 0.6942 ether; function setUp() public { vm.createSelectFork(vm.rpcUrl("mainnet")); diff --git a/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol b/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol index 38628707..3b7cf871 100644 --- a/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol +++ b/contracts/test/foundry/marketplace/utils/MerkleWithPosition.sol @@ -8,9 +8,11 @@ import { OrderStructs } from "@hypercerts/marketplace/libraries/OrderStructs.sol * hashLeafPair does not sort the nodes to match EIP-712. */ contract MerkleWithPosition { - /******************** + /** + * * PROOF GENERATION * - ********************/ + * + */ function getRoot(OrderStructs.MerkleTreeNode[] memory data) public pure returns (bytes32) { require(data.length > 1, "won't generate root for single leaf"); @@ -90,9 +92,11 @@ contract MerkleWithPosition { return result; } - /****************** + /** + * * MATH "LIBRARY" * - ******************/ + * + */ /// Original bitmagic adapted from https://github.com/paulrberg/prb-math/blob/main/contracts/PRBMath.sol /// @dev Note that x assumed > 1 diff --git a/contracts/test/foundry/protocol/HypercertMinter.t.sol b/contracts/test/foundry/protocol/HypercertMinter.t.sol index 6647ffc0..86364b56 100644 --- a/contracts/test/foundry/protocol/HypercertMinter.t.sol +++ b/contracts/test/foundry/protocol/HypercertMinter.t.sol @@ -38,7 +38,9 @@ contract MinterTestHelper { return 0; } sum = 0; - for (uint256 i = 0; i < array.length; i++) sum += array[i]; + for (uint256 i = 0; i < array.length; i++) { + sum += array[i]; + } } function buildFractions(uint256 size) public pure returns (uint256[] memory) { diff --git a/graph/abis/HypercertTrader.json b/graph/abis/HypercertTrader.json deleted file mode 100644 index 1f114e5e..00000000 --- a/graph/abis/HypercertTrader.json +++ /dev/null @@ -1,656 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "name": "InvalidBuy", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "name": "InvalidOffer", - "type": "error" - }, - { - "inputs": [], - "name": "NotAllowed", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "previousAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "AdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "beacon", - "type": "address" - } - ], - "name": "BeaconUpgraded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "name": "OfferCancelled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "offerer", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "name": "OfferCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "seller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "buyer", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "unitsBought", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "buyToken", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tokenAmountPerUnit", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "name": "Trade", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "offerIDs", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "unitAmounts", - "type": "uint256[]" - }, - { - "internalType": "address[]", - "name": "buyTokens", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "tokenAmountsPerUnit", - "type": "uint256[]" - } - ], - "name": "batchBuyUnits", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "unitAmount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "buyToken", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenAmountPerUnit", - "type": "uint256" - } - ], - "name": "buyUnits", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "name": "cancelOffer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "unitsForSale", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minUnitsPerTrade", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxUnitsPerTrade", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "minimumAmountPerUnit", - "type": "uint256" - } - ], - "internalType": "struct IHypercertTrader.AcceptedToken[]", - "name": "acceptedTokens", - "type": "tuple[]" - } - ], - "name": "createOffer", - "outputs": [ - { - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "offerID", - "type": "uint256" - } - ], - "name": "getOffer", - "outputs": [ - { - "components": [ - { - "internalType": "address", - "name": "offerer", - "type": "address" - }, - { - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "unitsAvailable", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minUnitsPerTrade", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxUnitsPerTrade", - "type": "uint256" - }, - { - "internalType": "enum IHypercertTrader.OfferType", - "name": "offerType", - "type": "uint8" - }, - { - "internalType": "enum IHypercertTrader.OfferStatus", - "name": "status", - "type": "uint8" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "minimumAmountPerUnit", - "type": "uint256" - } - ], - "internalType": "struct IHypercertTrader.AcceptedToken[]", - "name": "acceptedTokens", - "type": "tuple[]" - } - ], - "internalType": "struct IHypercertTrader.Offer", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "offers", - "outputs": [ - { - "internalType": "address", - "name": "offerer", - "type": "address" - }, - { - "internalType": "address", - "name": "hypercertContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fractionID", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "unitsAvailable", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minUnitsPerTrade", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxUnitsPerTrade", - "type": "uint256" - }, - { - "internalType": "enum IHypercertTrader.OfferType", - "name": "offerType", - "type": "uint8" - }, - { - "internalType": "enum IHypercertTrader.OfferStatus", - "name": "status", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proxiableUUID", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "totalUnitsForSale", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "name": "upgradeTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/graph/generated/HypercertTrader/HypercertTrader.ts b/graph/generated/HypercertTrader/HypercertTrader.ts deleted file mode 100644 index 36aef88f..00000000 --- a/graph/generated/HypercertTrader/HypercertTrader.ts +++ /dev/null @@ -1,954 +0,0 @@ -// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. - -import { - ethereum, - JSONValue, - TypedMap, - Entity, - Bytes, - Address, - BigInt, -} from "@graphprotocol/graph-ts"; - -export class AdminChanged extends ethereum.Event { - get params(): AdminChanged__Params { - return new AdminChanged__Params(this); - } -} - -export class AdminChanged__Params { - _event: AdminChanged; - - constructor(event: AdminChanged) { - this._event = event; - } - - get previousAdmin(): Address { - return this._event.parameters[0].value.toAddress(); - } - - get newAdmin(): Address { - return this._event.parameters[1].value.toAddress(); - } -} - -export class BeaconUpgraded extends ethereum.Event { - get params(): BeaconUpgraded__Params { - return new BeaconUpgraded__Params(this); - } -} - -export class BeaconUpgraded__Params { - _event: BeaconUpgraded; - - constructor(event: BeaconUpgraded) { - this._event = event; - } - - get beacon(): Address { - return this._event.parameters[0].value.toAddress(); - } -} - -export class Initialized extends ethereum.Event { - get params(): Initialized__Params { - return new Initialized__Params(this); - } -} - -export class Initialized__Params { - _event: Initialized; - - constructor(event: Initialized) { - this._event = event; - } - - get version(): i32 { - return this._event.parameters[0].value.toI32(); - } -} - -export class OfferCancelled extends ethereum.Event { - get params(): OfferCancelled__Params { - return new OfferCancelled__Params(this); - } -} - -export class OfferCancelled__Params { - _event: OfferCancelled; - - constructor(event: OfferCancelled) { - this._event = event; - } - - get creator(): Address { - return this._event.parameters[0].value.toAddress(); - } - - get hypercertContract(): Address { - return this._event.parameters[1].value.toAddress(); - } - - get fractionID(): BigInt { - return this._event.parameters[2].value.toBigInt(); - } - - get offerID(): BigInt { - return this._event.parameters[3].value.toBigInt(); - } -} - -export class OfferCreated extends ethereum.Event { - get params(): OfferCreated__Params { - return new OfferCreated__Params(this); - } -} - -export class OfferCreated__Params { - _event: OfferCreated; - - constructor(event: OfferCreated) { - this._event = event; - } - - get offerer(): Address { - return this._event.parameters[0].value.toAddress(); - } - - get hypercertContract(): Address { - return this._event.parameters[1].value.toAddress(); - } - - get fractionID(): BigInt { - return this._event.parameters[2].value.toBigInt(); - } - - get offerID(): BigInt { - return this._event.parameters[3].value.toBigInt(); - } -} - -export class OwnershipTransferred extends ethereum.Event { - get params(): OwnershipTransferred__Params { - return new OwnershipTransferred__Params(this); - } -} - -export class OwnershipTransferred__Params { - _event: OwnershipTransferred; - - constructor(event: OwnershipTransferred) { - this._event = event; - } - - get previousOwner(): Address { - return this._event.parameters[0].value.toAddress(); - } - - get newOwner(): Address { - return this._event.parameters[1].value.toAddress(); - } -} - -export class Paused extends ethereum.Event { - get params(): Paused__Params { - return new Paused__Params(this); - } -} - -export class Paused__Params { - _event: Paused; - - constructor(event: Paused) { - this._event = event; - } - - get account(): Address { - return this._event.parameters[0].value.toAddress(); - } -} - -export class Trade extends ethereum.Event { - get params(): Trade__Params { - return new Trade__Params(this); - } -} - -export class Trade__Params { - _event: Trade; - - constructor(event: Trade) { - this._event = event; - } - - get seller(): Address { - return this._event.parameters[0].value.toAddress(); - } - - get buyer(): Address { - return this._event.parameters[1].value.toAddress(); - } - - get hypercertContract(): Address { - return this._event.parameters[2].value.toAddress(); - } - - get fractionID(): BigInt { - return this._event.parameters[3].value.toBigInt(); - } - - get unitsBought(): BigInt { - return this._event.parameters[4].value.toBigInt(); - } - - get buyToken(): Address { - return this._event.parameters[5].value.toAddress(); - } - - get tokenAmountPerUnit(): BigInt { - return this._event.parameters[6].value.toBigInt(); - } - - get offerID(): BigInt { - return this._event.parameters[7].value.toBigInt(); - } -} - -export class Unpaused extends ethereum.Event { - get params(): Unpaused__Params { - return new Unpaused__Params(this); - } -} - -export class Unpaused__Params { - _event: Unpaused; - - constructor(event: Unpaused) { - this._event = event; - } - - get account(): Address { - return this._event.parameters[0].value.toAddress(); - } -} - -export class Upgraded extends ethereum.Event { - get params(): Upgraded__Params { - return new Upgraded__Params(this); - } -} - -export class Upgraded__Params { - _event: Upgraded; - - constructor(event: Upgraded) { - this._event = event; - } - - get implementation(): Address { - return this._event.parameters[0].value.toAddress(); - } -} - -export class HypercertTrader__getOfferResultValue0Struct extends ethereum.Tuple { - get offerer(): Address { - return this[0].toAddress(); - } - - get hypercertContract(): Address { - return this[1].toAddress(); - } - - get fractionID(): BigInt { - return this[2].toBigInt(); - } - - get unitsAvailable(): BigInt { - return this[3].toBigInt(); - } - - get minUnitsPerTrade(): BigInt { - return this[4].toBigInt(); - } - - get maxUnitsPerTrade(): BigInt { - return this[5].toBigInt(); - } - - get offerType(): i32 { - return this[6].toI32(); - } - - get status(): i32 { - return this[7].toI32(); - } - - get acceptedTokens(): Array { - return this[8].toTupleArray(); - } -} - -export class HypercertTrader__getOfferResultValue0AcceptedTokensStruct extends ethereum.Tuple { - get token(): Address { - return this[0].toAddress(); - } - - get minimumAmountPerUnit(): BigInt { - return this[1].toBigInt(); - } -} - -export class HypercertTrader__offersResult { - value0: Address; - value1: Address; - value2: BigInt; - value3: BigInt; - value4: BigInt; - value5: BigInt; - value6: i32; - value7: i32; - - constructor( - value0: Address, - value1: Address, - value2: BigInt, - value3: BigInt, - value4: BigInt, - value5: BigInt, - value6: i32, - value7: i32, - ) { - this.value0 = value0; - this.value1 = value1; - this.value2 = value2; - this.value3 = value3; - this.value4 = value4; - this.value5 = value5; - this.value6 = value6; - this.value7 = value7; - } - - toMap(): TypedMap { - let map = new TypedMap(); - map.set("value0", ethereum.Value.fromAddress(this.value0)); - map.set("value1", ethereum.Value.fromAddress(this.value1)); - map.set("value2", ethereum.Value.fromUnsignedBigInt(this.value2)); - map.set("value3", ethereum.Value.fromUnsignedBigInt(this.value3)); - map.set("value4", ethereum.Value.fromUnsignedBigInt(this.value4)); - map.set("value5", ethereum.Value.fromUnsignedBigInt(this.value5)); - map.set( - "value6", - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(this.value6)), - ); - map.set( - "value7", - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(this.value7)), - ); - return map; - } - - getOfferer(): Address { - return this.value0; - } - - getHypercertContract(): Address { - return this.value1; - } - - getFractionID(): BigInt { - return this.value2; - } - - getUnitsAvailable(): BigInt { - return this.value3; - } - - getMinUnitsPerTrade(): BigInt { - return this.value4; - } - - getMaxUnitsPerTrade(): BigInt { - return this.value5; - } - - getOfferType(): i32 { - return this.value6; - } - - getStatus(): i32 { - return this.value7; - } -} - -export class HypercertTrader extends ethereum.SmartContract { - static bind(address: Address): HypercertTrader { - return new HypercertTrader("HypercertTrader", address); - } - - getOffer(offerID: BigInt): HypercertTrader__getOfferResultValue0Struct { - let result = super.call( - "getOffer", - "getOffer(uint256):((address,address,uint256,uint256,uint256,uint256,uint8,uint8,(address,uint256)[]))", - [ethereum.Value.fromUnsignedBigInt(offerID)], - ); - - return changetype( - result[0].toTuple(), - ); - } - - try_getOffer( - offerID: BigInt, - ): ethereum.CallResult { - let result = super.tryCall( - "getOffer", - "getOffer(uint256):((address,address,uint256,uint256,uint256,uint256,uint8,uint8,(address,uint256)[]))", - [ethereum.Value.fromUnsignedBigInt(offerID)], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue( - changetype( - value[0].toTuple(), - ), - ); - } - - offers(param0: BigInt): HypercertTrader__offersResult { - let result = super.call( - "offers", - "offers(uint256):(address,address,uint256,uint256,uint256,uint256,uint8,uint8)", - [ethereum.Value.fromUnsignedBigInt(param0)], - ); - - return new HypercertTrader__offersResult( - result[0].toAddress(), - result[1].toAddress(), - result[2].toBigInt(), - result[3].toBigInt(), - result[4].toBigInt(), - result[5].toBigInt(), - result[6].toI32(), - result[7].toI32(), - ); - } - - try_offers( - param0: BigInt, - ): ethereum.CallResult { - let result = super.tryCall( - "offers", - "offers(uint256):(address,address,uint256,uint256,uint256,uint256,uint8,uint8)", - [ethereum.Value.fromUnsignedBigInt(param0)], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue( - new HypercertTrader__offersResult( - value[0].toAddress(), - value[1].toAddress(), - value[2].toBigInt(), - value[3].toBigInt(), - value[4].toBigInt(), - value[5].toBigInt(), - value[6].toI32(), - value[7].toI32(), - ), - ); - } - - owner(): Address { - let result = super.call("owner", "owner():(address)", []); - - return result[0].toAddress(); - } - - try_owner(): ethereum.CallResult
{ - let result = super.tryCall("owner", "owner():(address)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toAddress()); - } - - paused(): boolean { - let result = super.call("paused", "paused():(bool)", []); - - return result[0].toBoolean(); - } - - try_paused(): ethereum.CallResult { - let result = super.tryCall("paused", "paused():(bool)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBoolean()); - } - - proxiableUUID(): Bytes { - let result = super.call("proxiableUUID", "proxiableUUID():(bytes32)", []); - - return result[0].toBytes(); - } - - try_proxiableUUID(): ethereum.CallResult { - let result = super.tryCall( - "proxiableUUID", - "proxiableUUID():(bytes32)", - [], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBytes()); - } - - totalUnitsForSale(param0: Address, param1: BigInt): BigInt { - let result = super.call( - "totalUnitsForSale", - "totalUnitsForSale(address,uint256):(uint256)", - [ - ethereum.Value.fromAddress(param0), - ethereum.Value.fromUnsignedBigInt(param1), - ], - ); - - return result[0].toBigInt(); - } - - try_totalUnitsForSale( - param0: Address, - param1: BigInt, - ): ethereum.CallResult { - let result = super.tryCall( - "totalUnitsForSale", - "totalUnitsForSale(address,uint256):(uint256)", - [ - ethereum.Value.fromAddress(param0), - ethereum.Value.fromUnsignedBigInt(param1), - ], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } -} - -export class ConstructorCall extends ethereum.Call { - get inputs(): ConstructorCall__Inputs { - return new ConstructorCall__Inputs(this); - } - - get outputs(): ConstructorCall__Outputs { - return new ConstructorCall__Outputs(this); - } -} - -export class ConstructorCall__Inputs { - _call: ConstructorCall; - - constructor(call: ConstructorCall) { - this._call = call; - } -} - -export class ConstructorCall__Outputs { - _call: ConstructorCall; - - constructor(call: ConstructorCall) { - this._call = call; - } -} - -export class BatchBuyUnitsCall extends ethereum.Call { - get inputs(): BatchBuyUnitsCall__Inputs { - return new BatchBuyUnitsCall__Inputs(this); - } - - get outputs(): BatchBuyUnitsCall__Outputs { - return new BatchBuyUnitsCall__Outputs(this); - } -} - -export class BatchBuyUnitsCall__Inputs { - _call: BatchBuyUnitsCall; - - constructor(call: BatchBuyUnitsCall) { - this._call = call; - } - - get recipient(): Address { - return this._call.inputValues[0].value.toAddress(); - } - - get offerIDs(): Array { - return this._call.inputValues[1].value.toBigIntArray(); - } - - get unitAmounts(): Array { - return this._call.inputValues[2].value.toBigIntArray(); - } - - get buyTokens(): Array
{ - return this._call.inputValues[3].value.toAddressArray(); - } - - get tokenAmountsPerUnit(): Array { - return this._call.inputValues[4].value.toBigIntArray(); - } -} - -export class BatchBuyUnitsCall__Outputs { - _call: BatchBuyUnitsCall; - - constructor(call: BatchBuyUnitsCall) { - this._call = call; - } -} - -export class BuyUnitsCall extends ethereum.Call { - get inputs(): BuyUnitsCall__Inputs { - return new BuyUnitsCall__Inputs(this); - } - - get outputs(): BuyUnitsCall__Outputs { - return new BuyUnitsCall__Outputs(this); - } -} - -export class BuyUnitsCall__Inputs { - _call: BuyUnitsCall; - - constructor(call: BuyUnitsCall) { - this._call = call; - } - - get recipient(): Address { - return this._call.inputValues[0].value.toAddress(); - } - - get offerID(): BigInt { - return this._call.inputValues[1].value.toBigInt(); - } - - get unitAmount(): BigInt { - return this._call.inputValues[2].value.toBigInt(); - } - - get buyToken(): Address { - return this._call.inputValues[3].value.toAddress(); - } - - get tokenAmountPerUnit(): BigInt { - return this._call.inputValues[4].value.toBigInt(); - } -} - -export class BuyUnitsCall__Outputs { - _call: BuyUnitsCall; - - constructor(call: BuyUnitsCall) { - this._call = call; - } -} - -export class CancelOfferCall extends ethereum.Call { - get inputs(): CancelOfferCall__Inputs { - return new CancelOfferCall__Inputs(this); - } - - get outputs(): CancelOfferCall__Outputs { - return new CancelOfferCall__Outputs(this); - } -} - -export class CancelOfferCall__Inputs { - _call: CancelOfferCall; - - constructor(call: CancelOfferCall) { - this._call = call; - } - - get offerID(): BigInt { - return this._call.inputValues[0].value.toBigInt(); - } -} - -export class CancelOfferCall__Outputs { - _call: CancelOfferCall; - - constructor(call: CancelOfferCall) { - this._call = call; - } -} - -export class CreateOfferCall extends ethereum.Call { - get inputs(): CreateOfferCall__Inputs { - return new CreateOfferCall__Inputs(this); - } - - get outputs(): CreateOfferCall__Outputs { - return new CreateOfferCall__Outputs(this); - } -} - -export class CreateOfferCall__Inputs { - _call: CreateOfferCall; - - constructor(call: CreateOfferCall) { - this._call = call; - } - - get hypercertContract(): Address { - return this._call.inputValues[0].value.toAddress(); - } - - get fractionID(): BigInt { - return this._call.inputValues[1].value.toBigInt(); - } - - get unitsForSale(): BigInt { - return this._call.inputValues[2].value.toBigInt(); - } - - get minUnitsPerTrade(): BigInt { - return this._call.inputValues[3].value.toBigInt(); - } - - get maxUnitsPerTrade(): BigInt { - return this._call.inputValues[4].value.toBigInt(); - } - - get acceptedTokens(): Array { - return this._call.inputValues[5].value.toTupleArray(); - } -} - -export class CreateOfferCall__Outputs { - _call: CreateOfferCall; - - constructor(call: CreateOfferCall) { - this._call = call; - } - - get offerID(): BigInt { - return this._call.outputValues[0].value.toBigInt(); - } -} - -export class CreateOfferCallAcceptedTokensStruct extends ethereum.Tuple { - get token(): Address { - return this[0].toAddress(); - } - - get minimumAmountPerUnit(): BigInt { - return this[1].toBigInt(); - } -} - -export class InitializeCall extends ethereum.Call { - get inputs(): InitializeCall__Inputs { - return new InitializeCall__Inputs(this); - } - - get outputs(): InitializeCall__Outputs { - return new InitializeCall__Outputs(this); - } -} - -export class InitializeCall__Inputs { - _call: InitializeCall; - - constructor(call: InitializeCall) { - this._call = call; - } -} - -export class InitializeCall__Outputs { - _call: InitializeCall; - - constructor(call: InitializeCall) { - this._call = call; - } -} - -export class PauseCall extends ethereum.Call { - get inputs(): PauseCall__Inputs { - return new PauseCall__Inputs(this); - } - - get outputs(): PauseCall__Outputs { - return new PauseCall__Outputs(this); - } -} - -export class PauseCall__Inputs { - _call: PauseCall; - - constructor(call: PauseCall) { - this._call = call; - } -} - -export class PauseCall__Outputs { - _call: PauseCall; - - constructor(call: PauseCall) { - this._call = call; - } -} - -export class RenounceOwnershipCall extends ethereum.Call { - get inputs(): RenounceOwnershipCall__Inputs { - return new RenounceOwnershipCall__Inputs(this); - } - - get outputs(): RenounceOwnershipCall__Outputs { - return new RenounceOwnershipCall__Outputs(this); - } -} - -export class RenounceOwnershipCall__Inputs { - _call: RenounceOwnershipCall; - - constructor(call: RenounceOwnershipCall) { - this._call = call; - } -} - -export class RenounceOwnershipCall__Outputs { - _call: RenounceOwnershipCall; - - constructor(call: RenounceOwnershipCall) { - this._call = call; - } -} - -export class TransferOwnershipCall extends ethereum.Call { - get inputs(): TransferOwnershipCall__Inputs { - return new TransferOwnershipCall__Inputs(this); - } - - get outputs(): TransferOwnershipCall__Outputs { - return new TransferOwnershipCall__Outputs(this); - } -} - -export class TransferOwnershipCall__Inputs { - _call: TransferOwnershipCall; - - constructor(call: TransferOwnershipCall) { - this._call = call; - } - - get newOwner(): Address { - return this._call.inputValues[0].value.toAddress(); - } -} - -export class TransferOwnershipCall__Outputs { - _call: TransferOwnershipCall; - - constructor(call: TransferOwnershipCall) { - this._call = call; - } -} - -export class UnpauseCall extends ethereum.Call { - get inputs(): UnpauseCall__Inputs { - return new UnpauseCall__Inputs(this); - } - - get outputs(): UnpauseCall__Outputs { - return new UnpauseCall__Outputs(this); - } -} - -export class UnpauseCall__Inputs { - _call: UnpauseCall; - - constructor(call: UnpauseCall) { - this._call = call; - } -} - -export class UnpauseCall__Outputs { - _call: UnpauseCall; - - constructor(call: UnpauseCall) { - this._call = call; - } -} - -export class UpgradeToCall extends ethereum.Call { - get inputs(): UpgradeToCall__Inputs { - return new UpgradeToCall__Inputs(this); - } - - get outputs(): UpgradeToCall__Outputs { - return new UpgradeToCall__Outputs(this); - } -} - -export class UpgradeToCall__Inputs { - _call: UpgradeToCall; - - constructor(call: UpgradeToCall) { - this._call = call; - } - - get newImplementation(): Address { - return this._call.inputValues[0].value.toAddress(); - } -} - -export class UpgradeToCall__Outputs { - _call: UpgradeToCall; - - constructor(call: UpgradeToCall) { - this._call = call; - } -} - -export class UpgradeToAndCallCall extends ethereum.Call { - get inputs(): UpgradeToAndCallCall__Inputs { - return new UpgradeToAndCallCall__Inputs(this); - } - - get outputs(): UpgradeToAndCallCall__Outputs { - return new UpgradeToAndCallCall__Outputs(this); - } -} - -export class UpgradeToAndCallCall__Inputs { - _call: UpgradeToAndCallCall; - - constructor(call: UpgradeToAndCallCall) { - this._call = call; - } - - get newImplementation(): Address { - return this._call.inputValues[0].value.toAddress(); - } - - get data(): Bytes { - return this._call.inputValues[1].value.toBytes(); - } -} - -export class UpgradeToAndCallCall__Outputs { - _call: UpgradeToAndCallCall; - - constructor(call: UpgradeToAndCallCall) { - this._call = call; - } -} diff --git a/graph/networks.json b/graph/networks.json index 477c6127..28925338 100644 --- a/graph/networks.json +++ b/graph/networks.json @@ -9,10 +9,6 @@ "HypercertMinter": { "address": "0x822F17A9A5EeCFd66dBAFf7946a8071C265D1d07", "startBlock": 8537999 - }, - "HypercertTrader": { - "address": "0x689587461AA3103D3D7975c5e4B352Ab711C14C2", - "startBlock": 9638007 } }, "optimism": { diff --git a/graph/src/hypercert-trader.ts b/graph/src/hypercert-trader.ts deleted file mode 100644 index 5c06f969..00000000 --- a/graph/src/hypercert-trader.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - OfferCancelled as OfferCancelledEvent, - OfferCreated as OfferCreatedEvent, - Trade as TradeEvent, -} from "../generated/HypercertTrader/HypercertTrader"; -import { Trade } from "../generated/schema"; -import { getOrCreateOffer, getOrCreateOfferByID } from "./utils"; -import { log, BigInt } from "@graphprotocol/graph-ts"; - -export function handleOfferCancelled(event: OfferCancelledEvent): void { - const offer = getOrCreateOfferByID( - event.params.hypercertContract, - event.params.fractionID, - event.params.offerID, - ); - - if (!offer) { - return; - } - - offer.status = "Cancelled"; - - offer.save(); -} - -export function handleOfferCreated(event: OfferCreatedEvent): void { - const offer = getOrCreateOffer( - event.params.hypercertContract, - event.address, - event.params.fractionID, - event.params.offerID, - ); - - offer.save(); -} - -export function handleTrade(event: TradeEvent): void { - const offer = getOrCreateOfferByID( - event.params.hypercertContract, - event.params.fractionID, - event.params.offerID, - ); - - if (!offer) { - log.error("Offer with ID {} not found", [ - event.params.offerID.toHexString(), - ]); - return; - } - - const tradeID = offer.id.concat(event.transaction.hash.toHexString()); - - let trade = Trade.load(tradeID); - - if (trade == null) { - trade = new Trade(tradeID); - - trade.buyer = event.params.buyer; - trade.offerID = offer.id; - trade.unitsSold = event.params.unitsBought; - trade.token = event.params.buyToken.toHexString(); - trade.amountPerUnit = event.params.tokenAmountPerUnit; - - trade.offerID = offer.id; - } - - offer.unitsAvailable = offer.unitsAvailable.minus(event.params.unitsBought); - if (offer.unitsAvailable.equals(BigInt.fromI32(0))) { - offer.status = "Fulfilled"; - } - - trade.save(); -} diff --git a/graph/src/utils.ts b/graph/src/utils.ts index 42b51fc5..2828cc15 100644 --- a/graph/src/utils.ts +++ b/graph/src/utils.ts @@ -1,5 +1,4 @@ import { HypercertMinter } from "../generated/HypercertMinter/HypercertMinter"; -import { HypercertTrader } from "../generated/HypercertTrader/HypercertTrader"; import { AcceptedToken, Allowlist, diff --git a/graph/subgraph.yaml b/graph/subgraph.yaml index 3c469f83..6b664dde 100644 --- a/graph/subgraph.yaml +++ b/graph/subgraph.yaml @@ -44,27 +44,3 @@ dataSources: - event: BatchValueTransfer(uint256[],uint256[],uint256[],uint256[]) handler: handleBatchValueTransfer file: ./src/hypercert-minter.ts - - kind: ethereum - name: HypercertTrader - network: goerli - source: - address: "0x689587461AA3103D3D7975c5e4B352Ab711C14C2" - abi: HypercertTrader - mapping: - kind: ethereum/events - apiVersion: 0.0.7 - language: wasm/assemblyscript - entities: - - Offer - abis: - - name: HypercertTrader - file: ./abis/HypercertTrader.json - eventHandlers: - - event: OfferCancelled(indexed address,indexed address,indexed uint256,uint256) - handler: handleOfferCancelled - - event: OfferCreated(indexed address,indexed address,indexed uint256,uint256) - handler: handleOfferCreated - - event: Trade(indexed address,indexed address,indexed - address,uint256,uint256,address,uint256,uint256) - handler: handleTrade - file: ./src/hypercert-trader.ts diff --git a/graph/tests/hypercert-trader-utils.ts b/graph/tests/hypercert-trader-utils.ts deleted file mode 100644 index 74ee37c1..00000000 --- a/graph/tests/hypercert-trader-utils.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { - AdminChanged, - BeaconUpgraded, - Initialized, - OfferCancelled, - OfferCreated, - OwnershipTransferred, - Paused, - Trade, - Unpaused, - Upgraded, -} from "../generated/HypercertTrader/HypercertTrader"; -import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"; -import { newMockEvent } from "matchstick-as"; - -export const DEFAULT_TRADER_ADDRESS = Address.fromString( - "0x0000000000000000000000000000000000001337", -); - -export function createAdminChangedEvent( - previousAdmin: Address, - newAdmin: Address, -): AdminChanged { - let adminChangedEvent = changetype(newMockEvent()); - - adminChangedEvent.parameters = new Array(); - - adminChangedEvent.parameters.push( - new ethereum.EventParam( - "previousAdmin", - ethereum.Value.fromAddress(previousAdmin), - ), - ); - adminChangedEvent.parameters.push( - new ethereum.EventParam("newAdmin", ethereum.Value.fromAddress(newAdmin)), - ); - - return adminChangedEvent; -} - -export function createBeaconUpgradedEvent(beacon: Address): BeaconUpgraded { - let beaconUpgradedEvent = changetype(newMockEvent()); - - beaconUpgradedEvent.parameters = new Array(); - - beaconUpgradedEvent.parameters.push( - new ethereum.EventParam("beacon", ethereum.Value.fromAddress(beacon)), - ); - - return beaconUpgradedEvent; -} - -export function createInitializedEvent(version: i32): Initialized { - let initializedEvent = changetype(newMockEvent()); - - initializedEvent.parameters = new Array(); - - initializedEvent.parameters.push( - new ethereum.EventParam( - "version", - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(version)), - ), - ); - - return initializedEvent; -} - -export function createOfferCancelledEvent( - creator: Address, - hypercertContract: Address, - fractionID: BigInt, - offerID: BigInt, -): OfferCancelled { - let offerCancelledEvent = changetype(newMockEvent()); - - offerCancelledEvent.parameters = new Array(); - - offerCancelledEvent.parameters.push( - new ethereum.EventParam("creator", ethereum.Value.fromAddress(creator)), - ); - offerCancelledEvent.parameters.push( - new ethereum.EventParam( - "hypercertContract", - ethereum.Value.fromAddress(hypercertContract), - ), - ); - offerCancelledEvent.parameters.push( - new ethereum.EventParam( - "fractionID", - ethereum.Value.fromUnsignedBigInt(fractionID), - ), - ); - offerCancelledEvent.parameters.push( - new ethereum.EventParam( - "offerID", - ethereum.Value.fromUnsignedBigInt(offerID), - ), - ); - - offerCancelledEvent.address = DEFAULT_TRADER_ADDRESS; - - return offerCancelledEvent; -} - -export function createOfferCreatedEvent( - offerer: Address, - hypercertContract: Address, - fractionID: BigInt, - offerID: BigInt, -): OfferCreated { - let offerCreatedEvent = changetype(newMockEvent()); - - offerCreatedEvent.parameters = new Array(); - - offerCreatedEvent.parameters.push( - new ethereum.EventParam("offerer", ethereum.Value.fromAddress(offerer)), - ); - offerCreatedEvent.parameters.push( - new ethereum.EventParam( - "hypercertContract", - ethereum.Value.fromAddress(hypercertContract), - ), - ); - offerCreatedEvent.parameters.push( - new ethereum.EventParam( - "fractionID", - ethereum.Value.fromUnsignedBigInt(fractionID), - ), - ); - offerCreatedEvent.parameters.push( - new ethereum.EventParam( - "offerID", - ethereum.Value.fromUnsignedBigInt(offerID), - ), - ); - - offerCreatedEvent.address = DEFAULT_TRADER_ADDRESS; - - return offerCreatedEvent; -} - -export function createOwnershipTransferredEvent( - previousOwner: Address, - newOwner: Address, -): OwnershipTransferred { - let ownershipTransferredEvent = changetype( - newMockEvent(), - ); - - ownershipTransferredEvent.parameters = new Array(); - - ownershipTransferredEvent.parameters.push( - new ethereum.EventParam( - "previousOwner", - ethereum.Value.fromAddress(previousOwner), - ), - ); - ownershipTransferredEvent.parameters.push( - new ethereum.EventParam("newOwner", ethereum.Value.fromAddress(newOwner)), - ); - - return ownershipTransferredEvent; -} - -export function createPausedEvent(account: Address): Paused { - let pausedEvent = changetype(newMockEvent()); - - pausedEvent.parameters = new Array(); - - pausedEvent.parameters.push( - new ethereum.EventParam("account", ethereum.Value.fromAddress(account)), - ); - - return pausedEvent; -} - -export function createTradeEvent( - seller: Address, - buyer: Address, - hypercertContract: Address, - fractionID: BigInt, - unitsBought: BigInt, - buyToken: Address, - tokenAmountPerUnit: BigInt, - offerID: BigInt, -): Trade { - let tradeEvent = changetype(newMockEvent()); - - tradeEvent.parameters = new Array(); - - tradeEvent.parameters.push( - new ethereum.EventParam("seller", ethereum.Value.fromAddress(seller)), - ); - tradeEvent.parameters.push( - new ethereum.EventParam("buyer", ethereum.Value.fromAddress(buyer)), - ); - tradeEvent.parameters.push( - new ethereum.EventParam( - "hypercertContract", - ethereum.Value.fromAddress(hypercertContract), - ), - ); - tradeEvent.parameters.push( - new ethereum.EventParam( - "fractionID", - ethereum.Value.fromUnsignedBigInt(fractionID), - ), - ); - tradeEvent.parameters.push( - new ethereum.EventParam( - "unitsBought", - ethereum.Value.fromUnsignedBigInt(unitsBought), - ), - ); - tradeEvent.parameters.push( - new ethereum.EventParam("buyToken", ethereum.Value.fromAddress(buyToken)), - ); - tradeEvent.parameters.push( - new ethereum.EventParam( - "tokenAmountPerUnit", - ethereum.Value.fromUnsignedBigInt(tokenAmountPerUnit), - ), - ); - tradeEvent.parameters.push( - new ethereum.EventParam( - "offerID", - ethereum.Value.fromUnsignedBigInt(offerID), - ), - ); - - tradeEvent.address = DEFAULT_TRADER_ADDRESS; - - return tradeEvent; -} - -export function createUnpausedEvent(account: Address): Unpaused { - let unpausedEvent = changetype(newMockEvent()); - - unpausedEvent.parameters = new Array(); - - unpausedEvent.parameters.push( - new ethereum.EventParam("account", ethereum.Value.fromAddress(account)), - ); - - return unpausedEvent; -} - -export function createUpgradedEvent(implementation: Address): Upgraded { - let upgradedEvent = changetype(newMockEvent()); - - upgradedEvent.parameters = new Array(); - - upgradedEvent.parameters.push( - new ethereum.EventParam( - "implementation", - ethereum.Value.fromAddress(implementation), - ), - ); - - return upgradedEvent; -} diff --git a/graph/tests/hypercert-trader.test.ts b/graph/tests/hypercert-trader.test.ts deleted file mode 100644 index 51230b74..00000000 --- a/graph/tests/hypercert-trader.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { handleOfferCreated } from "../src/hypercert-trader"; -import { ZERO_ADDRESS } from "../src/utils"; -import { - DEFAULT_TRADER_ADDRESS, - createOfferCreatedEvent, -} from "./hypercert-trader-utils"; -import { Address, BigInt, Bytes, ethereum } from "@graphprotocol/graph-ts"; -import { - assert, - describe, - test, - clearStore, - beforeAll, - afterAll, - createMockedFunction, -} from "matchstick-as/assembly/index"; - -// Tests structure (matchstick-as >=0.5.0) -// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 - -describe("Describe entity assertions", () => { - beforeAll(() => { - let acceptedToken: Array = [ - ethereum.Value.fromAddress( - Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7"), - ), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ]; - - let tuple = changetype(acceptedToken); - let tupleValue = ethereum.Value.fromTupleArray([tuple]); - - const returnValues = [ - ethereum.Value.fromAddress( - Address.fromString("0x0000000000000000000000000000000000000003"), - ), - ethereum.Value.fromAddress( - Address.fromString("0x0000000000000000000000000000000000000004"), - ), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1)), - ethereum.Value.fromTupleArray([tuple]), - ]; - - let returnValueTuple = changetype(returnValues); - let returnValue = ethereum.Value.fromTuple(returnValueTuple); - - createMockedFunction( - DEFAULT_TRADER_ADDRESS, - "getOffer", - "getOffer(uint256):((address,address,uint256,uint256,uint256,uint256,uint8,uint8,(address,uint256)[]))", - ) - .withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1))]) - .returns([returnValue]); - }); - - afterAll(() => { - clearStore(); - }); - - test("Offer created and stored", () => { - assert.entityCount("Offer", 0); - - // Create an OfferCreated event - let offerCreatedEvent = createOfferCreatedEvent( - Address.fromString("0x0000000000000000000000000000000000000001"), - Address.fromString("0x0000000000000000000000000000000000000002"), - BigInt.fromI32(1), - BigInt.fromI32(1), - ); - - handleOfferCreated(offerCreatedEvent); - - assert.entityCount("Offer", 1); - }); -}); From 6925ce531ed1b9cde3c5ca8db53dcdbf2c19f21d Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 13:40:36 +0200 Subject: [PATCH 07/15] chore(pnpm): refresh lock file --- pnpm-lock.yaml | 1644 ++++++++++++++++++++++++++---------------------- 1 file changed, 881 insertions(+), 763 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ca9cd3a..c9fd82dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,7 +65,7 @@ importers: version: 1.0.9(hardhat@2.13.1) '@nomicfoundation/hardhat-toolbox': specifier: ^2.0.0 - version: 2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@9.1.0)(@types/node@18.18.6)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.13.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@4.9.5) + version: 2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@9.1.0)(@types/node@18.18.7)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.13.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@4.9.5) '@nomiclabs/hardhat-ethers': specifier: ^2.2.1 version: 2.2.3(ethers@5.7.2)(hardhat@2.13.1) @@ -74,7 +74,7 @@ importers: version: 3.1.7(hardhat@2.13.1) '@openzeppelin/contracts': specifier: <5.0.0 - version: 4.3.3 + version: 4.9.3 '@openzeppelin/hardhat-defender': specifier: ^1.8.2 version: 1.9.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.13.1) @@ -110,7 +110,7 @@ importers: version: 9.1.0 '@types/node': specifier: ^18.11.11 - version: 18.18.6 + version: 18.18.7 '@typescript-eslint/eslint-plugin': specifier: ^5.38.0 version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.52.0)(typescript@4.9.5) @@ -122,7 +122,7 @@ importers: version: 4.3.10 commitizen: specifier: ^4.2.5 - version: 4.3.0 + version: 4.3.0(typescript@4.9.5) copyfiles: specifier: ^2.4.1 version: 2.4.1 @@ -131,7 +131,7 @@ importers: version: 7.0.3 cz-conventional-changelog: specifier: ^3.3.0 - version: 3.3.0 + version: 3.3.0(typescript@4.9.5) dotenv: specifier: ^16.0.2 version: 16.3.1 @@ -203,7 +203,7 @@ importers: version: 6.2.0 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + version: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) typechain: specifier: ^8.3.1 version: 8.3.2(typescript@4.9.5) @@ -215,7 +215,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: ^4.20230115.0 - version: 4.20231016.0 + version: 4.20231025.0 typescript: specifier: ^4.9.5 version: 4.9.5 @@ -233,13 +233,13 @@ importers: version: 0.8.11 '@openzeppelin/defender-autotask-client': specifier: ^1.48.0 - version: 1.49.0 + version: 1.50.0 '@openzeppelin/defender-autotask-utils': specifier: ^1.48.0 - version: 1.49.0 + version: 1.50.0 '@openzeppelin/defender-base-client': specifier: ^1.48.0 - version: 1.49.0(debug@4.3.4) + version: 1.50.0(debug@4.3.4) '@openzeppelin/defender-sentinel-client': specifier: ^1.48.0 version: 1.49.0 @@ -248,7 +248,7 @@ importers: version: 1.0.5 '@supabase/supabase-js': specifier: ^2.4.1 - version: 2.38.2 + version: 2.38.3 axios: specifier: ^1.2.6 version: 1.5.1(debug@4.3.4) @@ -264,7 +264,7 @@ importers: devDependencies: '@types/node': specifier: ^18.11.18 - version: 18.18.6 + version: 18.18.7 rimraf: specifier: ^5.0.5 version: 5.0.5 @@ -276,7 +276,7 @@ importers: version: 9.5.0(typescript@4.9.5)(webpack@5.89.0) ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + version: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) tsx: specifier: ^3.14.0 version: 3.14.0 @@ -349,13 +349,13 @@ importers: version: 1.1.0(@rainbow-me/rainbowkit@1.0.8)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(wagmi@1.3.9)(zod@3.22.4) '@emotion/react': specifier: ^11.10.5 - version: 11.11.1(@types/react@18.2.31)(react@18.2.0) + version: 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/styled': specifier: ^11.10.5 - version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) '@graphprotocol/client-cli': specifier: ^2.2.16 - version: 2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) + version: 2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) '@hypercerts-org/contracts': specifier: 0.8.11 version: 0.8.11 @@ -364,16 +364,16 @@ importers: version: link:../vendor/observabletreemap '@hypercerts-org/sdk': specifier: 0.8.16 - version: 0.8.16(@envelop/core@3.0.6)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3) + version: 0.8.16(@envelop/core@3.0.6)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3) '@mui/icons-material': specifier: ^5.11.9 - version: 5.14.14(@mui/material@5.14.14)(@types/react@18.2.31)(react@18.2.0) + version: 5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0) '@mui/material': specifier: ^5.11.2 - version: 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) + version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) '@mui/x-date-pickers': specifier: ^5.0.12 - version: 5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(@mui/system@5.14.14)(@types/react@18.2.31)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) + version: 5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(@mui/system@5.14.15)(@types/react@18.2.33)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) '@next/eslint-plugin-next': specifier: ^13.4.12 version: 13.5.6 @@ -391,16 +391,16 @@ importers: version: 1.0.333(next@13.5.6)(react-dom@18.2.0)(react@18.2.0) '@rainbow-me/rainbowkit': specifier: 1.0.8 - version: 1.0.8(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9) + version: 1.0.8(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9) '@sentry/nextjs': specifier: ^7.73.0 - version: 7.74.1(next@13.5.6)(react@18.2.0)(webpack@5.89.0) + version: 7.75.1(next@13.5.6)(react@18.2.0)(webpack@5.89.0) '@sentry/utils': specifier: ^7.73.0 - version: 7.74.1 + version: 7.75.1 '@supabase/supabase-js': specifier: ^2.1.2 - version: 2.38.2 + version: 2.38.3 '@tanstack/react-query': specifier: ^4.36.1 version: 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) @@ -433,10 +433,10 @@ importers: version: 2.4.5(react@18.2.0) formik-mui: specifier: ^5.0.0-alpha.0 - version: 5.0.0-alpha.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3) + version: 5.0.0-alpha.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3) formik-mui-x-date-pickers: specifier: ^0.0.1 - version: 0.0.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(@mui/system@5.14.14)(@mui/x-date-pickers@5.0.20)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3) + version: 0.0.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(@mui/system@5.14.15)(@mui/x-date-pickers@5.0.20)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3) graphql: specifier: ^16.6.0 version: 16.8.1 @@ -457,7 +457,7 @@ importers: version: 5.4.1 primereact: specifier: ^9.6.0 - version: 9.6.3(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) + version: 9.6.3(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) qs: specifier: ^6.11.0 version: 6.11.2 @@ -475,7 +475,7 @@ importers: version: 14.2.3(react@18.2.0) react-markdown: specifier: ^8.0.7 - version: 8.0.7(@types/react@18.2.31)(react@18.2.0) + version: 8.0.7(@types/react@18.2.33)(react@18.2.0) react-toastify: specifier: ^9.1.1 version: 9.1.3(react-dom@18.2.0)(react@18.2.0) @@ -490,7 +490,7 @@ importers: version: 1.5.3(typescript@5.1.6)(zod@3.22.4) wagmi: specifier: 1.3.9 - version: 1.3.9(@types/react@18.2.31)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) + version: 1.3.9(@types/react@18.2.33)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) yup: specifier: ^0.32.11 version: 0.32.11 @@ -509,7 +509,7 @@ importers: version: 14.5.1(@testing-library/dom@9.3.3) '@types/node': specifier: ^18.0.0 - version: 18.18.6 + version: 18.18.7 '@types/papaparse': specifier: ^5.3.7 version: 5.3.10 @@ -518,7 +518,7 @@ importers: version: 6.9.9 '@types/react': specifier: ^18.0.14 - version: 18.2.31 + version: 18.2.33 '@types/react-dom': specifier: ^18.0.5 version: 18.2.14 @@ -545,7 +545,7 @@ importers: version: 1.1.7 jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + version: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-environment-jsdom: specifier: ^29.5.0 version: 29.7.0 @@ -557,7 +557,7 @@ importers: devDependencies: '@graphprotocol/graph-cli': specifier: 0.60.0 - version: 0.60.0(@types/node@18.18.6)(node-fetch@3.3.2)(typescript@4.9.5) + version: 0.60.0(@types/node@18.18.7)(node-fetch@3.3.2)(typescript@4.9.5) '@graphprotocol/graph-ts': specifier: 0.31.0 version: 0.31.0 @@ -578,37 +578,37 @@ importers: version: 5.7.0 '@graphprotocol/client-add-source-name': specifier: ^1.0.16 - version: 1.0.20(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + version: 1.0.20(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) '@graphprotocol/client-polling-live': specifier: ^2.0.0 - version: 2.0.0(@envelop/core@3.0.6)(@graphql-tools/merge@9.0.0)(graphql@16.8.1) + version: 2.0.0(@envelop/core@4.0.3)(@graphql-tools/merge@9.0.0)(graphql@16.8.1) '@graphql-mesh/cache-localforage': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/cross-helpers': specifier: ^0.4.1 version: 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) '@graphql-mesh/graphql': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2) '@graphql-mesh/http': specifier: ^0.96.13 - version: 0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.12)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2) + version: 0.96.14(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.13)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/merger-bare': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/runtime': specifier: ^0.96.12 - version: 0.96.12(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + version: 0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/store': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/types': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/utils': specifier: ^0.95.7 - version: 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + version: 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@16.8.1) @@ -620,7 +620,7 @@ importers: version: 1.0.5 '@whatwg-node/fetch': specifier: ^0.9.13 - version: 0.9.13 + version: 0.9.14 ajv: specifier: ^8.11.2 version: 8.12.0 @@ -641,7 +641,7 @@ importers: version: 0.17.0(uint8arraylist@2.4.3) jest: specifier: ^29.3.1 - version: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + version: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) loglevel: specifier: ^1.8.1 version: 1.8.1 @@ -669,7 +669,7 @@ importers: version: 8.2.0 '@graphprotocol/client-cli': specifier: ^3.0.0 - version: 3.0.0(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@9.0.0)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) + version: 3.0.0(@babel/core@7.23.2)(@envelop/core@4.0.3)(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@9.0.0)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) '@jest/globals': specifier: ^29.7.0 version: 29.7.0 @@ -696,7 +696,7 @@ importers: version: 9.1.0 '@types/node': specifier: ^18.11.17 - version: 18.18.6 + version: 18.18.7 '@types/sinon': specifier: ^10.0.15 version: 10.0.20 @@ -765,7 +765,7 @@ importers: version: 10.0.0(mocha@10.2.0) ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + version: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) tslib: specifier: ^2.5.0 version: 2.6.2 @@ -809,7 +809,7 @@ packages: resolution: {integrity: sha512-rU4G75TEOTIPlkeDnPEVwx/VmMMFta42kY2SMmVobRkrtNLnxtU08Yhriu6tSBc9oO0wXdfNNeuLnNnEnL7w/A==} dependencies: '@achingbrain/ssdp': 4.0.6 - '@libp2p/logger': 3.0.3 + '@libp2p/logger': 3.0.4 default-gateway: 7.2.2 err-code: 3.0.1 it-first: 3.0.3 @@ -1000,8 +1000,8 @@ packages: optional: true dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) - '@wry/context': 0.7.3 - '@wry/equality': 0.5.6 + '@wry/context': 0.7.4 + '@wry/equality': 0.5.7 '@wry/trie': 0.4.3 graphql: 16.8.1 graphql-tag: 2.12.6(graphql@16.8.1) @@ -2635,10 +2635,10 @@ packages: wagmi: '>=1.0.0 && <=2.0.0' dependencies: '@metamask/providers': 11.1.2 - '@rainbow-me/rainbowkit': 1.0.8(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9) - '@wagmi/connectors': 2.6.6(@wagmi/chains@1.6.0)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) + '@rainbow-me/rainbowkit': 1.0.8(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9) + '@wagmi/connectors': 2.7.0(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) viem: 1.5.3(typescript@5.1.6)(zod@3.22.4) - wagmi: 1.3.9(@types/react@18.2.31)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) + wagmi: 1.3.9(@types/react@18.2.33)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) transitivePeerDependencies: - '@react-native-async-storage/async-storage' - '@wagmi/chains' @@ -2765,8 +2765,8 @@ packages: mime: 3.0.0 dev: true - /@cloudflare/workers-types@4.20231016.0: - resolution: {integrity: sha512-eGB0cRVyoJpeyGJx2re5sbd9R316a61sY73xwnqm4cwGpb+OxCK2gc651RxGiN7H4w6LY1RpysUgeGLmj5B3+g==} + /@cloudflare/workers-types@4.20231025.0: + resolution: {integrity: sha512-TkcZkntUTOcvJ4vgmwpNfLTclpMbmbClZCe62B25/VTukmyv91joRa4eKzSjzCZUXTbFHNmVdOpmGaaJU2U3+A==} dev: true /@coinbase/wallet-sdk@3.7.2: @@ -2838,12 +2838,12 @@ packages: ajv: 8.12.0 dev: true - /@commitlint/config-validator@18.0.0: - resolution: {integrity: sha512-PlXy5QZzQeMgQM7jb0odIhxsI6GWcbGgfy+Hkz5ap31KES/oJgtEvgD8pjg0Z9Ri296bT6zK3ts6brS0MAcMgg==} + /@commitlint/config-validator@18.1.0: + resolution: {integrity: sha512-kbHkIuItXn93o2NmTdwi5Mk1ujyuSIysRE/XHtrcps/27GuUKEIqBJp6TdJ4Sq+ze59RlzYSHMKuDKZbfg9+uQ==} engines: {node: '>=v18'} requiresBuild: true dependencies: - '@commitlint/types': 18.0.0 + '@commitlint/types': 18.1.0 ajv: 8.12.0 dev: true optional: true @@ -2865,8 +2865,8 @@ packages: engines: {node: '>=v14'} dev: true - /@commitlint/execute-rule@18.0.0: - resolution: {integrity: sha512-eNUSaHajb+g3sgZeIrfc6cXNnKIkYN2SXtDVXuiE+hOa055T0bLdZK29gSd945JCztxPVwdOkPLDeLg3NfDubg==} + /@commitlint/execute-rule@18.1.0: + resolution: {integrity: sha512-w3Vt4K+O7+nSr9/gFSEfZ1exKUOPSlJaRpnk7Y+XowEhvwT7AIk1HNANH+gETf0zGZ020+hfiMW/Ome+SNCUsg==} engines: {node: '>=v18'} requiresBuild: true dev: true @@ -2921,28 +2921,25 @@ packages: - '@swc/wasm' dev: true - /@commitlint/load@18.0.0: - resolution: {integrity: sha512-ocvMSkzNZCJ4yV673xjd4Y7sFVG/mg7S6yvL5ioM0OIG2XTbcCdzpmq+BeJcIwsRYU9g/b688yh7RDzGlbai6w==} + /@commitlint/load@18.2.0(typescript@4.9.5): + resolution: {integrity: sha512-xjX3d3CRlOALwImhOsmLYZh14/+gW/KxsY7+bPKrzmGuFailf9K7ckhB071oYZVJdACnpY4hDYiosFyOC+MpAA==} engines: {node: '>=v18'} requiresBuild: true dependencies: - '@commitlint/config-validator': 18.0.0 - '@commitlint/execute-rule': 18.0.0 - '@commitlint/resolve-extends': 18.0.0 - '@commitlint/types': 18.0.0 - '@types/node': 18.18.6 + '@commitlint/config-validator': 18.1.0 + '@commitlint/execute-rule': 18.1.0 + '@commitlint/resolve-extends': 18.1.0 + '@commitlint/types': 18.1.0 + '@types/node': 18.18.7 chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.2.2) - cosmiconfig-typescript-loader: 4.4.0(@types/node@18.18.6)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.2.2) + cosmiconfig: 8.3.6(typescript@4.9.5) + cosmiconfig-typescript-loader: 5.0.0(@types/node@18.18.7)(cosmiconfig@8.3.6)(typescript@4.9.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@5.2.2) - typescript: 5.2.2 transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + - typescript dev: true optional: true @@ -2983,13 +2980,13 @@ packages: resolve-global: 1.0.0 dev: true - /@commitlint/resolve-extends@18.0.0: - resolution: {integrity: sha512-MD9+6GSiWvqgdJtfos+1gqz+zmy2vV7TbUVz2ETZzpfECgmUZSZSYzyivivBAQK6feS71KxmMLL8+YFF9+FFRQ==} + /@commitlint/resolve-extends@18.1.0: + resolution: {integrity: sha512-3mZpzOEJkELt7BbaZp6+bofJyxViyObebagFn0A7IHaLARhPkWTivXdjvZHS12nAORftv88Yhbh8eCPKfSvB7g==} engines: {node: '>=v18'} requiresBuild: true dependencies: - '@commitlint/config-validator': 18.0.0 - '@commitlint/types': 18.0.0 + '@commitlint/config-validator': 18.1.0 + '@commitlint/types': 18.1.0 import-fresh: 3.3.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 @@ -3027,8 +3024,8 @@ packages: chalk: 4.1.2 dev: true - /@commitlint/types@18.0.0: - resolution: {integrity: sha512-FDzAdSm7kIir0NW0bZLENdrEgf/9Ihs1AAqE9DK9R+dRFby4ookkxPMaz7elZmG+e5rBl7hGrWJzJINqG9cDDg==} + /@commitlint/types@18.1.0: + resolution: {integrity: sha512-65vGxZmbs+2OVwEItxhp3Ul7X2m2LyLfifYI/NdPwRqblmuES2w2aIRhIjb7cwUIBHHSTT8WXj4ixVHQibmvLQ==} engines: {node: '>=v18'} requiresBuild: true dependencies: @@ -3042,8 +3039,8 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 - /@cypress/code-coverage@3.12.5(@babel/core@7.23.2)(@babel/preset-env@7.23.2)(babel-loader@9.1.3)(cypress@12.17.4)(webpack@5.89.0): - resolution: {integrity: sha512-0hczq2kgzkh/fBLm74rHcDRX//W3bhJJw/aPWu57/pPaRp5c+LcatWuv8ZtIWNXit2kBClueOrVj0I20Arh80A==} + /@cypress/code-coverage@3.12.6(@babel/core@7.23.2)(@babel/preset-env@7.23.2)(babel-loader@9.1.3)(cypress@12.17.4)(webpack@5.89.0): + resolution: {integrity: sha512-QeOB54dkxee8LKdbK9NqMinlMtzy60K2DexSvx4XZdrU52gut9Ukc7sUvRe44gK1yJFYno977Kx9miNIeYWVtA==} peerDependencies: '@babel/core': ^7.0.1 '@babel/preset-env': ^7.0.0 @@ -3309,7 +3306,7 @@ packages: react-router: 5.3.4(react@18.2.0) react-router-config: 5.1.1(react-router@5.3.4)(react@18.2.0) react-router-dom: 5.3.4(react@18.2.0) - rtl-detect: 1.0.4 + rtl-detect: 1.1.2 semver: 7.5.4 serve-handler: 6.1.5 shelljs: 0.8.5 @@ -3418,7 +3415,7 @@ packages: '@docusaurus/react-loadable': 5.5.2(react@18.2.0) '@docusaurus/types': 2.4.3(react-dom@18.2.0)(react@18.2.0) '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/react-router-config': 5.0.9 '@types/react-router-dom': 5.3.3 react: 18.2.0 @@ -3765,7 +3762,7 @@ packages: peerDependencies: react: '*' dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 prop-types: 15.8.1 react: 18.2.0 @@ -3836,7 +3833,7 @@ packages: '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3) '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3) '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/react-router-config': 5.0.9 clsx: 1.2.1 parse-numeric-range: 1.3.0 @@ -3916,7 +3913,7 @@ packages: '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3) '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3) algoliasearch: 4.20.0 - algoliasearch-helper: 3.14.2(algoliasearch@4.20.0) + algoliasearch-helper: 3.15.0(algoliasearch@4.20.0) clsx: 1.2.1 eta: 2.2.0 fs-extra: 10.1.0 @@ -3962,7 +3959,7 @@ packages: react-dom: ^16.8.4 || ^17.0.0 dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 commander: 5.1.0 joi: 17.11.0 react: 18.2.0 @@ -4093,7 +4090,7 @@ packages: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: false - /@emotion/react@11.11.1(@types/react@18.2.31)(react@18.2.0): + /@emotion/react@11.11.1(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==} peerDependencies: '@types/react': '*' @@ -4109,7 +4106,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.31 + '@types/react': 18.2.33 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false @@ -4128,7 +4125,7 @@ packages: resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} dev: false - /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0): + /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 @@ -4141,11 +4138,11 @@ packages: '@babel/runtime': 7.23.2 '@emotion/babel-plugin': 11.11.0 '@emotion/is-prop-valid': 1.2.1 - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/serialize': 1.1.2 '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 - '@types/react': 18.2.31 + '@types/react': 18.2.33 react: 18.2.0 dev: false @@ -4191,6 +4188,13 @@ packages: '@envelop/types': 3.0.2 tslib: 2.6.2 + /@envelop/core@4.0.3: + resolution: {integrity: sha512-O0Vz8E0TObT6ijAob8jYFVJavcGywKThM3UAsxUIBBVPYZTMiqI9lo2gmAnbMUnrDcAYkUTZEW9FDYPRdF5l6g==} + engines: {node: '>=16.0.0'} + dependencies: + '@envelop/types': 4.0.1 + tslib: 2.6.2 + /@envelop/core@5.0.0: resolution: {integrity: sha512-aJdnH/ptv+cvwfvciCBe7TSvccBwo9g0S5f6u35TBVzRVqIGkK03lFlIL+x1cnfZgN9EfR2b1PH2galrT1CdCQ==} engines: {node: '>=18.0.0'} @@ -4242,6 +4246,12 @@ packages: dependencies: tslib: 2.6.2 + /@envelop/types@4.0.1: + resolution: {integrity: sha512-ULo27/doEsP7uUhm2iTnElx13qTO6I5FKvmLoX41cpfuw8x6e0NUFknoqhEsLzAbgz8xVS5mjwcxGCXh4lDYzg==} + engines: {node: '>=16.0.0'} + dependencies: + tslib: 2.6.2 + /@envelop/types@5.0.0: resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==} engines: {node: '>=18.0.0'} @@ -5090,8 +5100,8 @@ packages: eslint: 8.52.0 eslint-visitor-keys: 3.4.3 - /@eslint-community/regexpp@4.9.1: - resolution: {integrity: sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==} + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} /@eslint/eslintrc@1.4.1: @@ -5848,7 +5858,7 @@ packages: tslib: 2.6.2 dev: false - /@graphprotocol/client-add-source-name@1.0.20(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): + /@graphprotocol/client-add-source-name@1.0.20(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): resolution: {integrity: sha512-JJ++BVg4fhNCbLej105uHpabZesLsCSo9p43ZKSTT1VUdbuZtarzyIHC3uUmbvCfWQMVTCJEBZGx4l41oooOiw==} peerDependencies: '@graphql-mesh/types': ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 @@ -5857,7 +5867,7 @@ packages: '@graphql-tools/wrap': ^9.4.2 graphql: ^16.6.0 dependencies: - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) @@ -5866,7 +5876,7 @@ packages: tslib: 2.6.2 dev: false - /@graphprotocol/client-add-source-name@2.0.0(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): + /@graphprotocol/client-add-source-name@2.0.0(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): resolution: {integrity: sha512-3vX8mVPIEJFwAoRhjTPd9IjQrBuE+Gv+JB7IEf8/9222qiU9EzHVFUekKxVtcxQXD40CfageS41CxOreWQ1enA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -5876,7 +5886,7 @@ packages: '@graphql-tools/wrap': ^9.4.2 || ^10.0.0 graphql: ^16.6.0 dependencies: - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) @@ -5903,7 +5913,7 @@ packages: tslib: 2.6.2 dev: false - /@graphprotocol/client-auto-pagination@2.0.0(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): + /@graphprotocol/client-auto-pagination@2.0.0(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): resolution: {integrity: sha512-TouHgs6rQLpZSgnMoPdes8/ZTtMMEoxWeUUCkfho/xfSi49prb5DcsI83pykln0OEAUnNPnaX0MhP+xA5LtFSg==} engines: {node: '>=16.0.0'} peerDependencies: @@ -5913,7 +5923,7 @@ packages: '@graphql-tools/wrap': ^9.4.2 || ^10.0.0 graphql: ^16.6.0 dependencies: - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) @@ -5938,7 +5948,7 @@ packages: - '@graphql-mesh/utils' dev: false - /@graphprotocol/client-auto-type-merging@2.0.0(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1): + /@graphprotocol/client-auto-type-merging@2.0.0(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1): resolution: {integrity: sha512-mxqXKHK2lO+k4r02Q44n3qhd5dufo+SSDduD8zGUDBsYcRQAtQD9PwmXRHyUoB9nw4A+NC+CtVh+76fueXCG1w==} engines: {node: '>=16.0.0'} peerDependencies: @@ -5946,8 +5956,8 @@ packages: '@graphql-tools/delegate': ^9.0.32 || ^10.0.0 graphql: ^16.6.0 dependencies: - '@graphql-mesh/transform-type-merging': 0.93.1(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/transform-type-merging': 0.93.1(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) graphql: 16.8.1 tslib: 2.6.2 @@ -5980,7 +5990,7 @@ packages: tslib: 2.6.2 dev: true - /@graphprotocol/client-cli@2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): + /@graphprotocol/client-cli@2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): resolution: {integrity: sha512-PIi8rFibYZVup+0jb08399RmbGF1ZrqUe6RXzLtKZBT57OWIMWwsFvdJyUAdr8Y8f0rrMn6A+Oy4nP1lf3hc1g==} hasBin: true peerDependencies: @@ -5991,8 +6001,8 @@ packages: '@graphprotocol/client-auto-type-merging': 1.0.25(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) '@graphprotocol/client-block-tracking': 1.0.14(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) '@graphprotocol/client-polling-live': 1.1.1(@envelop/core@3.0.6)(@graphql-tools/merge@8.4.2)(graphql@16.8.1) - '@graphql-mesh/cli': 0.82.35(@babel/core@7.23.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) - '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/cli': 0.82.35(@babel/core@7.23.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) + '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2) graphql: 16.8.1 tslib: 2.6.2 transitivePeerDependencies: @@ -6018,20 +6028,20 @@ packages: - utf-8-validate dev: false - /@graphprotocol/client-cli@3.0.0(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@9.0.0)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): + /@graphprotocol/client-cli@3.0.0(@babel/core@7.23.2)(@envelop/core@4.0.3)(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@9.0.0)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): resolution: {integrity: sha512-hTISbOzKavlDifBNsR6JqQMfdYwY7++hflPy+c3WHRrZ4OMoxFmW7ZuvaP6LvgKdJV77O8w9dnT/uxeHs6a90g==} engines: {node: '>=16.0.0'} hasBin: true peerDependencies: graphql: ^16.6.0 dependencies: - '@graphprotocol/client-add-source-name': 2.0.0(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) - '@graphprotocol/client-auto-pagination': 2.0.0(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) - '@graphprotocol/client-auto-type-merging': 2.0.0(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) + '@graphprotocol/client-add-source-name': 2.0.0(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + '@graphprotocol/client-auto-pagination': 2.0.0(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + '@graphprotocol/client-auto-type-merging': 2.0.0(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) '@graphprotocol/client-block-tracking': 2.0.0(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) - '@graphprotocol/client-polling-live': 2.0.0(@envelop/core@3.0.6)(@graphql-tools/merge@9.0.0)(graphql@16.8.1) - '@graphql-mesh/cli': 0.82.35(@babel/core@7.23.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) - '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(tslib@2.6.2) + '@graphprotocol/client-polling-live': 2.0.0(@envelop/core@4.0.3)(@graphql-tools/merge@9.0.0)(graphql@16.8.1) + '@graphql-mesh/cli': 0.82.35(@babel/core@7.23.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) + '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2) graphql: 16.8.1 tslib: 2.6.2 transitivePeerDependencies: @@ -6086,7 +6096,7 @@ packages: tslib: 2.6.2 dev: false - /@graphprotocol/client-polling-live@2.0.0(@envelop/core@3.0.6)(@graphql-tools/merge@9.0.0)(graphql@16.8.1): + /@graphprotocol/client-polling-live@2.0.0(@envelop/core@4.0.3)(@graphql-tools/merge@9.0.0)(graphql@16.8.1): resolution: {integrity: sha512-JQ0sKiFCX+ErR0fynBNUg/WDiVaaEndlS12fkgrFZrQA2vVpSyow9pW0nKMGVZJa4cN+VDskgwqK5BWXMvdeRA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -6094,21 +6104,21 @@ packages: '@graphql-tools/merge': ^8.3.14 || ^9.0.0 graphql: ^16.6.0 dependencies: - '@envelop/core': 3.0.6 + '@envelop/core': 4.0.3 '@graphql-tools/merge': 9.0.0(graphql@16.8.1) '@repeaterjs/repeater': 3.0.4 graphql: 16.8.1 tslib: 2.6.2 - /@graphprotocol/graph-cli@0.60.0(@types/node@18.18.6)(node-fetch@3.3.2)(typescript@4.9.5): + /@graphprotocol/graph-cli@0.60.0(@types/node@18.18.7)(node-fetch@3.3.2)(typescript@4.9.5): resolution: {integrity: sha512-8tGaQJ0EzAPtkDXCAijFGoVdJXM+pKFlGxjiU31TdG5bS4cIUoSB6yWojVsFFod0yETAwf+giel/0/8sudYsDw==} engines: {node: '>=14'} hasBin: true dependencies: '@float-capital/float-subgraph-uncrashable': 0.0.0-internal-testing.5 - '@oclif/core': 2.8.6(@types/node@18.18.6)(typescript@4.9.5) - '@oclif/plugin-autocomplete': 2.3.10(@types/node@18.18.6)(typescript@4.9.5) - '@oclif/plugin-not-found': 2.4.3(@types/node@18.18.6)(typescript@4.9.5) + '@oclif/core': 2.8.6(@types/node@18.18.7)(typescript@4.9.5) + '@oclif/plugin-autocomplete': 2.3.10(@types/node@18.18.7)(typescript@4.9.5) + '@oclif/plugin-not-found': 2.4.3(@types/node@18.18.7)(typescript@4.9.5) '@whatwg-node/fetch': 0.8.8 assemblyscript: 0.19.23 binary-install-raw: 0.0.13(debug@4.3.4) @@ -6363,23 +6373,23 @@ packages: localforage: 1.10.0 tslib: 2.6.2 - /@graphql-mesh/cache-localforage@0.95.7(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-/e9sFn0kgSxGE6O/GWfdGnFMOIfk1Y+IZwRqPIofHmIPdyC5cZ/gnkN6oRQv7nnx+c9hzQQ5OnxiOGdOKwb1cg==} + /@graphql-mesh/cache-localforage@0.95.8(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-PgCTHh1dLwjmusWEWAMQkglL7gR8VyyT9pzTcYBVFhGYNXysepCrl85QtaqtEMnR/YijgpCWaKGIYK+bosQZsg==} engines: {node: '>=16.0.0'} peerDependencies: - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) graphql: 16.8.1 localforage: 1.10.0 tslib: 2.6.2 dev: false - /@graphql-mesh/cli@0.82.35(@babel/core@7.23.2)(@types/node@18.18.6)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): + /@graphql-mesh/cli@0.82.35(@babel/core@7.23.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): resolution: {integrity: sha512-5IuXpk+Zpg05u6qNPX19VzC5/HCiLdDRF6EPZ3ze57FIRgGA3YsB1CUGga6Ky3inalURYwx0kWqmdjbdKZYx1w==} hasBin: true peerDependencies: @@ -6413,7 +6423,7 @@ packages: open: 7.4.2 pascal-case: 3.1.2 rimraf: 5.0.5 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@5.1.6) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@5.1.6) tsconfig-paths: 4.2.0 tslib: 2.6.2 typescript: 5.1.6 @@ -6493,7 +6503,7 @@ packages: graphql: 16.8.1 path-browserify: 1.0.1 - /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(tslib@2.6.2): + /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-1G2/1jkl1VPWhsZsUBwFQI5d9OxxEc+CMxy5ef0qI2WEXqIocOxMhEY53cc+tCSbuXR99rxos+KD/8Z6ZasaOQ==} peerDependencies: '@graphql-mesh/cross-helpers': ^0.3.4 @@ -6510,7 +6520,7 @@ packages: '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/utils': 0.93.2(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) - '@graphql-tools/url-loader': 7.17.18(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/url-loader': 7.17.18(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) graphql: 16.8.1 @@ -6523,7 +6533,7 @@ packages: - utf-8-validate dev: false - /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(tslib@2.6.2): + /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-1G2/1jkl1VPWhsZsUBwFQI5d9OxxEc+CMxy5ef0qI2WEXqIocOxMhEY53cc+tCSbuXR99rxos+KD/8Z6ZasaOQ==} peerDependencies: '@graphql-mesh/cross-helpers': ^0.3.4 @@ -6535,12 +6545,12 @@ packages: tslib: ^2.4.0 dependencies: '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/store': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/string-interpolation': 0.4.4(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) - '@graphql-tools/url-loader': 7.17.18(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/url-loader': 7.17.18(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) graphql: 16.8.1 @@ -6553,26 +6563,26 @@ packages: - utf-8-validate dev: true - /@graphql-mesh/graphql@0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2): - resolution: {integrity: sha512-Fjf1Ti2HYOEP+dFLVnVxafD/Z4Ev+sR6BUbx3E7Mw8r/XGY28KmCA/QftBOB6BRNKMLe5w7RsgjCrO+Qp0klNg==} + /@graphql-mesh/graphql@0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2): + resolution: {integrity: sha512-mEbz2XYSgRTdNidUBWB7FT3QzLliJwxJIoqipSbZNputJqSbUZZ6QD/oI1IrdPXqVl/ELE2CuLiogkOSO24C1Q==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/store': ^0.95.7 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/store': ^0.95.8 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/store': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/string-interpolation': 0.5.2(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) - '@graphql-tools/federation': 1.1.10(@types/node@18.18.6)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0) - '@graphql-tools/url-loader': 8.0.0(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/federation': 1.1.10(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0) + '@graphql-tools/url-loader': 8.0.0(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) graphql: 16.8.1 lodash.get: 4.4.2 @@ -6607,22 +6617,22 @@ packages: graphql-yoga: 3.9.1(graphql@16.8.1) tslib: 2.6.2 - /@graphql-mesh/http@0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.12)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-WNiOJkwuRKoVCv/+9bp8/PFdclyTN0COIwSXjzIf36QICPtUXhokPLkXKhR7Xdtk175aIIpUHYRRwlgQw3BC1w==} + /@graphql-mesh/http@0.96.14(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.13)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-38Mxw2K2RABBBO0IiXKZDu2o+jlM4vcUSEg+9h2Dz67oOJZHpKeId6z1PFb7uYMzAs29yoMcqXIEnews+HVhrQ==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/runtime': ^0.96.12 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/runtime': ^0.96.13 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/runtime': 0.96.12(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@whatwg-node/server': 0.9.15 + '@graphql-mesh/runtime': 0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@whatwg-node/server': 0.9.16 graphql: 16.8.1 graphql-yoga: 5.0.0(graphql@16.8.1) tslib: 2.6.2 @@ -6647,19 +6657,19 @@ packages: transitivePeerDependencies: - '@graphql-mesh/store' - /@graphql-mesh/merger-bare@0.95.7(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-QNLm5otrzcpClR8Puks4Md7Mh6AON+EWK+l3NBKvEkiOINFcnDRFv4FrSEXSfrAv/vHrSBbxAEXGUWHjjbQ8Kw==} + /@graphql-mesh/merger-bare@0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-E5R8Sv5Dkp+eswYKEDHgu8puwSeolPX1j9IHwBVe1npRRCXc3CjMsQJ9+kcTln453vbSBcM1a3fQspIaKA1Tcg==} engines: {node: '>=16.0.0'} peerDependencies: - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: - '@graphql-mesh/merger-stitching': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/merger-stitching': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/schema': 10.0.0(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) graphql: 16.8.1 @@ -6689,20 +6699,20 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-mesh/merger-stitching@0.95.7(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-0fooZHNseNrrVIm+OPfy7NdN1f/Cq6yhpW7d9lXjB8kPWjRGaX6gBUxpfCsRqSrfBDP1VwusZ6Z9EmW+84AtCQ==} + /@graphql-mesh/merger-stitching@0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-eAukU8AsjK8jIT3vFhalGoERh98xZgzKkTCQL7w2wPpFXveSDMn+9fVvCJ1EBKTsLa7SkNXqzAFkfYp21hW0ng==} engines: {node: '>=16.0.0'} peerDependencies: - '@graphql-mesh/store': ^0.95.7 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/store': ^0.95.8 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: - '@graphql-mesh/store': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/schema': 10.0.0(graphql@16.8.1) '@graphql-tools/stitch': 9.0.3(graphql@16.8.1) @@ -6736,13 +6746,13 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-mesh/runtime@0.96.12(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-b3a/XjbTtS8gF30wu35M4pA2KyUYkYcWlnYNGXWOObtdEtEXjj+GkX//yO2XzTGI/sGWKElAAKIv4asPsye4jA==} + /@graphql-mesh/runtime@0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-eZIW/gdEVLvCLEEae8e3lny7d89CFfDyu0Z0xu4yVEdYeVpG9Ki2mDYFHztusIIkZikecvdsoM9MZX6LYcPOkg==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 @@ -6751,7 +6761,7 @@ packages: '@envelop/extended-validation': 4.0.0(@envelop/core@5.0.0)(graphql@16.8.1) '@envelop/graphql-jit': 8.0.1(@envelop/core@5.0.0)(graphql@16.8.1) '@graphql-mesh/cross-helpers': 0.3.4(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(react-native@0.72.6) - '@graphql-mesh/string-interpolation': 0.5.2(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/utils': 0.93.2(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/batch-delegate': 9.0.0(graphql@16.8.1) @@ -6759,19 +6769,19 @@ packages: '@graphql-tools/executor': 1.2.0(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 10.0.1(graphql@16.8.1) - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 graphql: 16.8.1 graphql-jit: 0.8.2(graphql@16.8.1) tslib: 2.6.2 dev: false - /@graphql-mesh/runtime@0.96.12(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-b3a/XjbTtS8gF30wu35M4pA2KyUYkYcWlnYNGXWOObtdEtEXjj+GkX//yO2XzTGI/sGWKElAAKIv4asPsye4jA==} + /@graphql-mesh/runtime@0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-eZIW/gdEVLvCLEEae8e3lny7d89CFfDyu0Z0xu4yVEdYeVpG9Ki2mDYFHztusIIkZikecvdsoM9MZX6LYcPOkg==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 @@ -6780,15 +6790,15 @@ packages: '@envelop/extended-validation': 4.0.0(@envelop/core@5.0.0)(graphql@16.8.1) '@envelop/graphql-jit': 8.0.1(@envelop/core@5.0.0)(graphql@16.8.1) '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/string-interpolation': 0.5.2(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/batch-delegate': 9.0.0(graphql@16.8.1) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/executor': 1.2.0(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 10.0.1(graphql@16.8.1) - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 graphql: 16.8.1 graphql-jit: 0.8.2(graphql@16.8.1) tslib: 2.6.2 @@ -6812,21 +6822,21 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-mesh/store@0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-4T5MnkdV70gPzM3Hj+Er2Qg4FTzePzbzGdHdWRSbW++4K+05Hbe1gKPix2f3s3BGkAO4Et5XkkdILL5QynhQFw==} + /@graphql-mesh/store@0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-29lpMcvqS1DM9alUOCyj6he2V7ZzG/DZxkerRefT8Mo5FexwJZI3LeI0YHNSY9Cq0x8KzRoH1TWcTTN/1PDRRw==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/types': ^0.95.7 - '@graphql-mesh/utils': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: '@graphql-inspector/core': 5.0.1(graphql@16.8.1) '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) graphql: 16.8.1 tslib: 2.6.2 @@ -6843,8 +6853,8 @@ packages: lodash.get: 4.4.2 tslib: 2.6.2 - /@graphql-mesh/string-interpolation@0.5.2(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-TkSAJ9pj1zesQyDlHrEUevVGOc1s/z9IQC0AONcpMHAunb8uYGO4Yryl8JIRIvDl5DlexHt3z8kLDNCInRGWNQ==} + /@graphql-mesh/string-interpolation@0.5.3(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-/R4kj3M1uqUie/7RZ58zgRrT8RBrDsCCR6ii00s62DbLsl+jZYOZFyTqHGsFbP7L7aHnl0fo1dwhEJIs+rjCLg==} engines: {node: '>=16.0.0'} peerDependencies: graphql: ^16.6.0 @@ -6872,7 +6882,7 @@ packages: tslib: 2.6.2 dev: false - /@graphql-mesh/transform-type-merging@0.93.1(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2): + /@graphql-mesh/transform-type-merging@0.93.1(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-CUrqCMaEqO1LDusv59UPqmQju3f+LpEGxFu7CydMiIvbfKDDDrf8+dF3OVU7d/ZOMRxB6hR80JsQF0SVeXPCOQ==} peerDependencies: '@graphql-mesh/types': ^0.93.1 @@ -6880,8 +6890,8 @@ packages: graphql: ^16.6.0 tslib: ^2.4.0 dependencies: - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/stitching-directives': 2.3.34(graphql@16.8.1) graphql: 16.8.1 @@ -6904,16 +6914,16 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-mesh/types@0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-afM2uuGR//lBoDrQvyfDmCcPwObpouRauahKVrXGyxkWe9LuIBG+scBZcynSbKotO1SgFcbJtToafMAIk5CefQ==} + /@graphql-mesh/types@0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-H2xh5KGc3+Ly3VdAPnRdKTibZpW9zEFgUzsozL9MQhCs6WLX+/kOADb0uIDqYFKX5c/2axmcy87BFNOausXYig==} engines: {node: '>=16.0.0'} peerDependencies: - '@graphql-mesh/store': ^0.95.7 + '@graphql-mesh/store': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: - '@graphql-mesh/store': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/batch-delegate': 9.0.0(graphql@16.8.1) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) @@ -6935,7 +6945,7 @@ packages: '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) - dset: 3.1.2 + dset: 3.1.3 graphql: 16.8.1 js-yaml: 4.1.0 lodash.get: 4.4.2 @@ -6943,23 +6953,23 @@ packages: tiny-lru: 8.0.2 tslib: 2.6.2 - /@graphql-mesh/utils@0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-6YQTMTrLt6m/cAdesrBgbGVSrLd+68xTo1dRIhxUFHSgucSAqA47Q8E71Lc9cLHh80HQT+/pauKHHG40csy1Ng==} + /@graphql-mesh/utils@0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-gH2/kXvxMHVWMX8DppIIZpFfSUaoKDJ6eQHFoAAsdabGE+vLtVk0OEYqMGVGtD/8ZDFa/P6CmwXc6hBzoLY6Kg==} engines: {node: '>=16.0.0'} peerDependencies: '@graphql-mesh/cross-helpers': ^0.4.1 - '@graphql-mesh/types': ^0.95.7 + '@graphql-mesh/types': ^0.95.8 '@graphql-tools/utils': ^9.2.1 || ^10.0.0 graphql: ^16.6.0 tslib: ^2.4.0 dependencies: '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/string-interpolation': 0.5.2(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) - '@whatwg-node/fetch': 0.9.13 - dset: 3.1.2 + '@whatwg-node/fetch': 0.9.14 + dset: 3.1.3 graphql: 16.8.1 js-yaml: 4.1.0 lodash.get: 4.4.2 @@ -7093,7 +7103,7 @@ packages: - utf-8-validate dev: false - /@graphql-tools/executor-http@0.1.10(@types/node@18.18.6)(graphql@16.8.1): + /@graphql-tools/executor-http@0.1.10(@types/node@18.18.7)(graphql@16.8.1): resolution: {integrity: sha512-hnAfbKv0/lb9s31LhWzawQ5hghBfHS+gYWtqxME6Rl0Aufq9GltiiLBcl7OVVOnkLF0KhwgbYP1mB5VKmgTGpg==} peerDependencies: graphql: ^16.6.0 @@ -7101,16 +7111,16 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@repeaterjs/repeater': 3.0.4 '@whatwg-node/fetch': 0.8.8 - dset: 3.1.2 + dset: 3.1.3 extract-files: 11.0.0 graphql: 16.8.1 - meros: 1.3.0(@types/node@18.18.6) + meros: 1.3.0(@types/node@18.18.7) tslib: 2.6.2 value-or-promise: 1.0.12 transitivePeerDependencies: - '@types/node' - /@graphql-tools/executor-http@1.0.3(@types/node@18.18.6)(graphql@16.8.1): + /@graphql-tools/executor-http@1.0.3(@types/node@18.18.7)(graphql@16.8.1): resolution: {integrity: sha512-5WZIMBevRaxMabZ8U2Ty0dTUPy/PpeYSlMNEmC/YJjKKykgSfc/AwSejx2sE4FFKZ0I2kxRKRenyoWMHRAV49Q==} engines: {node: '>=16.0.0'} peerDependencies: @@ -7118,10 +7128,10 @@ packages: dependencies: '@graphql-tools/utils': 10.0.7(graphql@16.8.1) '@repeaterjs/repeater': 3.0.4 - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 extract-files: 11.0.0 graphql: 16.8.1 - meros: 1.3.0(@types/node@18.18.6) + meros: 1.3.0(@types/node@18.18.7) tslib: 2.6.2 value-or-promise: 1.0.12 transitivePeerDependencies: @@ -7197,14 +7207,14 @@ packages: tslib: 2.6.2 value-or-promise: 1.0.12 - /@graphql-tools/federation@1.1.10(@types/node@18.18.6)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0): + /@graphql-tools/federation@1.1.10(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-H51qTYwbtfIYBO1uHXlImRWzo9tknSoIGBgJckDh+hdxJx43sZaMjJiLHc2DjRc/A8d2Bf0bi0HbH++HqOos/w==} engines: {node: '>=16.0.0'} peerDependencies: graphql: ^16.6.0 dependencies: '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) - '@graphql-tools/executor-http': 1.0.3(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/executor-http': 1.0.3(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/merge': 9.0.0(graphql@16.8.1) '@graphql-tools/schema': 10.0.0(graphql@16.8.1) '@graphql-tools/stitch': 9.0.3(graphql@16.8.1) @@ -7379,7 +7389,7 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-tools/url-loader@7.17.18(@types/node@18.18.6)(graphql@16.8.1): + /@graphql-tools/url-loader@7.17.18(@types/node@18.18.7)(graphql@16.8.1): resolution: {integrity: sha512-ear0CiyTj04jCVAxi7TvgbnGDIN2HgqzXzwsfcqiVg9cvjT40NcMlZ2P1lZDgqMkZ9oyLTV8Bw6j+SyG6A+xPw==} peerDependencies: graphql: ^16.6.0 @@ -7387,7 +7397,7 @@ packages: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/executor-graphql-ws': 0.0.14(graphql@16.8.1) - '@graphql-tools/executor-http': 0.1.10(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/executor-http': 0.1.10(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/executor-legacy-ws': 0.0.11(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) @@ -7404,7 +7414,7 @@ packages: - encoding - utf-8-validate - /@graphql-tools/url-loader@8.0.0(@types/node@18.18.6)(graphql@16.8.1): + /@graphql-tools/url-loader@8.0.0(@types/node@18.18.7)(graphql@16.8.1): resolution: {integrity: sha512-rPc9oDzMnycvz+X+wrN3PLrhMBQkG4+sd8EzaFN6dypcssiefgWKToXtRKI8HHK68n2xEq1PyrOpkjHFJB+GwA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -7413,12 +7423,12 @@ packages: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/executor-graphql-ws': 1.1.0(graphql@16.8.1) - '@graphql-tools/executor-http': 1.0.3(@types/node@18.18.6)(graphql@16.8.1) + '@graphql-tools/executor-http': 1.0.3(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/executor-legacy-ws': 1.0.4(graphql@16.8.1) '@graphql-tools/utils': 10.0.7(graphql@16.8.1) '@graphql-tools/wrap': 10.0.1(graphql@16.8.1) '@types/ws': 8.5.8 - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 graphql: 16.8.1 isomorphic-ws: 5.0.0(ws@8.14.2) tslib: 2.6.2 @@ -7438,7 +7448,7 @@ packages: graphql: ^16.6.0 dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) - dset: 3.1.2 + dset: 3.1.3 graphql: 16.8.1 tslib: 2.6.2 @@ -7580,33 +7590,33 @@ packages: resolution: {integrity: sha512-n6fwMsaoR50VITM2upR4OOi4EZJmZvU6vvXrHWCSakP9e1OeDuAOk+kHiK+egqDRYj6uKtg9VTUFKZBYvu3jRg==} dev: false - /@hypercerts-org/sdk@0.8.16(@envelop/core@3.0.6)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.6)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3): + /@hypercerts-org/sdk@0.8.16(@envelop/core@3.0.6)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3): resolution: {integrity: sha512-WkxYb5RYhv3QI7+N5y4xg5IL4azLqCq4ia1/bgFB55vWJLjnmoMj3VYbiybMxyfgnPfh5O8n2DU86u8ClLgyhg==} dependencies: '@ethereum-attestation-service/eas-sdk': 0.28.3(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(typescript@5.1.6) '@ethersproject/abstract-signer': 5.7.0 - '@graphprotocol/client-add-source-name': 1.0.20(@graphql-mesh/types@0.95.7)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + '@graphprotocol/client-add-source-name': 1.0.20(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) '@graphprotocol/client-polling-live': 2.0.0(@envelop/core@3.0.6)(@graphql-tools/merge@8.4.2)(graphql@16.8.1) - '@graphql-mesh/cache-localforage': 0.95.7(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/cache-localforage': 0.95.8(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/cross-helpers': 0.4.1(@graphql-tools/utils@9.2.1)(graphql@16.8.1) - '@graphql-mesh/graphql': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(@types/node@18.18.6)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2) - '@graphql-mesh/http': 0.96.13(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.12)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/merger-bare': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/runtime': 0.96.12(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/store': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-mesh/utils@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.95.7(@graphql-mesh/store@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.7(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.7)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/graphql': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2) + '@graphql-mesh/http': 0.96.14(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.13)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/merger-bare': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/runtime': 0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) '@hypercerts-org/contracts': 0.8.11 '@openzeppelin/merkle-tree': 1.0.5 - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 ajv: 8.12.0 axios: 1.5.1(debug@4.3.4) dotenv: 16.3.1 ethers: 5.7.2 graphql: 16.8.1 ipfs-core: 0.17.0(uint8arraylist@2.4.3) - jest: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) loglevel: 1.8.1 mime: 3.0.0 nft.storage: 7.1.1(node-fetch@3.3.2) @@ -7655,8 +7665,8 @@ packages: engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: '@ipld/dag-cbor': 9.0.6 - cborg: 4.0.4 - multiformats: 12.1.2 + cborg: 4.0.5 + multiformats: 12.1.3 varint: 6.0.0 dev: false @@ -7685,8 +7695,8 @@ packages: resolution: {integrity: sha512-3kNab5xMppgWw6DVYx2BzmFq8t7I56AGWfp5kaU1fIPkwHVpBRglJJTYsGtbVluCi/s/q97HZM3bC+aDW4sxbQ==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: - cborg: 4.0.4 - multiformats: 12.1.2 + cborg: 4.0.5 + multiformats: 12.1.3 dev: false /@ipld/dag-json@8.0.11: @@ -7746,7 +7756,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -7766,14 +7776,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7806,7 +7816,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-mock: 29.7.0 /@jest/expect-utils@29.7.0: @@ -7830,7 +7840,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7861,7 +7871,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.20 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -7942,7 +7952,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/yargs': 15.0.17 chalk: 4.1.2 @@ -7952,7 +7962,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/yargs': 16.0.7 chalk: 4.1.2 @@ -7963,7 +7973,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/yargs': 17.0.29 chalk: 4.1.2 @@ -8417,14 +8427,14 @@ packages: - supports-color dev: false - /@libp2p/interface@0.1.3: - resolution: {integrity: sha512-C1O7Xqd2TGVWrIOEDx6kGJSk4YOysWGmYG5Oh3chnsCY0wjUSsLDpl9+wKrdiM/lJbAlHlV65ZOvSkIQ9cWPBQ==} + /@libp2p/interface@0.1.4: + resolution: {integrity: sha512-Pk8mzit/w7PbTh28n77RDLTU1CQBBzLygiNC07MvcEjaIwqXdNPN3Vuzr/5qiF6aDsbM9fA1W5dWoCif9xBdxg==} dependencies: '@multiformats/multiaddr': 12.1.7 abortable-iterator: 5.0.1 it-pushable: 3.2.1 it-stream-types: 2.0.1 - multiformats: 12.1.2 + multiformats: 12.1.3 p-defer: 4.0.0 race-signal: 1.0.1 uint8arraylist: 2.4.3 @@ -8503,14 +8513,14 @@ packages: - supports-color dev: false - /@libp2p/logger@3.0.3: - resolution: {integrity: sha512-85ioPX10QN4ZOZeurAZe5sQeRUCkIBT2DikKRbE/AIWKauIKHvvIrN4CSdCdzLw29XNA+xxNO2FVkf51HGgCeQ==} + /@libp2p/logger@3.0.4: + resolution: {integrity: sha512-MF42c7SOJIx5YmHhIsFaSYfaC266YYmMbAJHjjH8Zl5unFsqW82M+Xr7sGVj9/WXrWAd37ts8xJaQrkIXc3OZQ==} dependencies: - '@libp2p/interface': 0.1.3 + '@libp2p/interface': 0.1.4 '@multiformats/multiaddr': 12.1.7 debug: 4.3.4(supports-color@8.1.1) interface-datastore: 8.2.5 - multiformats: 12.1.2 + multiformats: 12.1.3 transitivePeerDependencies: - supports-color dev: false @@ -8940,8 +8950,8 @@ packages: tweetnacl: 1.0.3 tweetnacl-util: 0.15.1 - /@metamask/object-multiplex@1.2.0: - resolution: {integrity: sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==} + /@metamask/object-multiplex@1.3.0: + resolution: {integrity: sha512-czcQeVYdSNtabd+NcYQnrM69MciiJyd1qvKH8WM2Id3C0ZiUUX5Xa/MK+/VUk633DBhVOwdNzAKIQ33lGyA+eQ==} engines: {node: '>=12.0.0'} dependencies: end-of-stream: 1.4.4 @@ -8953,10 +8963,10 @@ packages: resolution: {integrity: sha512-xjE4cKrGpKZjripkMKMStc0H4LXrWJPijfbaj1kKeDLVhRH2Yu3ZecV3iIhf1EIJePeA+Kx6Pcm7d0IVJ+ea7g==} engines: {node: '>=16.0.0'} dependencies: - '@metamask/object-multiplex': 1.2.0 + '@metamask/object-multiplex': 1.3.0 '@metamask/safe-event-emitter': 3.0.0 detect-browser: 5.3.0 - eth-rpc-errors: 4.0.2 + eth-rpc-errors: 4.0.3 extension-port-stream: 2.1.1 fast-deep-equal: 3.1.3 is-stream: 2.0.1 @@ -9243,8 +9253,8 @@ packages: - supports-color dev: true - /@mui/base@5.0.0-beta.20(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-CS2pUuqxST7ch9VNDCklRYDbJ3rru20Tx7na92QvVVKfu3RL4z/QLuVIc8jYGsdCnauMaeUSlFNLAJNb0yXe6w==} + /@mui/base@5.0.0-beta.21(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-eTKWx3WV/nwmRUK4z4K1MzlMyWCsi3WJ3RtV4DiXZeRh4qd4JCyp1Zzzi8Wv9xM4dEBmqQntFoei716PzwmFfA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -9256,22 +9266,22 @@ packages: dependencies: '@babel/runtime': 7.23.2 '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.6(@types/react@18.2.31) - '@mui/utils': 5.14.14(@types/react@18.2.31)(react@18.2.0) + '@mui/types': 7.2.7(@types/react@18.2.33) + '@mui/utils': 5.14.15(@types/react@18.2.33)(react@18.2.0) '@popperjs/core': 2.11.8 - '@types/react': 18.2.31 + '@types/react': 18.2.33 clsx: 2.0.0 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@mui/core-downloads-tracker@5.14.14: - resolution: {integrity: sha512-Rw/xKiTOUgXD8hdKqj60aC6QcGprMipG7ne2giK6Mz7b4PlhL/xog9xLeclY3BxsRLkZQ05egFnIEY1CSibTbw==} + /@mui/core-downloads-tracker@5.14.15: + resolution: {integrity: sha512-ZCDzBWtCKjAYAlKKM3PA/jG/3uVIDT9ZitOtVixIVmTCQyc5jSV1qhJX8+qIGz4RQZ9KLzPWO2tXd0O5hvzouQ==} dev: false - /@mui/icons-material@5.14.14(@mui/material@5.14.14)(@types/react@18.2.31)(react@18.2.0): - resolution: {integrity: sha512-vwuaMsKvI7AWTeYqR8wYbpXijuU8PzMAJWRAq2DDIuOZPxjKyHlr8WQ25+azZYkIXtJ7AqnVb1ZmHdEyB4/kug==} + /@mui/icons-material@5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0): + resolution: {integrity: sha512-Dqu21vN/mVNzebJ+ofnKG+CeJYIhHuDs5+0fMEpdpzRt6UojelzdrEkNv+XkO0e1JMclzeXIRx404FirK/CFRw==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -9282,13 +9292,13 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.2 - '@mui/material': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.31 + '@mui/material': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.33 react: 18.2.0 dev: false - /@mui/material@5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cAmCwAHFQXxb44kWbVFkhKATN8tACgMsFwrXo8ro6WzYW73U/qsR5AcCiJIhCyYYg+gcftfkmNcpRaV3JjhHCg==} + /@mui/material@5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Gq65rHjvLzkxmhG8bvag851Oqsmru7qkUb/cCI2xu7dQzmY345f9xJRJi72sRGjhaqHXWeRKw/yIwp/7oQoeXg==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -9305,14 +9315,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.2 - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) - '@mui/base': 5.0.0-beta.20(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.14.14 - '@mui/system': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react@18.2.0) - '@mui/types': 7.2.6(@types/react@18.2.31) - '@mui/utils': 5.14.14(@types/react@18.2.31)(react@18.2.0) - '@types/react': 18.2.31 + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + '@mui/base': 5.0.0-beta.21(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.14.15 + '@mui/system': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0) + '@mui/types': 7.2.7(@types/react@18.2.33) + '@mui/utils': 5.14.15(@types/react@18.2.33)(react@18.2.0) + '@types/react': 18.2.33 '@types/react-transition-group': 4.4.8 clsx: 2.0.0 csstype: 3.1.2 @@ -9323,8 +9333,8 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.14.14(@types/react@18.2.31)(react@18.2.0): - resolution: {integrity: sha512-n77au3CQj9uu16hak2Y+rvbGSBaJKxziG/gEbOLVGrAuqZ+ycVSkorCfN6Y/4XgYOpG/xvmuiY3JwhAEOzY3iA==} + /@mui/private-theming@5.14.15(@types/react@18.2.33)(react@18.2.0): + resolution: {integrity: sha512-V2Xh+Tu6A07NoSpup0P9m29GwvNMYl5DegsGWqlOTJyAV7cuuVjmVPqxgvL8xBng4R85xqIQJRMjtYYktoPNuQ==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -9334,14 +9344,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.2 - '@mui/utils': 5.14.14(@types/react@18.2.31)(react@18.2.0) - '@types/react': 18.2.31 + '@mui/utils': 5.14.15(@types/react@18.2.33)(react@18.2.0) + '@types/react': 18.2.33 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styled-engine@5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): - resolution: {integrity: sha512-sF3DS2PVG+cFWvkVHQQaGFpL1h6gSwOW3L91pdxPLQDHDZ5mZ/X0SlXU5XA+WjypoysG4urdAQC7CH/BRvUiqg==} + /@mui/styled-engine@5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): + resolution: {integrity: sha512-mbOjRf867BysNpexe5Z/P8s3bWzDPNowmKhi7gtNDP/LPEeqAfiDSuC4WPTXmtvse1dCl30Nl755OLUYuoi7Mw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -9355,15 +9365,15 @@ packages: dependencies: '@babel/runtime': 7.23.2 '@emotion/cache': 11.11.0 - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/system@5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react@18.2.0): - resolution: {integrity: sha512-y4InFmCgGGWXnz+iK4jRTWVikY0HgYnABjz4wgiUgEa2W1H8M4ow+27BegExUWPkj4TWthQ2qG9FOGSMtI+PKA==} + /@mui/system@5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0): + resolution: {integrity: sha512-zr0Gdk1RgKiEk+tCMB900LaOpEC8NaGvxtkmMdL/CXgkqQZSVZOt2PQsxJWaw7kE4YVkIe4VukFVc43qcq9u3w==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -9379,32 +9389,32 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.2 - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) - '@mui/private-theming': 5.14.14(@types/react@18.2.31)(react@18.2.0) - '@mui/styled-engine': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) - '@mui/types': 7.2.6(@types/react@18.2.31) - '@mui/utils': 5.14.14(@types/react@18.2.31)(react@18.2.0) - '@types/react': 18.2.31 + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + '@mui/private-theming': 5.14.15(@types/react@18.2.33)(react@18.2.0) + '@mui/styled-engine': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/types': 7.2.7(@types/react@18.2.33) + '@mui/utils': 5.14.15(@types/react@18.2.33)(react@18.2.0) + '@types/react': 18.2.33 clsx: 2.0.0 csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/types@7.2.6(@types/react@18.2.31): - resolution: {integrity: sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng==} + /@mui/types@7.2.7(@types/react@18.2.33): + resolution: {integrity: sha512-sofpWmcBqOlTzRbr1cLQuUDKaUYVZTw8ENQrtL39TECRNENEzwgnNPh6WMfqMZlMvf1Aj9DLg74XPjnLr0izUQ==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 dev: false - /@mui/utils@5.14.14(@types/react@18.2.31)(react@18.2.0): - resolution: {integrity: sha512-3AKp8uksje5sRfVrtgG9Q/2TBsHWVBUtA0NaXliZqGcXo8J+A+Agp0qUW2rJ+ivgPWTCCubz9FZVT2IQZ3bGsw==} + /@mui/utils@5.14.15(@types/react@18.2.33)(react@18.2.0): + resolution: {integrity: sha512-QBfHovAvTa0J1jXuYDaXGk+Yyp7+Fm8GSqx6nK2JbezGqzCFfirNdop/+bL9Flh/OQ/64PeXcW4HGDdOge+n3A==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -9415,13 +9425,13 @@ packages: dependencies: '@babel/runtime': 7.23.2 '@types/prop-types': 15.7.9 - '@types/react': 18.2.31 + '@types/react': 18.2.33 prop-types: 15.8.1 react: 18.2.0 react-is: 18.2.0 dev: false - /@mui/x-date-pickers@5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(@mui/system@5.14.14)(@types/react@18.2.31)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): + /@mui/x-date-pickers@5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(@mui/system@5.14.15)(@types/react@18.2.33)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ERukSeHIoNLbI1C2XRhF9wRhqfsr+Q4B1SAw2ZlU7CWgcG8UBOxgqRKDEOVAIoSWL+DWT6GRuQjOKvj6UXZceA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -9455,11 +9465,11 @@ packages: '@date-io/dayjs': 2.17.0(dayjs@1.11.10) '@date-io/luxon': 2.17.0 '@date-io/moment': 2.17.0 - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) - '@mui/material': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react@18.2.0) - '@mui/utils': 5.14.14(@types/react@18.2.31)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + '@mui/material': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0) + '@mui/utils': 5.14.15(@types/react@18.2.33)(react@18.2.0) '@types/react-transition-group': 4.4.8 clsx: 1.2.1 dayjs: 1.11.10 @@ -9518,9 +9528,9 @@ packages: dependencies: '@chainsafe/is-ip': 2.0.2 '@chainsafe/netmask': 2.0.0 - '@libp2p/interface': 0.1.3 + '@libp2p/interface': 0.1.4 dns-over-http-resolver: 2.1.2 - multiformats: 12.1.2 + multiformats: 12.1.3 uint8-varint: 2.0.1 uint8arrays: 4.0.6 transitivePeerDependencies: @@ -9538,7 +9548,7 @@ packages: resolution: {integrity: sha512-Yf0UpAaONjed+8PTt5NM/GG4Z4Ai4m1qfT7bqevjnkwRQ12K+0jxtRomirz+VJx4PokpA2St1ZSD1iMkZTqPRQ==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: - multiformats: 12.1.2 + multiformats: 12.1.3 murmurhash3js-revisited: 3.0.0 dev: false @@ -9872,7 +9882,7 @@ packages: hardhat: 2.13.1(ts-node@10.9.1)(typescript@4.9.5) dev: true - /@nomicfoundation/hardhat-toolbox@2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@9.1.0)(@types/node@18.18.6)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.13.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@4.9.5): + /@nomicfoundation/hardhat-toolbox@2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@9.1.0)(@types/node@18.18.7)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.13.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@4.9.5): resolution: {integrity: sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==} peerDependencies: '@ethersproject/abi': ^5.4.7 @@ -9905,13 +9915,13 @@ packages: '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@5.7.2)(hardhat@2.13.1)(typechain@8.3.2) '@types/chai': 4.3.9 '@types/mocha': 9.1.0 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chai: 4.3.10 ethers: 5.7.2 hardhat: 2.13.1(ts-node@10.9.1)(typescript@4.9.5) hardhat-gas-reporter: 1.0.9(hardhat@2.13.1) solidity-coverage: 0.8.5(hardhat@2.13.1) - ts-node: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) typechain: 8.3.2(typescript@4.9.5) typescript: 4.9.5 dev: true @@ -10063,7 +10073,7 @@ packages: d3-require: 1.3.0 dev: false - /@oclif/core@2.15.0(@types/node@18.18.6)(typescript@4.9.5): + /@oclif/core@2.15.0(@types/node@18.18.7)(typescript@4.9.5): resolution: {integrity: sha512-fNEMG5DzJHhYmI3MgpByTvltBOMyFcnRIUMxbiz2ai8rhaYgaTHMG3Q38HcosfIvtw9nCjxpcQtC8MN8QtVCcA==} engines: {node: '>=14.0.0'} dependencies: @@ -10090,7 +10100,7 @@ packages: strip-ansi: 6.0.1 supports-color: 8.1.1 supports-hyperlinks: 2.3.0 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) tslib: 2.6.2 widest-line: 3.1.0 wordwrap: 1.0.0 @@ -10102,7 +10112,7 @@ packages: - typescript dev: true - /@oclif/core@2.8.6(@types/node@18.18.6)(typescript@4.9.5): + /@oclif/core@2.8.6(@types/node@18.18.7)(typescript@4.9.5): resolution: {integrity: sha512-1QlPaHMhOORySCXkQyzjsIsy2GYTilOw3LkjeHkCgsPJQjAT4IclVytJusWktPbYNys9O+O4V23J44yomQvnBQ==} engines: {node: '>=14.0.0'} dependencies: @@ -10125,12 +10135,12 @@ packages: natural-orderby: 2.0.3 object-treeify: 1.1.33 password-prompt: 1.1.3 - semver: 7.5.4 + semver: 7.4.0 string-width: 4.2.3 strip-ansi: 6.0.1 supports-color: 8.1.1 supports-hyperlinks: 2.3.0 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) tslib: 2.6.2 widest-line: 3.1.0 wordwrap: 1.0.0 @@ -10142,11 +10152,11 @@ packages: - typescript dev: true - /@oclif/plugin-autocomplete@2.3.10(@types/node@18.18.6)(typescript@4.9.5): + /@oclif/plugin-autocomplete@2.3.10(@types/node@18.18.7)(typescript@4.9.5): resolution: {integrity: sha512-Ow1AR8WtjzlyCtiWWPgzMyT8SbcDJFr47009riLioHa+MHX2BCDtVn2DVnN/E6b9JlPV5ptQpjefoRSNWBesmg==} engines: {node: '>=12.0.0'} dependencies: - '@oclif/core': 2.15.0(@types/node@18.18.6)(typescript@4.9.5) + '@oclif/core': 2.15.0(@types/node@18.18.7)(typescript@4.9.5) chalk: 4.1.2 debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: @@ -10157,11 +10167,11 @@ packages: - typescript dev: true - /@oclif/plugin-not-found@2.4.3(@types/node@18.18.6)(typescript@4.9.5): + /@oclif/plugin-not-found@2.4.3(@types/node@18.18.7)(typescript@4.9.5): resolution: {integrity: sha512-nIyaR4y692frwh7wIHZ3fb+2L6XEecQwRDIb4zbEam0TvaVmBQWZoColQyWA84ljFBPZ8XWiQyTz+ixSwdRkqg==} engines: {node: '>=12.0.0'} dependencies: - '@oclif/core': 2.15.0(@types/node@18.18.6)(typescript@4.9.5) + '@oclif/core': 2.15.0(@types/node@18.18.7)(typescript@4.9.5) chalk: 4.1.2 fast-levenshtein: 3.0.0 transitivePeerDependencies: @@ -10187,10 +10197,14 @@ packages: resolution: {integrity: sha512-tDBopO1c98Yk7Cv/PZlHqrvtVjlgK5R4J6jxLwoO7qxK4xqOiZG+zSkIvGFpPZ0ikc3QOED3plgdqjgNTnBc7g==} dev: true - /@openzeppelin/defender-admin-client@1.49.0: - resolution: {integrity: sha512-ka+GTbsnGO6j1R2AGj027uu29es/EBVs3VjJStb+7u/1lNhx1xSRS11JBD0a0GNhrwqsKU4czIemlIKMlUzhhQ==} + /@openzeppelin/contracts@4.9.3: + resolution: {integrity: sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==} + dev: true + + /@openzeppelin/defender-admin-client@1.50.0: + resolution: {integrity: sha512-JxeA111ifCIzXho2gFymhepufB0ElI1UMvFIMEfJLvRL7g7V69wSiN8v+OqZyqZTahiY32Rb+TwdhVjKF5Zu+A==} dependencies: - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) axios: 1.5.1(debug@4.3.4) ethers: 5.7.2 lodash: 4.17.21 @@ -10202,11 +10216,11 @@ packages: - utf-8-validate dev: true - /@openzeppelin/defender-autotask-client@1.49.0: - resolution: {integrity: sha512-FhqFB/E0jaDNEgx0WklodpRS2RPfE181+kwtGL23a8uzGiyQRs7+Ia/a8ARGaLEqD9gWwAte6ODiqZAaim7/jg==} + /@openzeppelin/defender-autotask-client@1.50.0: + resolution: {integrity: sha512-QWob3F6xuOu8r8oPy0Y2XLfAL1PTuKE2F4nC4wGeu3JJT8/pJz3xnHX5DgUYwiGIMqnkitUNUoBcmi4CPI31yw==} hasBin: true dependencies: - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) axios: 1.5.1(debug@4.3.4) dotenv: 10.0.0 glob: 7.2.3 @@ -10218,11 +10232,11 @@ packages: - encoding dev: false - /@openzeppelin/defender-autotask-utils@1.49.0: - resolution: {integrity: sha512-rJls6HOuddBsPJwbSmIN/nugdIsgdrwzgpVJC/hxv+amu0HsSkrH26LbcoDig7Eb2dp68Avtmo0EOyAUP1bNGA==} + /@openzeppelin/defender-autotask-utils@1.50.0: + resolution: {integrity: sha512-wuhm5idjsIiC7hdLj+z5ewDmyKx5q0tRXKHp05K9X8uo1CyLdHV2kKZjBrWzGE9qxVhJ79f9PzHZrLcyPHNDOg==} dev: false - /@openzeppelin/defender-base-client@1.49.0(debug@4.3.4): + /@openzeppelin/defender-base-client@1.49.0: resolution: {integrity: sha512-nG2jslaAUbo2ZW9yBStstxTPscAchN/vRdJ16M34whuZRtUp1bccCBVLdv3oiPOdjwFaa1OBXJkheN+eF8alzA==} dependencies: amazon-cognito-identity-js: 6.3.6 @@ -10233,12 +10247,25 @@ packages: transitivePeerDependencies: - debug - encoding + dev: false + + /@openzeppelin/defender-base-client@1.50.0(debug@4.3.4): + resolution: {integrity: sha512-V5uJ4t3kr9ex1RrqGH2DwsHuyW7/hl3VK0sSkq3VVbAewtcsW3cdg/UkXd5ITu6mtz76RoYkvUBHtkYUm0nb+w==} + dependencies: + amazon-cognito-identity-js: 6.3.6 + async-retry: 1.3.3 + axios: 1.5.1(debug@4.3.4) + lodash: 4.17.21 + node-fetch: 2.7.0 + transitivePeerDependencies: + - debug + - encoding /@openzeppelin/defender-sentinel-client@1.49.0: resolution: {integrity: sha512-fr39U1GRWvJP1fWgwqjTYCz7uhfVfXJReWcivwxMeaoyMl+jYFxj8NkMhqkkbmI6O4TUyNMsmAQ34qFf0IS0/A==} dependencies: '@ethersproject/abi': 5.7.0 - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.49.0 axios: 1.5.1(debug@4.3.4) lodash: 4.17.21 node-fetch: 2.7.0 @@ -10251,8 +10278,8 @@ packages: resolution: {integrity: sha512-dl2pQyBvwEZVq1sgw/i+mLQiu4ZD7iKn2/ghD9RbAGHIM8hZQ4ou8cXl1S6wCA92prpeO1rPGQ+NcJajbY4MCw==} deprecated: '@openzeppelin/hardhat-defender is deprecated. This functionality is now included as part of @openzeppelin/hardhat-upgrades' dependencies: - '@openzeppelin/defender-admin-client': 1.49.0 - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-admin-client': 1.50.0 + '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) '@openzeppelin/hardhat-upgrades': 1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.13.1) ethereumjs-util: 7.1.5 transitivePeerDependencies: @@ -10283,7 +10310,7 @@ packages: dependencies: '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.13.1) '@nomiclabs/hardhat-etherscan': 3.1.7(hardhat@2.13.1) - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) '@openzeppelin/platform-deploy-client': 0.8.0(debug@4.3.4) '@openzeppelin/upgrades-core': 1.31.0 chalk: 4.1.2 @@ -10308,7 +10335,7 @@ packages: deprecated: '@openzeppelin/platform-deploy-client is deprecated. Please use @openzeppelin/defender-sdk-deploy-client' dependencies: '@ethersproject/abi': 5.7.0 - '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) axios: 0.21.4(debug@4.3.4) lodash: 4.17.21 node-fetch: 2.7.0 @@ -10561,7 +10588,7 @@ packages: /@protobufjs/utf8@1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@rainbow-me/rainbowkit@1.0.8(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9): + /@rainbow-me/rainbowkit@1.0.8(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)(viem@1.5.3)(wagmi@1.3.9): resolution: {integrity: sha512-m1B9/X3p8MLmj4fDfs2NpJlFRmKz7vOR0jmcdBw2SMFzXqP1FQFQc4pjvtLEeyfEUGSNNceGrecFZRVS0Qk//A==} engines: {node: '>=12.4'} peerDependencies: @@ -10577,9 +10604,9 @@ packages: qrcode: 1.5.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.4(@types/react@18.2.31)(react@18.2.0) + react-remove-scroll: 2.5.4(@types/react@18.2.33)(react@18.2.0) viem: 1.5.3(typescript@5.1.6)(zod@3.22.4) - wagmi: 1.3.9(@types/react@18.2.31)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) + wagmi: 1.3.9(@types/react@18.2.33)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) transitivePeerDependencies: - '@types/react' dev: false @@ -11187,26 +11214,24 @@ packages: '@scure/base': 1.1.3 dev: true - /@sentry-internal/tracing@7.74.1: - resolution: {integrity: sha512-nNaiZreQxCitG2PzYPaC7XtyA9OMsETGYMKAtiK4p62/uTmeYbsBva9BoNx1XeiHRwbrVQYRMKQ9nV5e2jS4/A==} + /@sentry-internal/tracing@7.75.1: + resolution: {integrity: sha512-nynV+7iVcF8k3CqhvI2K7iA8h4ovJhgYHKnXR8RDDevQOqNG2AEX9+hjCj9fZM4MhKHYFqf1od2oO9lTr38kwg==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - tslib: 2.6.2 + '@sentry/core': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 dev: false - /@sentry/browser@7.74.1: - resolution: {integrity: sha512-OYWNne/KO60lOvkIpIlJUyiJt/9j8DGI57thSDFEYSmmbNqMitczUTBOaEStouvHKyfchqLZm1CZfWKt+z0VOA==} + /@sentry/browser@7.75.1: + resolution: {integrity: sha512-0+jPfPA5P9HVYYRQraDokGCY2NiMknSfz11dggClK4VmjvG+hOXiEyf73SFVwLFnv/hwrkWySjoIrVCX65xXQA==} engines: {node: '>=8'} dependencies: - '@sentry-internal/tracing': 7.74.1 - '@sentry/core': 7.74.1 - '@sentry/replay': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - tslib: 2.6.2 + '@sentry-internal/tracing': 7.75.1 + '@sentry/core': 7.75.1 + '@sentry/replay': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 dev: false /@sentry/cli@1.75.2: @@ -11252,13 +11277,12 @@ packages: '@sentry/utils': 5.30.0 tslib: 1.14.1 - /@sentry/core@7.74.1: - resolution: {integrity: sha512-LvEhOSfdIvwkr+PdlrT/aA/iOLhkXrSkvjqAQyogE4ddCWeYfS0NoirxNt1EaxMBAWKhYZRqzkA7WA4LDLbzlA==} + /@sentry/core@7.75.1: + resolution: {integrity: sha512-Kw4KyKBxbxbh8OKO0S11Tm0gWP+6AaXXYrsq3hp8H338l/wOmIzyckmCbUrc/XJeoRqaFLJbdcCrcUEDZUvsVQ==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - tslib: 2.6.2 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 dev: false /@sentry/hub@5.30.0: @@ -11269,15 +11293,14 @@ packages: '@sentry/utils': 5.30.0 tslib: 1.14.1 - /@sentry/integrations@7.74.1: - resolution: {integrity: sha512-Q7chPehHpHB4WOQ1J/X6NiN2ptiqJMmxtL+6wHumzIAyrjup3c9XekR83qEs8zpqYJAlb/4MUlwd9fPbkhGXnQ==} + /@sentry/integrations@7.75.1: + resolution: {integrity: sha512-qSCyTNX3DiL1aYRmdq10LRhPLfh1KJYKhbmGszC1PII4mt9FbLVmC8fSXiDbhgiuSUKKrDE+J2lC//w688lvHw==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 + '@sentry/core': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 localforage: 1.10.0 - tslib: 2.6.2 dev: false /@sentry/minimal@5.30.0: @@ -11288,8 +11311,8 @@ packages: '@sentry/types': 5.30.0 tslib: 1.14.1 - /@sentry/nextjs@7.74.1(next@13.5.6)(react@18.2.0)(webpack@5.89.0): - resolution: {integrity: sha512-1RySEs3WBEqlpQCAFQ/XwV+oW4wEAtpYglvAyDBwPen/s6KnkkZ0za0l3Ug0O6S9HvMiNll1rPhvnkH5nM37Tg==} + /@sentry/nextjs@7.75.1(next@13.5.6)(react@18.2.0)(webpack@5.89.0): + resolution: {integrity: sha512-5DEW6Ksjuox8idf3O0NclF/rHSS0Z1VCIoFHW6d28FHFKU+/RkFwJTB13KfpQMBj1fiA9qSNJdy/2QlGUWVRmA==} engines: {node: '>=8'} peerDependencies: next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 @@ -11300,13 +11323,13 @@ packages: optional: true dependencies: '@rollup/plugin-commonjs': 24.0.0(rollup@2.78.0) - '@sentry/core': 7.74.1 - '@sentry/integrations': 7.74.1 - '@sentry/node': 7.74.1 - '@sentry/react': 7.74.1(react@18.2.0) - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - '@sentry/vercel-edge': 7.74.1 + '@sentry/core': 7.75.1 + '@sentry/integrations': 7.75.1 + '@sentry/node': 7.75.1 + '@sentry/react': 7.75.1(react@18.2.0) + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 + '@sentry/vercel-edge': 7.75.1 '@sentry/webpack-plugin': 1.20.0 chalk: 3.0.0 next: 13.5.6(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) @@ -11314,7 +11337,6 @@ packages: resolve: 1.22.8 rollup: 2.78.0 stacktrace-parser: 0.1.10 - tslib: 2.6.2 webpack: 5.89.0(webpack-cli@5.1.4) transitivePeerDependencies: - encoding @@ -11337,43 +11359,40 @@ packages: transitivePeerDependencies: - supports-color - /@sentry/node@7.74.1: - resolution: {integrity: sha512-aMUQ2LFZF64FBr+cgjAqjT4OkpYBIC9lyWI8QqjEHqNho5+LGu18/iVrJPD4fgs4UhGdCuAiQjpC36MbmnIDZA==} + /@sentry/node@7.75.1: + resolution: {integrity: sha512-E174NbP3j7OIqQQYPtpMGz1FfL/KE5PeGnhoACyMIk0D5MGB7Ia7Y9+nYfHB7+EOJPV2Ob6BYlhemX/MxPrYWg==} engines: {node: '>=8'} dependencies: - '@sentry-internal/tracing': 7.74.1 - '@sentry/core': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - cookie: 0.5.0 + '@sentry-internal/tracing': 7.75.1 + '@sentry/core': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 https-proxy-agent: 5.0.1 - lru_map: 0.3.3 - tslib: 2.6.2 transitivePeerDependencies: - supports-color dev: false - /@sentry/react@7.74.1(react@18.2.0): - resolution: {integrity: sha512-16oTsNi2hl/S5AL/e5bo9DQZDwXPkX0nC8ajrpU0z2pH4cwjQZUZt/9Xq1+MKqDIEZkqDcMwpTmBptOvy1Pvkw==} + /@sentry/react@7.75.1(react@18.2.0): + resolution: {integrity: sha512-5zFcIor8vwQa13VRwk7yDE8U7uspj0eKpsjOcYcfSvDkiL7LW0sA6rXxvO3jwd1AKaB3EAfr1F4oIdEz8aRIkA==} engines: {node: '>=8'} peerDependencies: react: 15.x || 16.x || 17.x || 18.x dependencies: - '@sentry/browser': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 + '@sentry/browser': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 hoist-non-react-statics: 3.3.2 react: 18.2.0 - tslib: 2.6.2 dev: false - /@sentry/replay@7.74.1: - resolution: {integrity: sha512-qmbOl+jYdyhoHFbPp9WemKx8UojID5hVmuVLxNIP0ANqAwmE9OQEK9YFg2cf7L/TpKb1tqz0qLgi5MYIdcdpgQ==} + /@sentry/replay@7.75.1: + resolution: {integrity: sha512-MKQTDWNYs9QXCJ+irGX5gu8Kxdk/Ds5puhILy8+DnCoXgXuPFRMGob1Sxt8qXmbQmcGeogsx221MNTselsRS6g==} engines: {node: '>=12'} dependencies: - '@sentry/core': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 + '@sentry-internal/tracing': 7.75.1 + '@sentry/core': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 dev: false /@sentry/tracing@5.30.0: @@ -11390,8 +11409,8 @@ packages: resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} engines: {node: '>=6'} - /@sentry/types@7.74.1: - resolution: {integrity: sha512-2jIuPc+YKvXqZETwr2E8VYnsH1zsSUR/wkIvg1uTVeVNyoowJv+YsOtCdeGyL2AwiotUBSPKu7O1Lz0kq5rMOQ==} + /@sentry/types@7.75.1: + resolution: {integrity: sha512-km+ygqgMDaFfTrbQwdhrptFqx0Oq15jZABqIoIpbaOCkCAMm+tyCqrFS8dTfaq5wpCktqWOy2qU/DOpppO99Cg==} engines: {node: '>=8'} dev: false @@ -11402,22 +11421,20 @@ packages: '@sentry/types': 5.30.0 tslib: 1.14.1 - /@sentry/utils@7.74.1: - resolution: {integrity: sha512-qUsqufuHYcy5gFhLZslLxA5kcEOkkODITXW3c7D+x+8iP/AJqa8v8CeUCVNS7RetHCuIeWAbbTClC4c411EwQg==} + /@sentry/utils@7.75.1: + resolution: {integrity: sha512-QzW2eRjY20epD//9/tQ0FTNwdAL6XZi+LyJNUQIeK3NMnc5NgHrgpxId87gmFq8cNx47utH1Blub8RuMbKqiwQ==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.74.1 - tslib: 2.6.2 + '@sentry/types': 7.75.1 dev: false - /@sentry/vercel-edge@7.74.1: - resolution: {integrity: sha512-E2lTfEtDFSh57EkjVe4EcgcdjOM8UvfZVsmANBqG4bnwRKrNX9GouClzKU2Ckd5vQnOiCH9r8x2aJ/dTqyBswQ==} + /@sentry/vercel-edge@7.75.1: + resolution: {integrity: sha512-TCiObqegXdWkObf0YUDTvAPgGS5rOpRtZKQmjJ03ZahwrSMZTWESvlKo1V/5JhgfZSRWJTvDnJAtomteopT5/A==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.74.1 - '@sentry/types': 7.74.1 - '@sentry/utils': 7.74.1 - tslib: 2.6.2 + '@sentry/core': 7.75.1 + '@sentry/types': 7.75.1 + '@sentry/utils': 7.75.1 dev: false /@sentry/webpack-plugin@1.20.0: @@ -11686,8 +11703,8 @@ packages: '@supabase/node-fetch': 2.6.14 dev: false - /@supabase/realtime-js@2.8.1: - resolution: {integrity: sha512-bka5U4OeoKMdorGMPjdF30cl8n8nbhn+I9H4iySKzbN45W6AGxi7xoODnxdq/QwaDGtVyTMVbU+GVWre0QCdtw==} + /@supabase/realtime-js@2.8.4: + resolution: {integrity: sha512-5C9slLTGikHnYmAnIBOaPogAgbcNY68vnIyE6GpqIKjHElVb6LIi4clwNcjHSj4z6szuvvzj8T/+ePEgGEGekw==} dependencies: '@supabase/node-fetch': 2.6.14 '@types/phoenix': 1.6.3 @@ -11703,14 +11720,14 @@ packages: '@supabase/node-fetch': 2.6.14 dev: false - /@supabase/supabase-js@2.38.2: - resolution: {integrity: sha512-yOCi94oO5WVUBhQ890BvtT7J3p8spP47PMhn22YldIcDLEQWE6N6X2JzfWCLHktXw+oCYUJfQBuqYhyLHAKb0w==} + /@supabase/supabase-js@2.38.3: + resolution: {integrity: sha512-qIQxXZJN42iM41VChBo3kmGNyRhM0LOk27fkTX8A4tHnWxt0zmxMGFKVCX7Qnrz8zttiJkcym/BZreM/F9k0FQ==} dependencies: '@supabase/functions-js': 2.1.5 '@supabase/gotrue-js': 2.57.0 '@supabase/node-fetch': 2.6.14 '@supabase/postgrest-js': 1.8.5 - '@supabase/realtime-js': 2.8.1 + '@supabase/realtime-js': 2.8.4 '@supabase/storage-js': 2.5.4 transitivePeerDependencies: - supports-color @@ -11880,7 +11897,7 @@ packages: engines: {node: '>=14'} hasBin: true dependencies: - '@cypress/code-coverage': 3.12.5(@babel/core@7.23.2)(@babel/preset-env@7.23.2)(babel-loader@9.1.3)(cypress@12.17.4)(webpack@5.89.0) + '@cypress/code-coverage': 3.12.6(@babel/core@7.23.2)(@babel/preset-env@7.23.2)(babel-loader@9.1.3)(cypress@12.17.4)(webpack@5.89.0) '@cypress/webpack-dev-server': 3.6.1(debug@4.3.4)(webpack@5.89.0) '@drptbl/gremlins.js': 2.2.1 '@foundry-rs/easy-foundryup': 0.1.3 @@ -12107,7 +12124,7 @@ packages: '@babel/parser': 7.18.9 '@babel/traverse': 7.17.3 '@babel/types': 7.17.0 - '@vue/compiler-sfc': 3.3.6 + '@vue/compiler-sfc': 3.3.7 javascript-natural-sort: 0.7.1 lodash: 4.17.21 prettier: 2.8.8 @@ -12115,14 +12132,6 @@ packages: - supports-color dev: true - /@trufflesuite/bigint-buffer@1.1.10: - resolution: {integrity: sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw==} - engines: {node: '>= 14.0.0'} - requiresBuild: true - dependencies: - node-gyp-build: 4.4.0 - dev: true - /@trufflesuite/bigint-buffer@1.1.9: resolution: {integrity: sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==} engines: {node: '>= 10.0.0'} @@ -12294,29 +12303,29 @@ packages: /@types/better-sqlite3@7.6.6: resolution: {integrity: sha512-nuFAptzt0hZYBvyLzKQCbuCCK+RN9PHH4ezar5EJLIg2qpVhwQ/uLvLO/K8A9O7N8DafawgFupiyXQSs0U48Ng==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/bn.js@4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/bn.js@5.1.3: resolution: {integrity: sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/body-parser@1.19.4: resolution: {integrity: sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==} dependencies: '@types/connect': 3.4.37 - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/bonjour@3.5.12: resolution: {integrity: sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/chai-as-promised@7.1.7: resolution: {integrity: sha512-APucaP5rlmTRYKtRA6FE5QPP87x76ejw5t5guRJ4y5OgMnwtsvigw7HHhKZlx2MGXLeZd6R/GNZR/IqDHcbtQw==} @@ -12337,25 +12346,25 @@ packages: /@types/cli-progress@3.11.4: resolution: {integrity: sha512-yufTxeeNCZuEIxx2uebK8lpSAsJM4lvzakm/VxzYhDtqhXCzwH9jpn7nPCxzrROuEbLATqhFq4MIPoG0tlrsvw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/concat-stream@1.6.1: resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/connect-history-api-fallback@1.5.2: resolution: {integrity: sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==} dependencies: '@types/express-serve-static-core': 4.17.39 - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/connect@3.4.37: resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -12369,7 +12378,7 @@ packages: /@types/dns-packet@5.6.2: resolution: {integrity: sha512-vgUZ0ilYvpnTDx7tBmmAUn1HsyzK3huAtulHaDbBBCW5UdaDrEei5XJjWHnD4s8r9/MSL1hJ8s+nvJdcvNKgMA==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/eslint-scope@3.7.6: @@ -12390,7 +12399,7 @@ packages: /@types/express-serve-static-core@4.17.39: resolution: {integrity: sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/qs': 6.9.9 '@types/range-parser': 1.2.6 '@types/send': 0.17.3 @@ -12406,20 +12415,20 @@ packages: /@types/form-data@0.0.33: resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/graceful-fs@4.1.8: resolution: {integrity: sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/hast@2.3.7: resolution: {integrity: sha512-EVLigw5zInURhzfXUM65eixfadfsHKomGKUakToXo84t8gGIJuTcD2xooM2See7GyQ7DRtYjhCHnSUQez8JaLw==} @@ -12433,7 +12442,7 @@ packages: /@types/hoist-non-react-statics@3.3.4: resolution: {integrity: sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ==} dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 hoist-non-react-statics: 3.3.2 dev: false @@ -12450,7 +12459,7 @@ packages: /@types/http-proxy@1.17.13: resolution: {integrity: sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/istanbul-lib-coverage@2.0.5: resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==} @@ -12483,7 +12492,7 @@ packages: /@types/jsdom@20.0.1: resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/tough-cookie': 4.0.4 parse5: 7.1.2 dev: true @@ -12493,13 +12502,12 @@ packages: /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - requiresBuild: true dev: true /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/level-errors@3.0.1: resolution: {integrity: sha512-eFJZWaOUhgjSqgEsPKJZrqXS9aEDUQh/5F9saFhhkR5uEVKlYb4GSG8XyoVC7APklcQKPGDVenTointTZBGIQg==} @@ -12510,7 +12518,7 @@ packages: dependencies: '@types/abstract-leveldown': 7.2.4 '@types/level-errors': 3.0.1 - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/lodash@4.14.200: @@ -12547,7 +12555,7 @@ packages: /@types/mkdirp@0.5.2: resolution: {integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/mocha@9.1.0: @@ -12561,13 +12569,13 @@ packages: resolution: {integrity: sha512-2SZ079yRhuhDn5BssqkQGp07vErjm3PD8S/JAlduWXacZ8SVHF0q6R2m0PwjjgTDU1vE3kibNPlmnr1iKbP/Sw==} dependencies: '@types/dns-packet': 5.6.2 - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/node-fetch@2.6.7: resolution: {integrity: sha512-lX17GZVpJ/fuCjguZ5b3TjEbSENxmEk1B2z02yoXSK9WMEWRivhdSY73wWMn6bpcCDAOh6qAdktpKHIlkDk2lg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 form-data: 4.0.0 dev: true @@ -12590,8 +12598,10 @@ packages: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} dev: false - /@types/node@18.18.6: - resolution: {integrity: sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==} + /@types/node@18.18.7: + resolution: {integrity: sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==} + dependencies: + undici-types: 5.26.5 /@types/node@20.5.1: resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} @@ -12607,7 +12617,7 @@ packages: /@types/papaparse@5.3.10: resolution: {integrity: sha512-mS1Fta/xJ9EDYmAvpeWzcV9Gr0cOl1ClpW7di9+wSUNDIDO55tBtyXg97O7K+Syrd9rDEmuejM2iqmJIJ1SO5g==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/parse-json@4.0.1: @@ -12620,7 +12630,7 @@ packages: /@types/pbkdf2@3.1.1: resolution: {integrity: sha512-4HCoGwR3221nOc7G0Z/6KgTNGgaaFGkbGrtUJsB+zlKX2LBVjFHHIUkieMBgHHXgBH5Gq6dZHJKdBYdtlhBQvw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/phoenix@1.6.3: resolution: {integrity: sha512-D8TtchWVnU2ZdPVDY6tBJuz8MUDmCNVduilZTrf0Gn/u5I/uZEXOsaL4Gs4F0j43cysHsU/4h7eqAKc+SF2boQ==} @@ -12641,37 +12651,37 @@ packages: /@types/react-dom@18.2.14: resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==} dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 dev: true /@types/react-router-config@5.0.9: resolution: {integrity: sha512-a7zOj9yVUtM3Ns5stoseQAAsmppNxZpXDv6tZiFV5qlRmV4W96u53on1vApBX1eRSc8mrFOiB54Hc0Pk1J8GFg==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/react-router': 5.1.20 /@types/react-router-dom@5.3.3: resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/react-router': 5.1.20 /@types/react-router@5.1.20: resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.31 + '@types/react': 18.2.33 /@types/react-transition-group@4.4.8: resolution: {integrity: sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==} dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 dev: false - /@types/react@18.2.31: - resolution: {integrity: sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==} + /@types/react@18.2.33: + resolution: {integrity: sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==} dependencies: '@types/prop-types': 15.7.9 '@types/scheduler': 0.16.5 @@ -12680,7 +12690,7 @@ packages: /@types/readable-stream@2.3.15: resolution: {integrity: sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 safe-buffer: 5.1.2 /@types/resolve@1.20.2: @@ -12690,7 +12700,7 @@ packages: /@types/responselike@1.0.2: resolution: {integrity: sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/retry@0.12.0: resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} @@ -12702,7 +12712,7 @@ packages: /@types/sax@1.2.6: resolution: {integrity: sha512-A1mpYCYu1aHFayy8XKN57ebXeAbh9oQIZ1wXcno6b1ESUAfMBDMx7mf/QGlYwcMRaFryh9YBuH03i/3FlPGDkQ==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/scheduler@0.16.5: @@ -12711,11 +12721,7 @@ packages: /@types/secp256k1@4.0.5: resolution: {integrity: sha512-aIonTBMErtE3T9MxDvTZRzcrT/mCqpEZBw3CCY/i+oG9n57N/+7obBkhFgavUAIrX21bU0LHg1XRgtaLdelBhA==} dependencies: - '@types/node': 18.18.6 - - /@types/seedrandom@3.0.1: - resolution: {integrity: sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==} - dev: true + '@types/node': 18.18.7 /@types/semver@7.5.4: resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} @@ -12725,7 +12731,7 @@ packages: resolution: {integrity: sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==} dependencies: '@types/mime': 1.3.4 - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/serve-index@1.9.3: resolution: {integrity: sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==} @@ -12737,12 +12743,12 @@ packages: dependencies: '@types/http-errors': 2.0.3 '@types/mime': 3.0.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/set-cookie-parser@2.4.5: resolution: {integrity: sha512-ZPmztaAQ4rbnW/WTUnT1dwSENQo4bjGqxCSeyK+gZxmd+zJl/QAeF6dpEXcS5UEJX22HwiggFSaY8nE1nRmkbg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/sinon@10.0.20: @@ -12766,7 +12772,7 @@ packages: /@types/sockjs@0.3.35: resolution: {integrity: sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/source-list-map@0.1.4: resolution: {integrity: sha512-Kdfm7Sk5VX8dFW7Vbp18+fmAatBewzBILa1raHYxrGEFXT0jNl9x3LWfuW7bTbjEKFNey9Dfkj/UzT6z/NvRlg==} @@ -12817,7 +12823,7 @@ packages: /@types/webpack-sources@3.2.2: resolution: {integrity: sha512-acCzhuVe+UJy8abiSFQWXELhhNMZjQjQKpLNEi1pKGgKXZj0ul614ATcx4kkhunPost6Xw+aCq8y8cn1/WwAiA==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/source-list-map': 0.1.4 source-map: 0.7.4 dev: true @@ -12825,7 +12831,7 @@ packages: /@types/webpack@4.41.35: resolution: {integrity: sha512-XRC6HLGHtNfN8/xWeu1YUQV1GSE+28q8lSqvcJ+0xt/zW9Wmn4j9pCSvaXPyRlCKrl5OuqECQNEJUy2vo8oWqg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/tapable': 1.0.10 '@types/uglify-js': 3.17.3 '@types/webpack-sources': 3.2.2 @@ -12836,18 +12842,18 @@ packages: /@types/websocket@1.0.8: resolution: {integrity: sha512-wvkOpWApbuxVfHhSQ1XrjVN4363vsfLJwEo4AboIZk0g1vJA5nmLp8GXUHuIdf4/Fe7+/V0Efe2HvWiLqHtlqw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/ws@8.5.8: resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /@types/yargs-parser@21.0.2: resolution: {integrity: sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==} @@ -12871,7 +12877,7 @@ packages: resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} requiresBuild: true dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true optional: true @@ -12886,7 +12892,7 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.9.1 + '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 5.62.0(eslint@8.19.0)(typescript@5.1.6) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.19.0)(typescript@5.1.6) @@ -12914,7 +12920,7 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.9.1 + '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 5.62.0(eslint@8.52.0)(typescript@4.9.5) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.52.0)(typescript@4.9.5) @@ -13200,56 +13206,56 @@ packages: pretty-format: 27.5.1 dev: true - /@vue/compiler-core@3.3.6: - resolution: {integrity: sha512-2JNjemwaNwf+MkkatATVZi7oAH1Hx0B04DdPH3ZoZ8vKC1xZVP7nl4HIsk8XYd3r+/52sqqoz9TWzYc3yE9dqA==} + /@vue/compiler-core@3.3.7: + resolution: {integrity: sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==} dependencies: '@babel/parser': 7.23.0 - '@vue/shared': 3.3.6 + '@vue/shared': 3.3.7 estree-walker: 2.0.2 source-map-js: 1.0.2 dev: true - /@vue/compiler-dom@3.3.6: - resolution: {integrity: sha512-1MxXcJYMHiTPexjLAJUkNs/Tw2eDf2tY3a0rL+LfuWyiKN2s6jvSwywH3PWD8bKICjfebX3GWx2Os8jkRDq3Ng==} + /@vue/compiler-dom@3.3.7: + resolution: {integrity: sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==} dependencies: - '@vue/compiler-core': 3.3.6 - '@vue/shared': 3.3.6 + '@vue/compiler-core': 3.3.7 + '@vue/shared': 3.3.7 dev: true - /@vue/compiler-sfc@3.3.6: - resolution: {integrity: sha512-/Kms6du2h1VrXFreuZmlvQej8B1zenBqIohP0690IUBkJjsFvJxY0crcvVRJ0UhMgSR9dewB+khdR1DfbpArJA==} + /@vue/compiler-sfc@3.3.7: + resolution: {integrity: sha512-7pfldWy/J75U/ZyYIXRVqvLRw3vmfxDo2YLMwVtWVNew8Sm8d6wodM+OYFq4ll/UxfqVr0XKiVwti32PCrruAw==} dependencies: '@babel/parser': 7.23.0 - '@vue/compiler-core': 3.3.6 - '@vue/compiler-dom': 3.3.6 - '@vue/compiler-ssr': 3.3.6 - '@vue/reactivity-transform': 3.3.6 - '@vue/shared': 3.3.6 + '@vue/compiler-core': 3.3.7 + '@vue/compiler-dom': 3.3.7 + '@vue/compiler-ssr': 3.3.7 + '@vue/reactivity-transform': 3.3.7 + '@vue/shared': 3.3.7 estree-walker: 2.0.2 magic-string: 0.30.5 postcss: 8.4.31 source-map-js: 1.0.2 dev: true - /@vue/compiler-ssr@3.3.6: - resolution: {integrity: sha512-QTIHAfDCHhjXlYGkUg5KH7YwYtdUM1vcFl/FxFDlD6d0nXAmnjizka3HITp8DGudzHndv2PjKVS44vqqy0vP4w==} + /@vue/compiler-ssr@3.3.7: + resolution: {integrity: sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==} dependencies: - '@vue/compiler-dom': 3.3.6 - '@vue/shared': 3.3.6 + '@vue/compiler-dom': 3.3.7 + '@vue/shared': 3.3.7 dev: true - /@vue/reactivity-transform@3.3.6: - resolution: {integrity: sha512-RlJl4dHfeO7EuzU1iJOsrlqWyJfHTkJbvYz/IOJWqu8dlCNWtxWX377WI0VsbAgBizjwD+3ZjdnvSyyFW1YVng==} + /@vue/reactivity-transform@3.3.7: + resolution: {integrity: sha512-APhRmLVbgE1VPGtoLQoWBJEaQk4V8JUsqrQihImVqKT+8U6Qi3t5ATcg4Y9wGAPb3kIhetpufyZ1RhwbZCIdDA==} dependencies: '@babel/parser': 7.23.0 - '@vue/compiler-core': 3.3.6 - '@vue/shared': 3.3.6 + '@vue/compiler-core': 3.3.7 + '@vue/shared': 3.3.7 estree-walker: 2.0.2 magic-string: 0.30.5 dev: true - /@vue/shared@3.3.6: - resolution: {integrity: sha512-Xno5pEqg8SVhomD0kTSmfh30ZEmV/+jZtyh39q6QflrjdJCXah5lrnOLi9KB6a5k5aAHXMXjoMnxlzUkCNfWLQ==} + /@vue/shared@3.3.7: + resolution: {integrity: sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==} dev: true /@wagmi/chains@1.0.0(typescript@5.1.6): @@ -13310,7 +13316,42 @@ packages: - zod dev: false - /@wagmi/core@1.3.8(@types/react@18.2.31)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4): + /@wagmi/connectors@2.7.0(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4): + resolution: {integrity: sha512-1KOL0HTJl5kzSC/YdKwFwiokr6poUQn1V/tcT0TpG3iH2x0lSM7FTkvCjVVY/6lKzTXrLlo9y2aE7AsOPnkvqg==} + peerDependencies: + '@wagmi/chains': '>=1.7.0' + typescript: '>=5.0.4' + viem: '>=0.3.35' + peerDependenciesMeta: + '@wagmi/chains': + optional: true + typescript: + optional: true + dependencies: + '@coinbase/wallet-sdk': 3.7.2 + '@ledgerhq/connect-kit-loader': 1.1.2 + '@safe-global/safe-apps-provider': 0.17.1(typescript@5.1.6)(zod@3.22.4) + '@safe-global/safe-apps-sdk': 8.1.0(typescript@5.1.6)(zod@3.22.4) + '@walletconnect/ethereum-provider': 2.9.2(@walletconnect/modal@2.6.1) + '@walletconnect/legacy-provider': 2.0.0 + '@walletconnect/modal': 2.6.1(react@18.2.0) + '@walletconnect/utils': 2.9.2 + abitype: 0.8.7(typescript@5.1.6)(zod@3.22.4) + eventemitter3: 4.0.7 + typescript: 5.1.6 + viem: 1.5.3(typescript@5.1.6)(zod@3.22.4) + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - encoding + - lokijs + - react + - supports-color + - utf-8-validate + - zod + dev: false + + /@wagmi/core@1.3.8(@types/react@18.2.33)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4): resolution: {integrity: sha512-OYSxikoMizqVnpSkFTwGE7PwFaz2k0PXteSiI0W2Mtk4j4sZzRFdP+9AWeDB6AYm0yU3WvgN1IATx0EEBKUe3w==} peerDependencies: typescript: '>=5.0.4' @@ -13325,7 +13366,7 @@ packages: eventemitter3: 4.0.7 typescript: 5.1.6 viem: 1.5.3(typescript@5.1.6)(zod@3.22.4) - zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0) + zustand: 4.4.4(@types/react@18.2.33)(react@18.2.0) transitivePeerDependencies: - '@react-native-async-storage/async-storage' - '@types/react' @@ -13365,6 +13406,32 @@ packages: - utf-8-validate dev: false + /@walletconnect/core@2.9.2: + resolution: {integrity: sha512-VARMPAx8sIgodeyngDHbealP3B621PQqjqKsByFUTOep8ZI1/R/20zU+cmq6j9RCrL+kLKZcrZqeVzs8Z7OlqQ==} + dependencies: + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/jsonrpc-ws-connection': 1.0.13 + '@walletconnect/keyvaluestorage': 1.0.2 + '@walletconnect/logger': 2.0.1 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/relay-auth': 1.0.4 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.9.2 + '@walletconnect/utils': 2.9.2 + events: 3.3.0 + lodash.isequal: 4.5.0 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - lokijs + - utf-8-validate + dev: false + /@walletconnect/crypto@1.0.3: resolution: {integrity: sha512-+2jdORD7XQs76I2Odgr3wwrtyuLUXD/kprNVsjWRhhhdO9Mt6WqVzOPu0/t7OHSmgal8k7SoBQzUc5hu/8zL/g==} dependencies: @@ -13416,6 +13483,32 @@ packages: - utf-8-validate dev: false + /@walletconnect/ethereum-provider@2.9.2(@walletconnect/modal@2.6.1): + resolution: {integrity: sha512-eO1dkhZffV1g7vpG19XUJTw09M/bwGUwwhy1mJ3AOPbOSbMPvwiCuRz2Kbtm1g9B0Jv15Dl+TvJ9vTgYF8zoZg==} + peerDependencies: + '@walletconnect/modal': '>=2' + peerDependenciesMeta: + '@walletconnect/modal': + optional: true + dependencies: + '@walletconnect/jsonrpc-http-connection': 1.0.7 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/modal': 2.6.1(react@18.2.0) + '@walletconnect/sign-client': 2.9.2 + '@walletconnect/types': 2.9.2 + '@walletconnect/universal-provider': 2.9.2 + '@walletconnect/utils': 2.9.2 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - encoding + - lokijs + - utf-8-validate + dev: false + /@walletconnect/events@1.0.1: resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} dependencies: @@ -13478,6 +13571,19 @@ packages: - utf-8-validate dev: false + /@walletconnect/jsonrpc-ws-connection@1.0.13: + resolution: {integrity: sha512-mfOM7uFH4lGtQxG+XklYuFBj6dwVvseTt5/ahOkkmpcAEgz2umuzu7fTR+h5EmjQBdrmYyEBOWADbeaFNxdySg==} + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + events: 3.3.0 + tslib: 1.14.1 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@walletconnect/keyvaluestorage@1.0.2: resolution: {integrity: sha512-U/nNG+VLWoPFdwwKx0oliT4ziKQCEoQ27L5Hhw8YOFGA2Po9A9pULUYNWhDgHkrb0gYDNt//X7wABcEWWBd3FQ==} peerDependencies: @@ -13566,6 +13672,14 @@ packages: - react dev: false + /@walletconnect/modal-core@2.6.1(react@18.2.0): + resolution: {integrity: sha512-f2hYlJ5pwzGvjyaZ6BoGR5uiMgXzWXt6w6ktt1N8lmY6PiYp8whZgqx2hTxVWwVlsGnaIfh6UHp1hGnANx0eTQ==} + dependencies: + valtio: 1.11.0(react@18.2.0) + transitivePeerDependencies: + - react + dev: false + /@walletconnect/modal-ui@2.5.9(react@18.2.0): resolution: {integrity: sha512-nfBaAT9Ls7RZTBBgAq+Nt/3AoUcinIJ9bcq5UHXTV3lOPu/qCKmUC/0HY3GvUK8ykabUAsjr0OAGmcqkB91qug==} dependencies: @@ -13577,6 +13691,17 @@ packages: - react dev: false + /@walletconnect/modal-ui@2.6.1(react@18.2.0): + resolution: {integrity: sha512-RFUOwDAMijSK8B7W3+KoLKaa1l+KEUG0LCrtHqaB0H0cLnhEGdLR+kdTdygw+W8+yYZbkM5tXBm7MlFbcuyitA==} + dependencies: + '@walletconnect/modal-core': 2.6.1(react@18.2.0) + lit: 2.7.6 + motion: 10.16.2 + qrcode: 1.5.3 + transitivePeerDependencies: + - react + dev: false + /@walletconnect/modal@2.5.9(react@18.2.0): resolution: {integrity: sha512-Zs2RvPwbBNRdBhb50FuJCxi3FJltt1KSpI7odjU/x9GTpTOcSOkmR66PBCy2JvNA0+ztnS1Xs0LVEr3lu7/Jzw==} dependencies: @@ -13586,6 +13711,15 @@ packages: - react dev: false + /@walletconnect/modal@2.6.1(react@18.2.0): + resolution: {integrity: sha512-G84tSzdPKAFk1zimgV7JzIUFT5olZUVtI3GcOk77OeLYjlMfnDT23RVRHm5EyCrjkptnvpD0wQScXePOFd2Xcw==} + dependencies: + '@walletconnect/modal-core': 2.6.1(react@18.2.0) + '@walletconnect/modal-ui': 2.6.1(react@18.2.0) + transitivePeerDependencies: + - react + dev: false + /@walletconnect/randombytes@1.0.3: resolution: {integrity: sha512-35lpzxcHFbTN3ABefC9W+uBpNZl1GC4Wpx0ed30gibfO/y9oLdy1NznbV96HARQKSBV9J9M/rrtIvf6a23jfYw==} dependencies: @@ -13638,6 +13772,25 @@ packages: - utf-8-validate dev: false + /@walletconnect/sign-client@2.9.2: + resolution: {integrity: sha512-anRwnXKlR08lYllFMEarS01hp1gr6Q9XUgvacr749hoaC/AwGVlxYFdM8+MyYr3ozlA+2i599kjbK/mAebqdXg==} + dependencies: + '@walletconnect/core': 2.9.2 + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.9.2 + '@walletconnect/utils': 2.9.2 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - lokijs + - utf-8-validate + dev: false + /@walletconnect/time@1.0.2: resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} dependencies: @@ -13658,6 +13811,20 @@ packages: - lokijs dev: false + /@walletconnect/types@2.9.2: + resolution: {integrity: sha512-7Rdn30amnJEEal4hk83cdwHUuxI1SWQ+K7fFFHBMqkuHLGi3tpMY6kpyfDxnUScYEZXqgRps4Jo5qQgnRqVM7A==} + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/keyvaluestorage': 1.0.2 + '@walletconnect/logger': 2.0.1 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - lokijs + dev: false + /@walletconnect/universal-provider@2.9.0: resolution: {integrity: sha512-k3nkSBkF69sJJVoe17IVoPtnhp/sgaa2t+x7BvA/BKeMxE0DGdtRJdEXotTc8DBmI7o2tkq6l8+HyFBGjQ/CjQ==} dependencies: @@ -13678,6 +13845,26 @@ packages: - utf-8-validate dev: false + /@walletconnect/universal-provider@2.9.2: + resolution: {integrity: sha512-JmaolkO8D31UdRaQCHwlr8uIFUI5BYhBzqYFt54Mc6gbIa1tijGOmdyr6YhhFO70LPmS6gHIjljwOuEllmlrxw==} + dependencies: + '@walletconnect/jsonrpc-http-connection': 1.0.7 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/sign-client': 2.9.2 + '@walletconnect/types': 2.9.2 + '@walletconnect/utils': 2.9.2 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - encoding + - lokijs + - utf-8-validate + dev: false + /@walletconnect/utils@2.9.0: resolution: {integrity: sha512-7Tu3m6dZL84KofrNBcblsgpSqU2vdo9ImLD7zWimLXERVGNQ8smXG+gmhQYblebIBhsPzjy9N38YMC3nPlfQNw==} dependencies: @@ -13700,6 +13887,28 @@ packages: - lokijs dev: false + /@walletconnect/utils@2.9.2: + resolution: {integrity: sha512-D44hwXET/8JhhIjqljY6qxSu7xXnlPrf63UN/Qfl98vDjWlYVcDl2+JIQRxD9GPastw0S8XZXdRq59XDXLuZBg==} + dependencies: + '@stablelib/chacha20poly1305': 1.0.1 + '@stablelib/hkdf': 1.0.1 + '@stablelib/random': 1.0.2 + '@stablelib/sha256': 1.0.1 + '@stablelib/x25519': 1.0.3 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.9.2 + '@walletconnect/window-getters': 1.0.1 + '@walletconnect/window-metadata': 1.0.1 + detect-browser: 5.3.0 + query-string: 7.1.3 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - lokijs + dev: false + /@walletconnect/window-getters@1.0.1: resolution: {integrity: sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==} dependencies: @@ -13916,11 +14125,11 @@ packages: urlpattern-polyfill: 8.0.2 web-streams-polyfill: 3.2.1 - /@whatwg-node/fetch@0.9.13: - resolution: {integrity: sha512-PPtMwhjtS96XROnSpowCQM85gCUG2m7AXZFw0PZlGbhzx2GK7f2iOXilfgIJ0uSlCuuGbOIzfouISkA7C4FJOw==} + /@whatwg-node/fetch@0.9.14: + resolution: {integrity: sha512-wurZC82zzZwXRDSW0OS9l141DynaJQh7Yt0FD1xZ8niX7/Et/7RoiLiltbVU1fSF1RR9z6ndEaTUQBAmddTm1w==} engines: {node: '>=16.0.0'} dependencies: - '@whatwg-node/node-fetch': 0.4.19 + '@whatwg-node/node-fetch': 0.5.0 urlpattern-polyfill: 9.0.0 /@whatwg-node/node-fetch@0.3.6: @@ -13932,8 +14141,8 @@ packages: fast-url-parser: 1.1.3 tslib: 2.6.2 - /@whatwg-node/node-fetch@0.4.19: - resolution: {integrity: sha512-AW7/m2AuweAoSXmESrYQr/KBafueScNbn2iNO0u6xFr2JZdPmYsSm5yvAXYk6yDLv+eDmSSKrf7JnFZ0CsJIdA==} + /@whatwg-node/node-fetch@0.5.0: + resolution: {integrity: sha512-q76lDAafvHNGWedNAVHrz/EyYTS8qwRLcwne8SJQdRN5P3HydxU6XROFvJfTML6KZXQX2FDdGY4/SnaNyd7M0Q==} engines: {node: '>=16.0.0'} dependencies: '@whatwg-node/events': 0.1.1 @@ -13948,23 +14157,23 @@ packages: '@whatwg-node/fetch': 0.8.8 tslib: 2.6.2 - /@whatwg-node/server@0.9.15: - resolution: {integrity: sha512-MDmw3HYfZt8hzP8Vac/LPwD8LyZFByVuO1vHPBDsOMYNf4XWv6eHbRCk4UFfLT8OMFgd+qMy/zNYc74o+3tXZg==} + /@whatwg-node/server@0.9.16: + resolution: {integrity: sha512-gktQkRyONEw2EGpx7UZaC6zNlUm21CGlqAHQXU3QC6W0zlLM5ZQNDCeD66q/nsPHDV08X2NTHlABsuAEk5rh/w==} engines: {node: '>=16.0.0'} dependencies: - '@whatwg-node/fetch': 0.9.13 + '@whatwg-node/fetch': 0.9.14 tslib: 2.6.2 dev: false - /@wry/context@0.7.3: - resolution: {integrity: sha512-Nl8WTesHp89RF803Se9X3IiHjdmLBrIvPMaJkl+rKVJAYyPsz1TEUbu89943HpvujtSJgDUx9W4vZw3K1Mr3sA==} + /@wry/context@0.7.4: + resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} engines: {node: '>=8'} dependencies: tslib: 2.6.2 dev: false - /@wry/equality@0.5.6: - resolution: {integrity: sha512-D46sfMTngaYlrH+OspKf8mIJETntFnf6Hsjb0V41jAXJ7Bx2kB8Rv8RCUujuVWYttFtHkUNp7g+FwxNQAr6mXA==} + /@wry/equality@0.5.7: + resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} engines: {node: '>=8'} dependencies: tslib: 2.6.2 @@ -14106,18 +14315,6 @@ packages: xtend: 4.0.2 dev: true - /abstract-leveldown@7.2.0: - resolution: {integrity: sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==} - engines: {node: '>=10'} - dependencies: - buffer: 6.0.3 - catering: 2.1.1 - is-buffer: 2.0.5 - level-concat-iterator: 3.1.0 - level-supports: 2.1.0 - queue-microtask: 1.2.3 - dev: true - /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -14246,8 +14443,8 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /algoliasearch-helper@3.14.2(algoliasearch@4.20.0): - resolution: {integrity: sha512-FjDSrjvQvJT/SKMW74nPgFpsoPUwZCzGbCqbp8HhBFfSk/OvNFxzCaCmuO0p7AWeLy1gD+muFwQEkBwcl5H4pg==} + /algoliasearch-helper@3.15.0(algoliasearch@4.20.0): + resolution: {integrity: sha512-DGUnK3TGtDQsaUE4ayF/LjSN0DGsuYThB8WBgnnDY0Wq04K6lNVruO3LfqJOgSfDiezp+Iyt8Tj4YKHi+/ivSA==} peerDependencies: algoliasearch: '>= 3.1 < 6' dependencies: @@ -14765,7 +14962,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.22.1 - caniuse-lite: 1.0.30001553 + caniuse-lite: 1.0.30001554 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -15457,8 +15654,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001553 - electron-to-chromium: 1.4.564 + caniuse-lite: 1.0.30001554 + electron-to-chromium: 1.4.567 node-releases: 2.0.13 update-browserslist-db: 1.0.13(browserslist@4.22.1) @@ -15722,13 +15919,13 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.22.1 - caniuse-lite: 1.0.30001553 + caniuse-lite: 1.0.30001554 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: false - /caniuse-lite@1.0.30001553: - resolution: {integrity: sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A==} + /caniuse-lite@1.0.30001554: + resolution: {integrity: sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==} /capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -15784,8 +15981,8 @@ packages: resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} hasBin: true - /cborg@4.0.4: - resolution: {integrity: sha512-nu+JXYskYqWN/tFWQVjL2ZYlUwK+dapqkTpruAtJkwmDv7XaTgg8PStUbO+sXfhqSWaeQ9LPSPCTrO2WZ2Bxfg==} + /cborg@4.0.5: + resolution: {integrity: sha512-q8TAjprr8pn9Fp53rOIGp/UFDdFY6os2Nq62YogPSIzczJD9M6g2b6igxMkpCiZZKJ0kn/KzDLDvG+EqBIEeCg==} hasBin: true dev: false @@ -16369,13 +16566,13 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} - /commitizen@4.3.0: + /commitizen@4.3.0(typescript@4.9.5): resolution: {integrity: sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==} engines: {node: '>= 12'} hasBin: true dependencies: cachedir: 2.3.0 - cz-conventional-changelog: 3.3.0 + cz-conventional-changelog: 3.3.0(typescript@4.9.5) dedent: 0.7.0 detect-indent: 6.1.0 find-node-modules: 2.1.3 @@ -16389,8 +16586,7 @@ packages: strip-bom: 4.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + - typescript dev: true /common-path-prefix@3.0.0: @@ -16551,7 +16747,7 @@ packages: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} dependencies: - is-what: 4.1.15 + is-what: 4.1.16 dev: false /copy-text-to-clipboard@3.2.0: @@ -16631,7 +16827,7 @@ packages: layout-base: 2.0.1 dev: false - /cosmiconfig-typescript-loader@4.4.0(@types/node@18.18.6)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.2.2): + /cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@4.9.5): resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==} engines: {node: '>=v14.21.3'} peerDependencies: @@ -16640,27 +16836,27 @@ packages: ts-node: '>=10' typescript: '>=4' dependencies: - '@types/node': 18.18.6 - cosmiconfig: 8.3.6(typescript@5.2.2) - ts-node: 10.9.1(@types/node@18.18.6)(typescript@5.2.2) - typescript: 5.2.2 + '@types/node': 20.5.1 + cosmiconfig: 8.3.6(typescript@4.9.5) + ts-node: 10.9.1(@types/node@20.5.1)(typescript@4.9.5) + typescript: 4.9.5 dev: true - optional: true - /cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@4.9.5): - resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==} - engines: {node: '>=v14.21.3'} + /cosmiconfig-typescript-loader@5.0.0(@types/node@18.18.7)(cosmiconfig@8.3.6)(typescript@4.9.5): + resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} + engines: {node: '>=v16'} + requiresBuild: true peerDependencies: '@types/node': '*' - cosmiconfig: '>=7' - ts-node: '>=10' + cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 20.5.1 + '@types/node': 18.18.7 cosmiconfig: 8.3.6(typescript@4.9.5) - ts-node: 10.9.1(@types/node@20.5.1)(typescript@4.9.5) + jiti: 1.20.0 typescript: 4.9.5 dev: true + optional: true /cosmiconfig@5.2.1: resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} @@ -16734,23 +16930,6 @@ packages: path-type: 4.0.0 typescript: 5.1.6 - /cosmiconfig@8.3.6(typescript@5.2.2): - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - typescript: 5.2.2 - dev: true - optional: true - /crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -16775,7 +16954,7 @@ packages: safe-buffer: 5.2.1 sha.js: 2.4.11 - /create-jest@29.7.0(@types/node@18.18.6)(ts-node@10.9.1): + /create-jest@29.7.0(@types/node@18.18.7)(ts-node@10.9.1): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -16784,7 +16963,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17131,21 +17310,20 @@ packages: lodash: 4.17.21 dev: false - /cz-conventional-changelog@3.3.0: + /cz-conventional-changelog@3.3.0(typescript@4.9.5): resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} engines: {node: '>= 10'} dependencies: chalk: 2.4.2 - commitizen: 4.3.0 + commitizen: 4.3.0(typescript@4.9.5) conventional-commit-types: 3.0.0 lodash.map: 4.6.0 longest: 2.0.1 word-wrap: 1.2.5 optionalDependencies: - '@commitlint/load': 18.0.0 + '@commitlint/load': 18.2.0(typescript@4.9.5) transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' + - typescript dev: true /d3-array@3.2.4: @@ -18157,8 +18335,8 @@ packages: pify: 4.0.1 dev: true - /dset@3.1.2: - resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} + /dset@3.1.3: + resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==} engines: {node: '>=4'} /duplexer3@0.1.5: @@ -18212,8 +18390,8 @@ packages: dependencies: encoding: 0.1.13 - /electron-to-chromium@1.4.564: - resolution: {integrity: sha512-bGAx9+teIzL5I4esQwCMtiXtb78Ysc8xOKTPOvmafbJZ4SQ40kDO1ym3yRcGSkfaBtV81fGgHOgPoe6DsmpmkA==} + /electron-to-chromium@1.4.567: + resolution: {integrity: sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==} /elkjs@0.8.2: resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} @@ -18970,7 +19148,7 @@ packages: hasBin: true dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) - '@eslint-community/regexpp': 4.9.1 + '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.2 '@eslint/js': 8.52.0 '@humanwhocodes/config-array': 0.11.13 @@ -19144,6 +19322,12 @@ packages: fast-safe-stringify: 2.1.1 dev: false + /eth-rpc-errors@4.0.3: + resolution: {integrity: sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==} + dependencies: + fast-safe-stringify: 2.1.1 + dev: false + /ethereum-bloom-filters@1.0.10: resolution: {integrity: sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==} dependencies: @@ -19316,7 +19500,7 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 require-like: 0.1.2 dev: false @@ -20110,7 +20294,7 @@ packages: dependencies: fetch-blob: 3.2.0 - /formik-mui-x-date-pickers@0.0.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(@mui/system@5.14.14)(@mui/x-date-pickers@5.0.20)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3): + /formik-mui-x-date-pickers@0.0.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(@mui/system@5.14.15)(@mui/x-date-pickers@5.0.20)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3): resolution: {integrity: sha512-IxZsY6er+g0eNsucIDHcNs6DLaPDdG14IYx/lS2HSuKnTgV4vGEWpXyGMpkY/vGyh+W3N5U4TrBVu+7eRb5rLA==} peerDependencies: '@emotion/react': '>=11.5.0' @@ -20122,17 +20306,17 @@ packages: react: '>=17.0.2' tiny-warning: '>=1.0.3' dependencies: - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) - '@mui/material': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react@18.2.0) - '@mui/x-date-pickers': 5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(@mui/system@5.14.14)(@types/react@18.2.31)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + '@mui/material': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0) + '@mui/x-date-pickers': 5.0.20(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(@mui/system@5.14.15)(@types/react@18.2.33)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) formik: 2.4.5(react@18.2.0) react: 18.2.0 tiny-warning: 1.0.3 dev: false - /formik-mui@5.0.0-alpha.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.14)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3): + /formik-mui@5.0.0-alpha.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.14.15)(formik@2.4.5)(react@18.2.0)(tiny-warning@1.0.3): resolution: {integrity: sha512-tcY8B4I3N2UK9ghgVpeBWsXGMDe1y4LVKwI8GiUbLKGB86fI/CN9UMr4FuNo6kzNXvO42LFNmCxdEVzovNCyYQ==} peerDependencies: '@emotion/react': '>=11.5.0' @@ -20142,9 +20326,9 @@ packages: react: '>=17.0.2' tiny-warning: '>=1.0.3' dependencies: - '@emotion/react': 11.11.1(@types/react@18.2.31)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.31)(react@18.2.0) - '@mui/material': 5.14.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + '@mui/material': 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) formik: 2.4.5(react@18.2.0) react: 18.2.0 tiny-warning: 1.0.3 @@ -20315,15 +20499,6 @@ packages: /ganache@7.4.3: resolution: {integrity: sha512-RpEDUiCkqbouyE7+NMXG26ynZ+7sGiODU84Kz+FVoXUnQ4qQM4M8wif3Y4qUCt+D/eM1RVeGq0my62FPD6Y1KA==} hasBin: true - dependencies: - '@trufflesuite/bigint-buffer': 1.1.10 - '@types/bn.js': 5.1.3 - '@types/lru-cache': 5.1.1 - '@types/seedrandom': 3.0.1 - emittery: 0.10.0 - keccak: 3.0.2 - leveldown: 6.1.0 - secp256k1: 4.0.3 optionalDependencies: bufferutil: 4.0.5 utf-8-validate: 5.0.7 @@ -20869,7 +21044,7 @@ packages: '@graphql-yoga/subscription': 3.1.0 '@whatwg-node/fetch': 0.8.8 '@whatwg-node/server': 0.7.7 - dset: 3.1.2 + dset: 3.1.3 graphql: 16.8.1 lru-cache: 7.18.3 tslib: 2.6.2 @@ -20886,9 +21061,9 @@ packages: '@graphql-tools/utils': 10.0.7(graphql@16.8.1) '@graphql-yoga/logger': 2.0.0 '@graphql-yoga/subscription': 5.0.0 - '@whatwg-node/fetch': 0.9.13 - '@whatwg-node/server': 0.9.15 - dset: 3.1.2 + '@whatwg-node/fetch': 0.9.14 + '@whatwg-node/server': 0.9.16 + dset: 3.1.3 graphql: 16.8.1 lru-cache: 10.0.1 tslib: 2.6.2 @@ -21060,7 +21235,7 @@ packages: solc: 0.7.3(debug@4.3.4) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) tsort: 0.0.1 typescript: 4.9.5 undici: 5.26.5 @@ -22096,7 +22271,7 @@ packages: '@libp2p/interface-peer-info': 1.0.10 '@libp2p/interface-pubsub': 3.0.7 '@multiformats/multiaddr': 11.6.1 - '@types/node': 18.18.6 + '@types/node': 18.18.7 interface-datastore: 7.0.4 ipfs-unixfs: 8.0.0 multiformats: 10.0.3 @@ -23044,8 +23219,8 @@ packages: get-intrinsic: 1.2.2 dev: true - /is-what@4.1.15: - resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} dev: false @@ -23637,7 +23812,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -23657,7 +23832,7 @@ packages: - babel-plugin-macros - supports-color - /jest-cli@29.7.0(@types/node@18.18.6)(ts-node@10.9.1): + /jest-cli@29.7.0(@types/node@18.18.7)(ts-node@10.9.1): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -23671,10 +23846,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -23684,7 +23859,7 @@ packages: - supports-color - ts-node - /jest-config@29.7.0(@types/node@18.18.6)(ts-node@10.9.1): + /jest-config@29.7.0(@types/node@18.18.7)(ts-node@10.9.1): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -23699,7 +23874,7 @@ packages: '@babel/core': 7.23.2 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 babel-jest: 29.7.0(@babel/core@7.23.2) chalk: 4.1.2 ci-info: 3.9.0 @@ -23719,7 +23894,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@types/node@18.18.6)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.18.7)(typescript@4.9.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -23762,7 +23937,7 @@ packages: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 20.0.3 @@ -23779,7 +23954,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -23792,7 +23967,7 @@ packages: jest: optional: true dependencies: - jest: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-diff: 29.7.0 jest-get-type: 29.6.3 dev: true @@ -23807,7 +23982,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.8 - '@types/node': 18.18.6 + '@types/node': 18.18.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -23854,7 +24029,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-util: 29.7.0 /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -23908,7 +24083,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -23938,7 +24113,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -23988,7 +24163,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -23999,7 +24174,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -24022,7 +24197,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -24033,7 +24208,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -24041,12 +24216,12 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - /jest@29.7.0(@types/node@18.18.6)(ts-node@10.9.1): + /jest@29.7.0(@types/node@18.18.7)(ts-node@10.9.1): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -24059,7 +24234,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -24069,7 +24244,6 @@ packages: /jiti@1.20.0: resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} hasBin: true - dev: false /joi@17.11.0: resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==} @@ -24251,7 +24425,7 @@ packages: engines: {node: '>=10.0.0'} dependencies: '@metamask/safe-event-emitter': 2.0.0 - eth-rpc-errors: 4.0.2 + eth-rpc-errors: 4.0.3 dev: false /json-rpc-middleware-stream@4.2.3: @@ -24428,16 +24602,6 @@ packages: node-gyp-build: 4.6.1 dev: true - /keccak@3.0.2: - resolution: {integrity: sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==} - engines: {node: '>=10.0.0'} - requiresBuild: true - dependencies: - node-addon-api: 2.0.2 - node-gyp-build: 4.6.1 - readable-stream: 3.6.2 - dev: true - /keccak@3.0.4: resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} engines: {node: '>=10.0.0'} @@ -24544,13 +24708,6 @@ packages: engines: {node: '>=6'} dev: true - /level-concat-iterator@3.1.0: - resolution: {integrity: sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==} - engines: {node: '>=10'} - dependencies: - catering: 2.1.1 - dev: true - /level-errors@2.0.1: resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==} engines: {node: '>=6'} @@ -24590,11 +24747,6 @@ packages: xtend: 4.0.2 dev: true - /level-supports@2.1.0: - resolution: {integrity: sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==} - engines: {node: '>=10'} - dev: true - /level-supports@4.0.1: resolution: {integrity: sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==} engines: {node: '>=12'} @@ -24622,16 +24774,6 @@ packages: browser-level: 1.0.1 classic-level: 1.3.0 - /leveldown@6.1.0: - resolution: {integrity: sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==} - engines: {node: '>=10.12.0'} - requiresBuild: true - dependencies: - abstract-leveldown: 7.2.0 - napi-macros: 2.0.0 - node-gyp-build: 4.6.1 - dev: true - /levelup@4.4.0: resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==} engines: {node: '>=6'} @@ -24850,6 +24992,14 @@ packages: lit-html: 2.8.0 dev: false + /lit@2.7.6: + resolution: {integrity: sha512-1amFHA7t4VaaDe+vdQejSVBklwtH9svGoG6/dZi9JhxtJBBlqY5D1RV7iLUYY0trCqQc4NfhYYZilZiVHt7Hxg==} + dependencies: + '@lit/reactive-element': 1.6.3 + lit-element: 3.3.3 + lit-html: 2.8.0 + dev: false + /load-json-file@1.1.0: resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} engines: {node: '>=0.10.0'} @@ -25581,7 +25731,7 @@ packages: web-worker: 1.2.0 dev: false - /meros@1.3.0(@types/node@18.18.6): + /meros@1.3.0(@types/node@18.18.7): resolution: {integrity: sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==} engines: {node: '>=13'} peerDependencies: @@ -25590,7 +25740,7 @@ packages: '@types/node': optional: true dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -26455,8 +26605,8 @@ packages: engines: {node: '>=16.0.0', npm: '>=7.0.0'} dev: false - /multiformats@12.1.2: - resolution: {integrity: sha512-6mRIsrZXyw5xNPO31IGBMmxgDXBSgCGDsBAtazkZ02ip4hMwZNrQvfxXZtytRoBSWuzSq5f9VmMnXj76fIz5FQ==} + /multiformats@12.1.3: + resolution: {integrity: sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dev: false @@ -26536,10 +26686,6 @@ packages: hasBin: true dev: false - /napi-macros@2.0.0: - resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} - dev: true - /napi-macros@2.2.2: resolution: {integrity: sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==} @@ -26616,7 +26762,7 @@ packages: '@next/env': 13.5.6 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001553 + caniuse-lite: 1.0.30001554 postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26730,11 +26876,6 @@ packages: dev: true optional: true - /node-gyp-build@4.4.0: - resolution: {integrity: sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==} - hasBin: true - dev: true - /node-gyp-build@4.6.1: resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} hasBin: true @@ -27097,7 +27238,7 @@ packages: /optimism@0.17.5: resolution: {integrity: sha512-TEcp8ZwK1RczmvMnvktxHSF2tKgMWjJ71xEFGX5ApLh67VsMSTy1ZUlipJw8W+KaqgOmQ+4pqwkeivY89j+4Vw==} dependencies: - '@wry/context': 0.7.3 + '@wry/context': 0.7.4 '@wry/trie': 0.4.3 tslib: 2.6.2 dev: false @@ -28304,7 +28445,7 @@ packages: engines: {node: '>=4'} dev: false - /primereact@9.6.3(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0): + /primereact@9.6.3(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-E1Apj4zHeEqFuNR43PFmcpYO51V17PDaVuZmYggEUxZHUo7XWoqkOGg7896SRRMDMBQcO3+HXdJQXi6DOWRV1g==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -28314,7 +28455,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/react-transition-group': 4.4.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -28436,7 +28577,7 @@ packages: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 18.18.6 + '@types/node': 18.18.7 long: 4.0.0 /protobufjs@7.2.5: @@ -28454,7 +28595,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.18.6 + '@types/node': 18.18.7 long: 5.2.3 dev: false @@ -28777,8 +28918,8 @@ packages: - vue-template-compiler dev: false - /react-devtools-core@4.28.4: - resolution: {integrity: sha512-IUZKLv3CimeM07G3vX4H4loxVpByrzq3HvfTX7v9migalwvLs9ZY5D3S3pKR33U+GguYfBBdMMZyToFhsSE/iQ==} + /react-devtools-core@4.28.5: + resolution: {integrity: sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==} dependencies: shell-quote: 1.8.1 ws: 7.5.9 @@ -28874,7 +29015,7 @@ packages: webpack: 5.89.0(webpack-cli@5.1.4) dev: false - /react-markdown@8.0.7(@types/react@18.2.31)(react@18.2.0): + /react-markdown@8.0.7(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} peerDependencies: '@types/react': '>=16' @@ -28882,7 +29023,7 @@ packages: dependencies: '@types/hast': 2.3.7 '@types/prop-types': 15.7.9 - '@types/react': 18.2.31 + '@types/react': 18.2.33 '@types/unist': 2.0.9 comma-separated-tokens: 2.0.3 hast-util-whitespace: 2.0.1 @@ -28956,7 +29097,7 @@ packages: pretty-format: 26.6.2 promise: 8.3.0 react: 18.2.0 - react-devtools-core: 4.28.4 + react-devtools-core: 4.28.5 react-refresh: 0.4.3 react-shallow-renderer: 16.15.0(react@18.2.0) regenerator-runtime: 0.13.11 @@ -28978,7 +29119,7 @@ packages: resolution: {integrity: sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==} engines: {node: '>=0.10.0'} - /react-remove-scroll-bar@2.3.4(@types/react@18.2.31)(react@18.2.0): + /react-remove-scroll-bar@2.3.4(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} engines: {node: '>=10'} peerDependencies: @@ -28988,13 +29129,13 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 react: 18.2.0 - react-style-singleton: 2.2.1(@types/react@18.2.31)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.33)(react@18.2.0) tslib: 2.6.2 dev: false - /react-remove-scroll@2.5.4(@types/react@18.2.31)(react@18.2.0): + /react-remove-scroll@2.5.4(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} engines: {node: '>=10'} peerDependencies: @@ -29004,13 +29145,13 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 react: 18.2.0 - react-remove-scroll-bar: 2.3.4(@types/react@18.2.31)(react@18.2.0) - react-style-singleton: 2.2.1(@types/react@18.2.31)(react@18.2.0) + react-remove-scroll-bar: 2.3.4(@types/react@18.2.33)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.33)(react@18.2.0) tslib: 2.6.2 - use-callback-ref: 1.3.0(@types/react@18.2.31)(react@18.2.0) - use-sidecar: 1.1.2(@types/react@18.2.31)(react@18.2.0) + use-callback-ref: 1.3.0(@types/react@18.2.33)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.33)(react@18.2.0) dev: false /react-router-config@5.1.1(react-router@5.3.4)(react@18.2.0): @@ -29065,7 +29206,7 @@ packages: react: 18.2.0 react-is: 18.2.0 - /react-style-singleton@2.2.1(@types/react@18.2.31)(react@18.2.0): + /react-style-singleton@2.2.1(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: @@ -29075,7 +29216,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 @@ -29945,8 +30086,8 @@ packages: '@babel/runtime': 7.23.2 dev: false - /rtl-detect@1.0.4: - resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==} + /rtl-detect@1.1.2: + resolution: {integrity: sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==} dev: false /rtlcss@3.5.0: @@ -31892,7 +32033,7 @@ packages: bs-logger: 0.2.6 esbuild: 0.17.19 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.18.6)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.18.7)(ts-node@10.9.1) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -31931,7 +32072,7 @@ packages: tsconfig-paths: 3.14.2 dev: true - /ts-node@10.9.1(@types/node@18.18.6)(typescript@4.9.5): + /ts-node@10.9.1(@types/node@18.18.7)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -31950,7 +32091,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.18.6 + '@types/node': 18.18.7 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -31961,7 +32102,7 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /ts-node@10.9.1(@types/node@18.18.6)(typescript@5.1.6): + /ts-node@10.9.1(@types/node@18.18.7)(typescript@5.1.6): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -31980,7 +32121,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.18.6 + '@types/node': 18.18.7 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -31991,38 +32132,6 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /ts-node@10.9.1(@types/node@18.18.6)(typescript@5.2.2): - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.18.6 - acorn: 8.10.0 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.2.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - optional: true - /ts-node@10.9.1(@types/node@20.5.1)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -32403,14 +32512,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} - engines: {node: '>=14.17'} - hasBin: true - requiresBuild: true - dev: true - optional: true - /typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -32474,7 +32575,7 @@ packages: /uint8arrays@4.0.6: resolution: {integrity: sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==} dependencies: - multiformats: 12.1.2 + multiformats: 12.1.3 dev: false /unbox-primitive@1.0.2: @@ -32501,6 +32602,9 @@ packages: resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici@5.26.5: resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==} engines: {node: '>=14.0'} @@ -32802,7 +32906,7 @@ packages: /urlpattern-polyfill@9.0.0: resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} - /use-callback-ref@1.3.0(@types/react@18.2.31)(react@18.2.0): + /use-callback-ref@1.3.0(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} peerDependencies: @@ -32812,7 +32916,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 react: 18.2.0 tslib: 2.6.2 dev: false @@ -32850,7 +32954,7 @@ packages: use-isomorphic-layout-effect: 1.1.2(react@18.2.0) dev: false - /use-sidecar@1.1.2(@types/react@18.2.31)(react@18.2.0): + /use-sidecar@1.1.2(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: @@ -32860,7 +32964,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 detect-node-es: 1.1.0 react: 18.2.0 tslib: 2.6.2 @@ -33007,6 +33111,20 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false + /valtio@1.11.0(react@18.2.0): + resolution: {integrity: sha512-65Yd0yU5qs86b5lN1eu/nzcTgQ9/6YnD6iO+DDaDbQLn1Zv2w12Gwk43WkPlUBxk5wL/6cD5YMFf7kj6HZ1Kpg==} + engines: {node: '>=12.20.0'} + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + dependencies: + proxy-compare: 2.5.1 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} dev: false @@ -33122,7 +33240,7 @@ packages: - zod dev: false - /vite-node@0.28.5(@types/node@18.18.6): + /vite-node@0.28.5(@types/node@18.18.7): resolution: {integrity: sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==} engines: {node: '>=v14.16.0'} hasBin: true @@ -33134,7 +33252,7 @@ packages: picocolors: 1.0.0 source-map: 0.6.1 source-map-support: 0.5.21 - vite: 4.5.0(@types/node@18.18.6) + vite: 4.5.0(@types/node@18.18.7) transitivePeerDependencies: - '@types/node' - less @@ -33146,7 +33264,7 @@ packages: - terser dev: true - /vite@4.5.0(@types/node@18.18.6): + /vite@4.5.0(@types/node@18.18.7): resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -33174,7 +33292,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 esbuild: 0.18.20 postcss: 8.4.31 rollup: 3.29.4 @@ -33206,7 +33324,7 @@ packages: dependencies: '@types/chai': 4.3.9 '@types/chai-subset': 1.3.4 - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@vitest/expect': 0.28.5 '@vitest/runner': 0.28.5 '@vitest/spy': 0.28.5 @@ -33225,8 +33343,8 @@ packages: tinybench: 2.5.1 tinypool: 0.3.1 tinyspy: 1.1.1 - vite: 4.5.0(@types/node@18.18.6) - vite-node: 0.28.5(@types/node@18.18.6) + vite: 4.5.0(@types/node@18.18.7) + vite-node: 0.28.5(@types/node@18.18.7) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -33275,7 +33393,7 @@ packages: hasBin: true dev: true - /wagmi@1.3.9(@types/react@18.2.31)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4): + /wagmi@1.3.9(@types/react@18.2.33)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4): resolution: {integrity: sha512-BQbl+vWLNpLraXd/MWsl1P3I41l7DHrujx6qshIa1HDV7Mdh0GNrDuluRYBtuK2bBx9WM/Fjw45Ef2aKADan9A==} peerDependencies: react: '>=17.0.0' @@ -33288,7 +33406,7 @@ packages: '@tanstack/query-sync-storage-persister': 4.36.1 '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) '@tanstack/react-query-persist-client': 4.36.1(@tanstack/react-query@4.36.1) - '@wagmi/core': 1.3.8(@types/react@18.2.31)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) + '@wagmi/core': 1.3.8(@types/react@18.2.33)(react@18.2.0)(typescript@5.1.6)(viem@1.5.3)(zod@3.22.4) abitype: 0.8.7(typescript@5.1.6)(zod@3.22.4) react: 18.2.0 typescript: 5.1.6 @@ -34250,7 +34368,7 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - /zustand@4.4.4(@types/react@18.2.31)(react@18.2.0): + /zustand@4.4.4(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==} engines: {node: '>=12.7.0'} peerDependencies: @@ -34265,7 +34383,7 @@ packages: react: optional: true dependencies: - '@types/react': 18.2.31 + '@types/react': 18.2.33 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) dev: false From a314fbfbde4527a2d31c8f30d4bac044cb85e176 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 13:44:36 +0200 Subject: [PATCH 08/15] chore(build): reduce turbo concurrency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38706568..43364411 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "private": true, "scripts": { - "build": "turbo run build --concurrency=100%", + "build": "turbo run build --concurrency=1", "copy": "pnpm copy:frontend && pnpm copy:docs && pnpm copy:html", "copy:docs": "mkdir -p ./build/docs/ && cp -r ./docs/build/* ./build/docs/", "copy:frontend": "mkdir -p ./build/ && cp -r ./frontend/out/* ./build/ && cp ./frontend/_redirects ./build/", From 2238f7e82ad5e44e2d8cb1161199a914e71336f9 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 14:10:18 +0200 Subject: [PATCH 09/15] fix(build): whac a mole --- defender/package.json | 8 ++-- package.json | 2 +- pnpm-lock.yaml | 103 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 20 deletions(-) diff --git a/defender/package.json b/defender/package.json index f2da4c70..2d8a57f4 100644 --- a/defender/package.json +++ b/defender/package.json @@ -14,10 +14,10 @@ }, "dependencies": { "@hypercerts-org/contracts": "0.8.11", - "@openzeppelin/defender-autotask-client": "^1.48.0", - "@openzeppelin/defender-autotask-utils": "^1.48.0", - "@openzeppelin/defender-base-client": "^1.48.0", - "@openzeppelin/defender-sentinel-client": "^1.48.0", + "@openzeppelin/defender-autotask-client": "1.50.0", + "@openzeppelin/defender-autotask-utils": "1.50.0", + "@openzeppelin/defender-base-client": "1.49.0", + "@openzeppelin/defender-sentinel-client": "1.49.0", "@openzeppelin/merkle-tree": "^1.0.2", "@supabase/supabase-js": "^2.4.1", "axios": "^1.2.6", diff --git a/package.json b/package.json index 43364411..38706568 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "private": true, "scripts": { - "build": "turbo run build --concurrency=1", + "build": "turbo run build --concurrency=100%", "copy": "pnpm copy:frontend && pnpm copy:docs && pnpm copy:html", "copy:docs": "mkdir -p ./build/docs/ && cp -r ./docs/build/* ./build/docs/", "copy:frontend": "mkdir -p ./build/ && cp -r ./frontend/out/* ./build/ && cp ./frontend/_redirects ./build/", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9fd82dc..988958cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -232,16 +232,16 @@ importers: specifier: 0.8.11 version: 0.8.11 '@openzeppelin/defender-autotask-client': - specifier: ^1.48.0 + specifier: 1.50.0 version: 1.50.0 '@openzeppelin/defender-autotask-utils': - specifier: ^1.48.0 + specifier: 1.50.0 version: 1.50.0 '@openzeppelin/defender-base-client': - specifier: ^1.48.0 - version: 1.50.0(debug@4.3.4) + specifier: 1.49.0 + version: 1.49.0(debug@4.3.4) '@openzeppelin/defender-sentinel-client': - specifier: ^1.48.0 + specifier: 1.49.0 version: 1.49.0 '@openzeppelin/merkle-tree': specifier: ^1.0.2 @@ -10204,7 +10204,7 @@ packages: /@openzeppelin/defender-admin-client@1.50.0: resolution: {integrity: sha512-JxeA111ifCIzXho2gFymhepufB0ElI1UMvFIMEfJLvRL7g7V69wSiN8v+OqZyqZTahiY32Rb+TwdhVjKF5Zu+A==} dependencies: - '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0 axios: 1.5.1(debug@4.3.4) ethers: 5.7.2 lodash: 4.17.21 @@ -10220,7 +10220,7 @@ packages: resolution: {integrity: sha512-QWob3F6xuOu8r8oPy0Y2XLfAL1PTuKE2F4nC4wGeu3JJT8/pJz3xnHX5DgUYwiGIMqnkitUNUoBcmi4CPI31yw==} hasBin: true dependencies: - '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.50.0 axios: 1.5.1(debug@4.3.4) dotenv: 10.0.0 glob: 7.2.3 @@ -10236,7 +10236,7 @@ packages: resolution: {integrity: sha512-wuhm5idjsIiC7hdLj+z5ewDmyKx5q0tRXKHp05K9X8uo1CyLdHV2kKZjBrWzGE9qxVhJ79f9PzHZrLcyPHNDOg==} dev: false - /@openzeppelin/defender-base-client@1.49.0: + /@openzeppelin/defender-base-client@1.49.0(debug@4.3.4): resolution: {integrity: sha512-nG2jslaAUbo2ZW9yBStstxTPscAchN/vRdJ16M34whuZRtUp1bccCBVLdv3oiPOdjwFaa1OBXJkheN+eF8alzA==} dependencies: amazon-cognito-identity-js: 6.3.6 @@ -10247,9 +10247,8 @@ packages: transitivePeerDependencies: - debug - encoding - dev: false - /@openzeppelin/defender-base-client@1.50.0(debug@4.3.4): + /@openzeppelin/defender-base-client@1.50.0: resolution: {integrity: sha512-V5uJ4t3kr9ex1RrqGH2DwsHuyW7/hl3VK0sSkq3VVbAewtcsW3cdg/UkXd5ITu6mtz76RoYkvUBHtkYUm0nb+w==} dependencies: amazon-cognito-identity-js: 6.3.6 @@ -10265,7 +10264,7 @@ packages: resolution: {integrity: sha512-fr39U1GRWvJP1fWgwqjTYCz7uhfVfXJReWcivwxMeaoyMl+jYFxj8NkMhqkkbmI6O4TUyNMsmAQ34qFf0IS0/A==} dependencies: '@ethersproject/abi': 5.7.0 - '@openzeppelin/defender-base-client': 1.49.0 + '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) axios: 1.5.1(debug@4.3.4) lodash: 4.17.21 node-fetch: 2.7.0 @@ -10279,7 +10278,7 @@ packages: deprecated: '@openzeppelin/hardhat-defender is deprecated. This functionality is now included as part of @openzeppelin/hardhat-upgrades' dependencies: '@openzeppelin/defender-admin-client': 1.50.0 - '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) '@openzeppelin/hardhat-upgrades': 1.28.0(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.7)(ethers@5.7.2)(hardhat@2.13.1) ethereumjs-util: 7.1.5 transitivePeerDependencies: @@ -10310,7 +10309,7 @@ packages: dependencies: '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.13.1) '@nomiclabs/hardhat-etherscan': 3.1.7(hardhat@2.13.1) - '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) '@openzeppelin/platform-deploy-client': 0.8.0(debug@4.3.4) '@openzeppelin/upgrades-core': 1.31.0 chalk: 4.1.2 @@ -10335,7 +10334,7 @@ packages: deprecated: '@openzeppelin/platform-deploy-client is deprecated. Please use @openzeppelin/defender-sdk-deploy-client' dependencies: '@ethersproject/abi': 5.7.0 - '@openzeppelin/defender-base-client': 1.50.0(debug@4.3.4) + '@openzeppelin/defender-base-client': 1.49.0(debug@4.3.4) axios: 0.21.4(debug@4.3.4) lodash: 4.17.21 node-fetch: 2.7.0 @@ -12132,6 +12131,14 @@ packages: - supports-color dev: true + /@trufflesuite/bigint-buffer@1.1.10: + resolution: {integrity: sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw==} + engines: {node: '>= 14.0.0'} + requiresBuild: true + dependencies: + node-gyp-build: 4.4.0 + dev: true + /@trufflesuite/bigint-buffer@1.1.9: resolution: {integrity: sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==} engines: {node: '>= 10.0.0'} @@ -12723,6 +12730,10 @@ packages: dependencies: '@types/node': 18.18.7 + /@types/seedrandom@3.0.1: + resolution: {integrity: sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==} + dev: true + /@types/semver@7.5.4: resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} dev: true @@ -14298,7 +14309,7 @@ packages: engines: {node: '>=6'} dependencies: buffer: 5.7.1 - immediate: 3.2.3 + immediate: 3.3.0 level-concat-iterator: 2.0.1 level-supports: 1.0.1 xtend: 4.0.2 @@ -14315,6 +14326,18 @@ packages: xtend: 4.0.2 dev: true + /abstract-leveldown@7.2.0: + resolution: {integrity: sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==} + engines: {node: '>=10'} + dependencies: + buffer: 6.0.3 + catering: 2.1.1 + is-buffer: 2.0.5 + level-concat-iterator: 3.1.0 + level-supports: 2.1.0 + queue-microtask: 1.2.3 + dev: true + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -20499,6 +20522,15 @@ packages: /ganache@7.4.3: resolution: {integrity: sha512-RpEDUiCkqbouyE7+NMXG26ynZ+7sGiODU84Kz+FVoXUnQ4qQM4M8wif3Y4qUCt+D/eM1RVeGq0my62FPD6Y1KA==} hasBin: true + dependencies: + '@trufflesuite/bigint-buffer': 1.1.10 + '@types/bn.js': 5.1.3 + '@types/lru-cache': 5.1.1 + '@types/seedrandom': 3.0.1 + emittery: 0.10.0 + keccak: 3.0.2 + leveldown: 6.1.0 + secp256k1: 4.0.3 optionalDependencies: bufferutil: 4.0.5 utf-8-validate: 5.0.7 @@ -24602,6 +24634,16 @@ packages: node-gyp-build: 4.6.1 dev: true + /keccak@3.0.2: + resolution: {integrity: sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==} + engines: {node: '>=10.0.0'} + requiresBuild: true + dependencies: + node-addon-api: 2.0.2 + node-gyp-build: 4.6.1 + readable-stream: 3.6.2 + dev: true + /keccak@3.0.4: resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} engines: {node: '>=10.0.0'} @@ -24708,6 +24750,13 @@ packages: engines: {node: '>=6'} dev: true + /level-concat-iterator@3.1.0: + resolution: {integrity: sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==} + engines: {node: '>=10'} + dependencies: + catering: 2.1.1 + dev: true + /level-errors@2.0.1: resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==} engines: {node: '>=6'} @@ -24747,6 +24796,11 @@ packages: xtend: 4.0.2 dev: true + /level-supports@2.1.0: + resolution: {integrity: sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==} + engines: {node: '>=10'} + dev: true + /level-supports@4.0.1: resolution: {integrity: sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==} engines: {node: '>=12'} @@ -24774,6 +24828,16 @@ packages: browser-level: 1.0.1 classic-level: 1.3.0 + /leveldown@6.1.0: + resolution: {integrity: sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==} + engines: {node: '>=10.12.0'} + requiresBuild: true + dependencies: + abstract-leveldown: 7.2.0 + napi-macros: 2.0.0 + node-gyp-build: 4.6.1 + dev: true + /levelup@4.4.0: resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==} engines: {node: '>=6'} @@ -26686,6 +26750,10 @@ packages: hasBin: true dev: false + /napi-macros@2.0.0: + resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} + dev: true + /napi-macros@2.2.2: resolution: {integrity: sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==} @@ -26876,6 +26944,11 @@ packages: dev: true optional: true + /node-gyp-build@4.4.0: + resolution: {integrity: sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==} + hasBin: true + dev: true + /node-gyp-build@4.6.1: resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} hasBin: true From 7e3fd830331dede77a9ac9e5d1a58c43d6185e08 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 15:28:38 +0200 Subject: [PATCH 10/15] chore(build): restore prettier in solhint --- contracts/.solhint.json | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/.solhint.json b/contracts/.solhint.json index fad41466..c7aea11c 100644 --- a/contracts/.solhint.json +++ b/contracts/.solhint.json @@ -1,4 +1,5 @@ { + "plugins": ["prettier"], "extends": "solhint:recommended", "rules": { "code-complexity": ["error", 12], From aa1a0910eb7045ab2ad744768f39439d180731a3 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 15:48:59 +0200 Subject: [PATCH 11/15] chore(build): env.template vars --- contracts/.env.example | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/.env.example b/contracts/.env.example index b7c57fac..8cef70d1 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -2,7 +2,11 @@ MNEMONIC="test test test test test test test test test test test junk" MNEMONIC_CELO=="test test test test test test test test test test test junk" INFURA_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" -ALCHEMY_OPTIMISM_URL="https://opt-mainnet.g.alchemy.com/v2/zzzzzzzzzzzzzzzzzzz" + +# RPCs +MAINNET_RPC_URL="" +OPTIMISM_RPC_URL="" +GOERLI_RPC_URL="" # OpenZeppelin OPENZEPPELIN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" From 465ce09b57da7630b52ba6618528689e02ea6518 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 16:40:02 +0200 Subject: [PATCH 12/15] chore(dist): clean and remap output contracts --- contracts/package.json | 2 +- contracts/src/index.ts | 10 +- contracts/src/protocol/HypercertTrader.sol | 405 ------------------ .../protocol/interfaces/IHypercertTrader.sol | 128 ------ contracts/tsconfig.build.json | 10 +- contracts/tsconfig.json | 10 +- graph/tests/.latest.json | 2 +- 7 files changed, 17 insertions(+), 550 deletions(-) delete mode 100644 contracts/src/protocol/HypercertTrader.sol delete mode 100644 contracts/src/protocol/interfaces/IHypercertTrader.sol diff --git a/contracts/package.json b/contracts/package.json index 5d06aefd..36d2c440 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -103,7 +103,7 @@ "scripts": { "build": "hardhat compile && pnpm tsc -p tsconfig.build.json && rollup -c && pnpm copy:contracts", "build:forge": "forge build", - "clean": "rimraf cache out dist typechain abi", + "clean": "rimraf cache out dist src/typechain src/abi", "copy:contracts": "copyfiles -u 1 ./src/**/*.sol ./src/*.sol ./contracts", "docs": "hardhat dodoc", "lint": "pnpm lint:sol && pnpm prettier:check", diff --git a/contracts/src/index.ts b/contracts/src/index.ts index 486b728b..641f675b 100644 --- a/contracts/src/index.ts +++ b/contracts/src/index.ts @@ -1,10 +1,10 @@ import HypercertMinterAbi from "./abi/HypercertMinter.json"; // import { HypercertMinter__factory } from "./types/factories/src/HypercertMinter__factory"; -import type { AllowlistMinter } from "./types/src/AllowlistMinter"; -import type { HypercertMinter } from "./types/src/HypercertMinter"; -import type { IAllowlist } from "./types/src/interfaces/IAllowlist"; -import type { IHypercertToken } from "./types/src/interfaces/IHypercertToken"; -import type { Errors } from "./types/src/libs/Errors"; +import type { AllowlistMinter } from "./types/src/protocol/AllowlistMinter"; +import type { HypercertMinter } from "./types/src/protocol/HypercertMinter"; +import type { IAllowlist } from "./types/src/protocol/interfaces/IAllowlist"; +import type { IHypercertToken } from "./types/src/protocol/interfaces/IHypercertToken"; +import type { Errors } from "./types/src/protocol/libs/Errors"; /* in order to adjust the build folder: 1) import any files here you want in the final build package. diff --git a/contracts/src/protocol/HypercertTrader.sol b/contracts/src/protocol/HypercertTrader.sol deleted file mode 100644 index 7f9aae3f..00000000 --- a/contracts/src/protocol/HypercertTrader.sol +++ /dev/null @@ -1,405 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import { IHypercertTrader } from "./interfaces/IHypercertTrader.sol"; -import { IHypercertToken } from "./interfaces/IHypercertToken.sol"; -import { PausableUpgradeable } from "oz-upgradeable/security/PausableUpgradeable.sol"; -import { IERC1155Upgradeable } from "oz-upgradeable/token/ERC1155/IERC1155Upgradeable.sol"; -import { OwnableUpgradeable } from "oz-upgradeable/access/OwnableUpgradeable.sol"; -import { Initializable } from "oz-upgradeable/proxy/utils/Initializable.sol"; -import { UUPSUpgradeable } from "oz-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -import { Errors } from "./libs/Errors.sol"; - -error NotAllowed(); -error InvalidOffer(string); -error InvalidBuy(string); - -interface IHypercertMinter { - function ownerOf(uint256 id) external view returns (address); - - function unitsOf(uint256 id) external view returns (uint256); -} - -/** - * @title Contract for managing hypercert trades - * @notice Implementation of the HypercertTrader Interface - * @author bitbeckers - */ -contract HypercertTrader is IHypercertTrader, Initializable, OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { - mapping(address => mapping(uint256 => uint256)) public totalUnitsForSale; - mapping(uint256 => Offer) public offers; - uint256 internal _offerCounter; - - /// INIT - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol } - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol } - function initialize() public virtual initializer { - __Pausable_init(); - __Ownable_init(); - __UUPSUpgradeable_init(); - } - - function getOffer(uint256 offerID) external view returns (Offer memory) { - return offers[offerID]; - } - - /** - * @dev Creates a new offer to sell Hypercert tokens. - * @param hypercertContract The address of the Hypercert token contract. - * @param fractionID The ID of the fraction to sell. - * @param unitsForSale The number of units available for sale. - * @param minUnitsPerTrade The minimum number of units that can be bought in a single trade. - * @param maxUnitsPerTrade The maximum number of units that can be bought in a single trade. - * @param acceptedTokens The list of tokens that are accepted for payment. - * @notice This function creates a new offer to sell Hypercert tokens. The offer specifies the Hypercert token contract, - * the fraction to sell, the number of units available for sale, the minimum and maximum number of units that can be bought - * in a single trade, and the list of tokens that are accepted for payment. The offer is added to the list of existing offers - * and can be bought by other users using the `buyUnits` function. - */ - //TODO remove payable from offer - function createOffer( - address hypercertContract, - uint256 fractionID, - uint256 unitsForSale, - uint256 minUnitsPerTrade, - uint256 maxUnitsPerTrade, - AcceptedToken[] memory acceptedTokens - ) external payable whenNotPaused returns (uint256 offerID) { - // This checks is the contract is approved to trade on behalf of the owner at the time of offering. - // However, the owner can revoke this approval at any time. - if (!IERC1155Upgradeable(hypercertContract).isApprovedForAll(msg.sender, address(this))) revert NotAllowed(); - - _validateOffer( - msg.sender, - hypercertContract, - fractionID, - unitsForSale, - minUnitsPerTrade, - maxUnitsPerTrade, - acceptedTokens - ); - - offerID = _offerCounter; - offers[offerID] = _createOffer( - msg.sender, - hypercertContract, - fractionID, - unitsForSale, - minUnitsPerTrade, - maxUnitsPerTrade, - acceptedTokens - ); - - _offerCounter += 1; - totalUnitsForSale[hypercertContract][fractionID] += unitsForSale; - - emit OfferCreated(msg.sender, hypercertContract, fractionID, offerID); - } - - /** - * @dev Buys Hypercert tokens from an existing offer. - * @param recipient The address that will receive the Hypercert tokens. - * @param offerID The ID of the offer to buy from. - * @param unitAmount The number of units to buy. - * @param buyToken The address of the token used for payment. - * @param tokenAmountPerUnit The amount of tokens to pay per unit. - * @notice This function buys Hypercert tokens from an existing offer. The function verifies that the offer is valid and that - * the buyer has provided enough payment in the specified token. If the offer is for a fraction of a Hypercert token, the function - * splits the fraction and transfers the appropriate number of units to the buyer. If the offer is for a fixed number of units, - * the function transfers the units to the buyer. The function also transfers the payment to the offerer and emits a `Trade` event. - */ - function buyUnits( - address recipient, - uint256 offerID, - uint256 unitAmount, - address buyToken, - uint256 tokenAmountPerUnit - ) public payable whenNotPaused { - // Get the offer and validate that it is open and has enough units available - Offer storage offer = offers[offerID]; - _validateBuyOffer(offer, unitAmount, buyToken, tokenAmountPerUnit); - - offer.unitsAvailable -= unitAmount; - totalUnitsForSale[offer.hypercertContract][offer.fractionID] -= unitAmount; - - if (offer.unitsAvailable == 0) { - offer.status = OfferStatus.Fulfilled; - } - - uint256 unitsInFraction = IHypercertMinter(offer.hypercertContract).unitsOf(offer.fractionID); - - // Check if full fraction is being bought - if (unitsInFraction == unitAmount) { - IERC1155Upgradeable(offer.hypercertContract).safeTransferFrom( - offer.offerer, - recipient, - offer.fractionID, - 1, - "" - ); - } else { - // Create uint256[] for the split with the remaining units and units to transfer - uint256[] memory units = new uint256[](2); - units[0] = unitsInFraction - unitAmount; - units[1] = unitAmount; - - IHypercertToken(offer.hypercertContract).splitFraction(recipient, offer.fractionID, units); - } - (bool success, ) = payable(offer.offerer).call{ value: msg.value }(""); - if (!success) revert InvalidBuy("Payment failed"); - - emit Trade( - offer.offerer, - recipient, - offer.hypercertContract, - offer.fractionID, - unitAmount, - buyToken, - tokenAmountPerUnit, - offerID - ); - } - - /** - * @dev Buys Hypercert tokens from multiple existing offers in a single transaction. - * @param recipient The address that will receive the Hypercert tokens. - * @param offerIDs The list of IDs of the offers to buy from. - * @param unitAmounts The list of numbers of units to buy for each offer. - * @param buyTokens The list of addresses of the tokens used for payment for each offer. - * @param tokenAmountsPerUnit The list of amounts of tokens to pay per unit for each offer. - * @notice This function allows a user to buy Hypercert tokens from multiple existing offers in a single transaction. - * The function takes in several arrays of parameters, including the IDs of the offers to buy from, - * the number of units to buy for each offer, the tokens used for payment for each offer, - * and the amounts of tokens to pay per unit for each offer. - * The function then executes the trades and transfers the Hypercert tokens to the specified recipient. - */ - function batchBuyUnits( - address recipient, - uint256[] calldata offerIDs, - uint256[] calldata unitAmounts, - address[] calldata buyTokens, - uint256[] calldata tokenAmountsPerUnit - ) external payable whenNotPaused { - uint256 len = offerIDs.length; - for (uint256 i; i < len; ) { - buyUnits(recipient, offerIDs[i], unitAmounts[i], buyTokens[i], tokenAmountsPerUnit[i]); - unchecked { - ++i; - } - } - } - - /** - * @dev Cancels an existing offer. - * @param offerID The ID of the offer to cancel. - * @notice This function cancels an existing offer. The function verifies that the offer exists and that the caller is the offerer. - * The function sets the offer status to `Cancelled` and emits an `OfferCancelled` event. - */ - function cancelOffer(uint256 offerID) external whenNotPaused { - // Get the offer and validate that the caller is the offerer - Offer storage offer = offers[offerID]; - if (offer.hypercertContract == address(0)) revert InvalidOffer("No contract address found"); - if (offer.offerer != msg.sender) revert NotAllowed(); - if (offer.status != OfferStatus.Open) revert InvalidOffer("status"); - - // Update the offer and emit an OfferCancelled event - offer.status = OfferStatus.Cancelled; - totalUnitsForSale[offer.hypercertContract][offer.fractionID] -= offer.unitsAvailable; - emit OfferCancelled(msg.sender, offer.hypercertContract, offer.fractionID, offerID); - } - - /// PAUSABLE - - function pause() external onlyOwner { - _pause(); - } - - function unpause() external onlyOwner { - _unpause(); - } - - /// INTERNAL - - /** - * @dev Internal function to create a new offer to sell Hypercert tokens. - * @param offerer The address of the offerer. - * @param hypercertContract The address of the Hypercert token contract. - * @param fractionID The ID of the fraction to sell. - * @param unitsAvailable The number of units available for sale. - * @param minUnitsPerTrade The minimum number of units that can be bought in a single trade. - * @param maxUnitsPerTrade The maximum number of units that can be bought in a single trade. - * @param acceptedTokens The list of tokens that are accepted for payment. - * @return The storage reference to the newly created offer. - */ - function _createOffer( - address offerer, - address hypercertContract, - uint256 fractionID, - uint256 unitsAvailable, - uint256 minUnitsPerTrade, - uint256 maxUnitsPerTrade, - AcceptedToken[] memory acceptedTokens - ) internal returns (Offer storage) { - // TODO optimise init and adding accepted tokens - Offer storage offer = offers[_offerCounter]; - offer.offerer = offerer; - offer.hypercertContract = hypercertContract; - offer.fractionID = fractionID; - offer.unitsAvailable = unitsAvailable; - offer.minUnitsPerTrade = minUnitsPerTrade; - offer.maxUnitsPerTrade = maxUnitsPerTrade; - offer.status = OfferStatus.Open; - - if (minUnitsPerTrade == maxUnitsPerTrade) { - offer.offerType = OfferType.Fraction; - } else { - offer.offerType = OfferType.Units; - } - - uint256 len = acceptedTokens.length; - for (uint256 i; i < len; ) { - offer.acceptedTokens.push(AcceptedToken(acceptedTokens[i].token, acceptedTokens[i].minimumAmountPerUnit)); - - unchecked { - ++i; - } - } - - return offer; - } - - /** - * @dev Validates the parameters for a new offer. - * @param hypercertContract The address of the Hypercert token contract. - * @param fractionID The ID of the fraction to sell. - * @param unitsForSale The number of units available for sale. - * @param minUnitsPerTrade The minimum number of units that can be bought in a single trade. - * @param maxUnitsPerTrade The maximum number of units that can be bought in a single trade. - * @param acceptedTokens The list of tokens that are accepted for payment. - * @notice This internal function validates the parameters for a new offer. The function verifies that the Hypercert token contract - * exists and that the fraction ID is valid. The function also verifies that the units available, minimum units per trade, and maximum - * units per trade are valid and that the accepted tokens list is not empty. - */ - function _validateOffer( - address offerer, - address hypercertContract, - uint256 fractionID, - uint256 unitsForSale, - uint256 minUnitsPerTrade, - uint256 maxUnitsPerTrade, - AcceptedToken[] memory acceptedTokens - ) internal view returns (bool) { - if (IHypercertMinter(hypercertContract).ownerOf(fractionID) != offerer) revert InvalidOffer("Not owner"); - - // Validate units exist and are available - uint256 totalUnits = IHypercertMinter(hypercertContract).unitsOf(fractionID); - if ( - unitsForSale == 0 || - totalUnits < unitsForSale || - totalUnits < totalUnitsForSale[hypercertContract][fractionID] + unitsForSale - ) { - revert InvalidOffer("Insufficient units"); - } - - // Validate min/max units per trade - if (maxUnitsPerTrade > unitsForSale || minUnitsPerTrade > maxUnitsPerTrade) { - revert InvalidOffer("Min/Max units"); - } - - // If sale is for a fraction, the units must be a multiple of the minimum units per trade - if (minUnitsPerTrade == maxUnitsPerTrade && unitsForSale % minUnitsPerTrade != 0) { - revert InvalidOffer("Units indivisible by fractions"); - } - - // Minimum amount per unit must be greater than 0 - if (acceptedTokens.length == 0 || acceptedTokens[0].minimumAmountPerUnit == 0) { - revert InvalidOffer("No accepted tokens"); - } - - // for now only accept the native token, ZERO_ADDRESS - if (acceptedTokens.length != 1 || acceptedTokens[0].token != address(0)) { - revert InvalidOffer("Only zero token"); - } - } - - /** - * @dev Validates an existing offer for a buy operation. - * @param offer The offer to validate. - * @param unitAmountsToBuy The number of units to buy. - * @param buyToken The address of the token used for payment. - * @param tokenAmountPerUnit The amount of tokens to pay per unit. - * @notice This internal function validates an existing offer for a buy operation. The function verifies that the offer exists, - * is open, and has enough units available for sale. The function also verifies that the specified token is accepted for payment - * and that the buyer has provided enough payment in the specified token. - */ - function _validateBuyOffer( - Offer memory offer, - uint256 unitAmountsToBuy, - address buyToken, - uint256 tokenAmountPerUnit - ) internal { - if (offer.status != OfferStatus.Open || offer.offerType != OfferType.Units) { - revert InvalidOffer("Wrong status"); - } - - if ( - offer.unitsAvailable < unitAmountsToBuy || - offer.minUnitsPerTrade > unitAmountsToBuy || - offer.maxUnitsPerTrade < unitAmountsToBuy - ) revert InvalidOffer("Min/Max units"); - - // Check for sufficient funds; currently only native token - if (buyToken != address(0) || (unitAmountsToBuy * tokenAmountPerUnit) > msg.value) { - revert InvalidOffer("Wrong token/value"); - } - - // TODO check on overpayment??? - - //TODO optimize array search or use mapping - // Check for accepted tokens - uint256 len = offer.acceptedTokens.length; - bool tokenAccepted = false; - for (uint256 i; i < len; ) { - if (!tokenAccepted) { - tokenAccepted = offer.acceptedTokens[i].token == buyToken; - } - - if ( - offer.acceptedTokens[i].token == buyToken && - offer.acceptedTokens[i].minimumAmountPerUnit > tokenAmountPerUnit - ) { - revert InvalidOffer("Wrong token/value"); - } - - unchecked { - ++i; - } - } - if (!tokenAccepted) revert InvalidOffer("Wrong token/value"); - } - - /// INTERNAL - - /// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner { - // solhint-disable-previous-line no-empty-blocks - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - * Assuming 30 available slots (slots cost space, cost gas) - * 1. totalUnitsForSale - * 2. offers - * 3. _offerCounter - */ - uint256[27] private __gap; -} diff --git a/contracts/src/protocol/interfaces/IHypercertTrader.sol b/contracts/src/protocol/interfaces/IHypercertTrader.sol deleted file mode 100644 index a8ee04f6..00000000 --- a/contracts/src/protocol/interfaces/IHypercertTrader.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -/// @title Interface for hypercert token trading -/// @author bitbeckers -/// @notice This interface declares the required functionality to interact with the hypercert marketplace -interface IHypercertTrader { - /** - * @dev Struct for an offer to sell Hypercert tokens. - */ - struct Offer { - address offerer; - address hypercertContract; - uint256 fractionID; - uint256 unitsAvailable; - uint256 minUnitsPerTrade; - uint256 maxUnitsPerTrade; - OfferType offerType; - OfferStatus status; - AcceptedToken[] acceptedTokens; - } - - /** - * @dev Enum for the type of offer (Units or Fraction). - */ - enum OfferType { - Units, - Fraction - } - - /** - * @dev Enum for the status of an offer (Open, Fulfilled, or Cancelled). - */ - enum OfferStatus { - Open, - Fulfilled, - Cancelled - } - - /** - * @dev Struct for a token that is accepted for payment. - */ - struct AcceptedToken { - address token; - uint256 minimumAmountPerUnit; - } - - event OfferCreated( - address indexed offerer, - address indexed hypercertContract, - uint256 indexed fractionID, - uint256 offerID - ); - - event Trade( - address indexed seller, - address indexed buyer, - address indexed hypercertContract, - uint256 fractionID, - uint256 unitsBought, - address buyToken, - uint256 tokenAmountPerUnit, - uint256 offerID - ); - - event OfferCancelled( - address indexed creator, - address indexed hypercertContract, - uint256 indexed fractionID, - uint256 offerID - ); - - /** - * @dev Creates a new offer to sell Hypercert tokens. - * @param hypercertContract The address of the Hypercert token contract. - * @param fractionID The ID of the fraction to sell. - * @param units The number of units available for sale. - * @param minUnitsPerTrade The minimum number of units that can be bought in a single trade. - * @param maxUnitsPerTrade The maximum number of units that can be bought in a single trade. - * @param acceptedTokens The list of tokens that are accepted for payment. - */ - function createOffer( - address hypercertContract, - uint256 fractionID, - uint256 units, - uint256 minUnitsPerTrade, - uint256 maxUnitsPerTrade, - AcceptedToken[] memory acceptedTokens - ) external payable returns (uint256 offerID); - - /** - * @dev Buys Hypercert tokens from an existing offer. - * @param recipient The address that will receive the Hypercert tokens. - * @param offerID The ID of the offer to buy from. - * @param unitAmount The number of units to buy. - * @param buyToken The address of the token used for payment. - * @param tokenAmountPerUnit The amount of tokens to pay per unit. - */ - function buyUnits( - address recipient, - uint256 offerID, - uint256 unitAmount, - address buyToken, - uint256 tokenAmountPerUnit - ) external payable; - - /** - * @dev Buys Hypercert tokens from multiple existing offers in a single transaction. - * @param recipient The address that will receive the Hypercert tokens. - * @param offerIDs The list of IDs of the offers to buy from. - * @param unitAmounts The list of numbers of units to buy for each offer. - * @param buyTokens The list of addresses of the tokens used for payment for each offer. - * @param tokenAmountsPerUnit The list of amounts of tokens to pay per unit for each offer. - */ - function batchBuyUnits( - address recipient, - uint256[] calldata offerIDs, - uint256[] calldata unitAmounts, - address[] calldata buyTokens, - uint256[] calldata tokenAmountsPerUnit - ) external payable; - - /** - * @dev Cancels an existing offer. - * @param offerID The ID of the offer to cancel. - */ - function cancelOffer(uint256 offerID) external; -} diff --git a/contracts/tsconfig.build.json b/contracts/tsconfig.build.json index d2a57421..baa92593 100644 --- a/contracts/tsconfig.build.json +++ b/contracts/tsconfig.build.json @@ -23,10 +23,10 @@ "include": ["./src"], "files": [ "src/abi/HypercertMinter.json", - "src/types/src/AllowlistMinter.ts", - "src/types/src/HypercertMinter.ts", - "src/types/src/interfaces/IAllowlist.ts", - "src/types/src/interfaces/IHypercertToken.ts", - "src/types/src/libs/Errors.ts" + "src/types/src/protocol/AllowlistMinter.ts", + "src/types/src/protocol/HypercertMinter.ts", + "src/types/src/protocol/interfaces/IAllowlist.ts", + "src/types/src/protocol/interfaces/IHypercertToken.ts", + "src/types/src/protocol/libs/Errors.ts" ] } diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index 8d0a6cfc..b58408fb 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -24,10 +24,10 @@ "files": [ "hardhat.config.ts", "src/abi/HypercertMinter.json", - "src/types/src/AllowlistMinter.ts", - "src/types/src/HypercertMinter.ts", - "src/types/src/interfaces/IAllowlist.ts", - "src/types/src/interfaces/IHypercertToken.ts", - "src/types/src/libs/Errors.ts" + "src/types/src/protocol/AllowlistMinter.ts", + "src/types/src/protocol/HypercertMinter.ts", + "src/types/src/protocol/interfaces/IAllowlist.ts", + "src/types/src/protocol/interfaces/IHypercertToken.ts", + "src/types/src/protocol/libs/Errors.ts" ] } diff --git a/graph/tests/.latest.json b/graph/tests/.latest.json index 29ed621c..ab8a5ffe 100644 --- a/graph/tests/.latest.json +++ b/graph/tests/.latest.json @@ -1,4 +1,4 @@ { "version": "0.6.0", - "timestamp": 1698242510644 + "timestamp": 1698330729535 } From 2b08ae588c7b8c8106a32d01b77fed2974077b9b Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Thu, 26 Oct 2023 16:44:39 +0200 Subject: [PATCH 13/15] chore(clean): last tests --- .../protocol/HypercertTrader.admin.t.sol | 88 --- .../protocol/HypercertTrader.offers.t.sol | 204 ----- .../protocol/HypercertTrader.sales.t.sol | 241 ------ .../api/contracts/AllowlistMinter.md | 75 +- .../api/contracts/HypercertMinter.md | 728 +++++++++++------- .../api/contracts/SemiFungible1155.md | 393 ++++++---- .../api/contracts/interfaces/IAllowlist.md | 28 +- .../contracts/interfaces/IHypercertToken.md | 149 ++-- .../developer/api/contracts/libs/Errors.md | 57 +- 9 files changed, 908 insertions(+), 1055 deletions(-) delete mode 100644 contracts/test/foundry/protocol/HypercertTrader.admin.t.sol delete mode 100644 contracts/test/foundry/protocol/HypercertTrader.offers.t.sol delete mode 100644 contracts/test/foundry/protocol/HypercertTrader.sales.t.sol diff --git a/contracts/test/foundry/protocol/HypercertTrader.admin.t.sol b/contracts/test/foundry/protocol/HypercertTrader.admin.t.sol deleted file mode 100644 index 817a6bb9..00000000 --- a/contracts/test/foundry/protocol/HypercertTrader.admin.t.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; - -import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; -import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; -import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; -import { PRBTest } from "prb-test/PRBTest.sol"; -import { StdCheats } from "forge-std/StdCheats.sol"; -import { StdUtils } from "forge-std/StdUtils.sol"; - -interface IHypercertMinter { - function ownerOf(uint256 id) external view returns (address); - - function unitsOf(uint256 id) external view returns (uint256); -} - -contract HypercertTraderHelper is HypercertTrader { - error NotAllowed(); - error InvalidOffer(string); - - // TODO - Use UUPS pattern for contracts and getting owner - address internal alice = address(1); - address internal bob = address(2); - - HypercertMinter public hypercertMinter = new HypercertMinter(); - - function getOfferCount() external view returns (uint256) { - return _offerCounter; - } -} - -contract HypercertTraderAdminTest is HypercertTraderHelper, PRBTest, StdCheats, StdUtils { - HypercertTraderHelper internal hypercertTrader; - - function setUp() public { - hypercertTrader = new HypercertTraderHelper(); - } - - function testPausability() public { - AcceptedToken[] memory acceptedTokens = new AcceptedToken[](1); - acceptedTokens[0] = AcceptedToken(address(0), 1); - - // Contract is not paused - assertEq(hypercertTrader.paused(), false); - - // Bob can't pause the contracts - vm.startPrank(bob); - vm.expectRevert("Ownable: caller is not the owner"); - hypercertTrader.pause(); - - // Owner can pause the contract - vm.startPrank(owner()); - vm.expectEmit(true, false, false, false); - emit Paused(owner()); - hypercertTrader.pause(); - - // All functions are paused - vm.expectRevert("Pausable: paused"); - hypercertTrader.createOffer(address(hypercertMinter), 1, 1, 1, 1, acceptedTokens); - - vm.expectRevert("Pausable: paused"); - hypercertTrader.cancelOffer(1); - - vm.expectRevert("Pausable: paused"); - hypercertTrader.buyUnits(alice, 1, 1, address(0), 1); - - // Bob can't unpause the contract - vm.startPrank(bob); - vm.expectRevert("Ownable: caller is not the owner"); - hypercertTrader.unpause(); - - // Owner can unpause the contract - vm.startPrank(owner()); - vm.expectEmit(true, false, false, false); - emit Unpaused(owner()); - hypercertTrader.unpause(); - - // All functions are unpaused - vm.expectRevert(NotAllowed.selector); - hypercertTrader.createOffer(address(hypercertMinter), 1, 1, 1, 1, acceptedTokens); - - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "No contract address found")); - hypercertTrader.cancelOffer(1); - - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Min/Max units")); - hypercertTrader.buyUnits(alice, 1, 1, address(0), 1); - } -} diff --git a/contracts/test/foundry/protocol/HypercertTrader.offers.t.sol b/contracts/test/foundry/protocol/HypercertTrader.offers.t.sol deleted file mode 100644 index a348e9ed..00000000 --- a/contracts/test/foundry/protocol/HypercertTrader.offers.t.sol +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; - -import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; -import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; -import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; -import { PRBTest } from "prb-test/PRBTest.sol"; -import { StdCheats } from "forge-std/StdCheats.sol"; -import { StdUtils } from "forge-std/StdUtils.sol"; - -interface IHypercertMinter { - function ownerOf(uint256 id) external view returns (address); - - function unitsOf(uint256 id) external view returns (uint256); -} - -contract HypercertTraderHelper is HypercertTrader { - error NotAllowed(); - error InvalidOffer(string); - - address alice = address(1); - address bob = address(2); - - HypercertMinter public hypercertMinter = new HypercertMinter(); - - function getOfferCount() external view returns (uint256) { - return _offerCounter; - } - - function exposedValidateBuyOffer( - address offerer, - address hypercertContract, - uint256 fractionID, - uint256 units, - uint256 minUnitsPerTrade, - uint256 maxUnitsPerTrade, - AcceptedToken[] memory acceptedTokens - ) external payable { - _validateOffer( - offerer, - hypercertContract, - fractionID, - units, - minUnitsPerTrade, - maxUnitsPerTrade, - acceptedTokens - ); - } -} - -contract HypercertTraderCreateOfferTest is HypercertTraderHelper, PRBTest, StdCheats, StdUtils { - HypercertTraderHelper internal hypercertTrader; - - function setUp() public { - hypercertTrader = new HypercertTraderHelper(); - } - - function testCreateOfferWithAllowance() public { - // Setup - vm.startPrank(alice); - hypercertMinter.mintClaim(alice, 10000, "ipfs://test", IHypercertToken.TransferRestrictions.FromCreatorOnly); - - uint256 baseID = 1 << 128; - uint256 tokenIndex = 1; - uint256 tokenID = baseID + tokenIndex; - - AcceptedToken[] memory acceptedTokens = new AcceptedToken[](1); - acceptedTokens[0] = AcceptedToken(address(0), 1); - - // Reverts when Trader contract is not approved - vm.expectRevert(NotAllowed.selector); - hypercertTrader.createOffer(address(hypercertMinter), tokenID, 1, 1, 1, acceptedTokens); - - // Set approval - hypercertMinter.setApprovalForAll(address(hypercertTrader), true); - - // Alice creates an offer - vm.expectEmit(true, true, true, true); - emit OfferCreated(alice, address(hypercertMinter), tokenID, 0); - uint256 offerID = hypercertTrader.createOffer(address(hypercertMinter), tokenID, 1, 1, 1, acceptedTokens); - - assertEq(hypercertTrader.getOfferCount(), 1); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertMinter), tokenID), 1); - - // Offer is created - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(offer.fractionID, tokenID); - assertEq(offer.unitsAvailable, 1); - assertEq(offer.minUnitsPerTrade, 1); - assertEq(offer.maxUnitsPerTrade, 1); - assertEq(offer.acceptedTokens.length, 1); - assertEq(uint256(offer.status), uint256(OfferStatus.Open)); - } - - function testCannotCreateOfferForNonExistentToken() public { - hypercertMinter.setApprovalForAll(address(hypercertTrader), true); - - AcceptedToken[] memory acceptedTokens = new AcceptedToken[](1); - acceptedTokens[0] = AcceptedToken(address(0), 1); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Not owner")); - hypercertTrader.createOffer(address(hypercertMinter), 1, 1, 1, 1, acceptedTokens); - } - - function testCanCancelOffer() public { - // Setup - vm.startPrank(alice); - hypercertMinter.mintClaim(alice, 10000, "ipfs://test", IHypercertToken.TransferRestrictions.FromCreatorOnly); - - uint256 baseID = 1 << 128; - uint256 tokenIndex = 1; - uint256 tokenID = baseID + tokenIndex; - - AcceptedToken[] memory acceptedTokens = new AcceptedToken[](1); - acceptedTokens[0] = AcceptedToken(address(0), 1); - - hypercertMinter.setApprovalForAll(address(hypercertTrader), true); - - // Alice creates an offer - vm.expectEmit(true, true, true, true); - emit OfferCreated(alice, address(hypercertMinter), tokenID, 0); - uint256 offerID = hypercertTrader.createOffer(address(hypercertMinter), tokenID, 1, 1, 1, acceptedTokens); - - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(offer.fractionID, tokenID); - assertEq(uint256(offer.status), uint256(OfferStatus.Open)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertMinter), tokenID), 1); - - // Bob tries to cancel the offer and is rejected - changePrank(bob); - vm.expectRevert(NotAllowed.selector); - hypercertTrader.cancelOffer(offerID); - - // Alice cancels the offer - changePrank(alice); - vm.expectEmit(true, true, true, true); - emit OfferCancelled(alice, address(hypercertMinter), tokenID, 0); - hypercertTrader.cancelOffer(offerID); - - // The offer is cancelled - Offer memory updatedOffer = hypercertTrader.getOffer(offerID); - assertEq(uint256(updatedOffer.status), uint256(OfferStatus.Cancelled)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertMinter), tokenID), 0); - } - - function testOfferValidations() public { - vm.mockCall( - address(hypercertMinter), - abi.encodeWithSelector(IHypercertMinter.ownerOf.selector, 42), - abi.encode(alice) - ); - vm.mockCall( - address(hypercertMinter), - abi.encodeWithSelector(IHypercertMinter.unitsOf.selector, 42), - abi.encode(10) - ); - AcceptedToken[] memory acceptedToken = new AcceptedToken[](1); - acceptedToken[0] = AcceptedToken(address(0), 1); - - // Fail on 0 units - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Insufficient units")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 0, 1, 1, acceptedToken); - - // Fail on more units than in the fraction - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Insufficient units")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 11, 1, 1, acceptedToken); - - // Fail on higher than max units - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Min/Max units")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 1, 999, acceptedToken); - - // Fail on lower than min units - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Min/Max units")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 999, 1, acceptedToken); - - // Fail when min-max are equal but the units are not divisible by the min-max value - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Units indivisible by fractions")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 7, 2, 2, acceptedToken); - - // Fail when there's no accepted token - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "No accepted tokens")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 1, 1, new AcceptedToken[](0)); - - // Fail when the accepted token has a minimum amount per unit of 0 - AcceptedToken[] memory invalidAcceptedToken = new AcceptedToken[](1); - invalidAcceptedToken[0] = AcceptedToken(address(0), 0); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "No accepted tokens")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 1, 1, invalidAcceptedToken); - - // Fail when accepted token that's not the native token - AcceptedToken[] memory nonNativeAcceptedToken = new AcceptedToken[](1); - nonNativeAcceptedToken[0] = AcceptedToken(address(1), 1); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Only zero token")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 1, 1, nonNativeAcceptedToken); - - // Fail on insufficient units in fraction - vm.mockCall( - address(hypercertMinter), - abi.encodeWithSelector(IHypercertMinter.unitsOf.selector, 42), - abi.encode(1) - ); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Insufficient units")); - hypercertTrader.exposedValidateBuyOffer(alice, address(hypercertMinter), 42, 2, 1, 1, new AcceptedToken[](0)); - } -} diff --git a/contracts/test/foundry/protocol/HypercertTrader.sales.t.sol b/contracts/test/foundry/protocol/HypercertTrader.sales.t.sol deleted file mode 100644 index 48f65355..00000000 --- a/contracts/test/foundry/protocol/HypercertTrader.sales.t.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; - -import { HypercertTrader } from "@hypercerts/protocol/HypercertTrader.sol"; -import { HypercertMinter } from "@hypercerts/protocol/HypercertMinter.sol"; -import { IHypercertToken } from "@hypercerts/protocol/interfaces/IHypercertToken.sol"; -import { PRBTest } from "prb-test/PRBTest.sol"; -import { StdCheats } from "forge-std/StdCheats.sol"; -import { StdUtils } from "forge-std/StdUtils.sol"; - -interface IHypercertMinter { - function ownerOf(uint256 id) external view returns (address); - - function unitsOf(uint256 id) external view returns (uint256); -} - -contract HypercertTraderHelper is HypercertTrader, PRBTest, StdCheats, StdUtils { - error NotAllowed(); - error InvalidOffer(string); - error InvalidBuy(string); - - address alice = makeAddr("alice"); - address bob = makeAddr("bob"); - - HypercertMinter public hypercertMinter = new HypercertMinter(); - - function getOfferCount() external view returns (uint256) { - return _offerCounter; - } - - function exposedValidateBuyOffer( - Offer memory offer, - uint256 unitAmount, - address buyToken, - uint256 tokenAmountPerUnit - ) external payable { - _validateBuyOffer(offer, unitAmount, buyToken, tokenAmountPerUnit); - } - - function createDefaultOffer(bool full) external returns (uint256 offerID, uint256 fractionID) { - vm.startPrank(alice); - hypercertMinter.mintClaim(alice, 10000, "ipfs://test", IHypercertToken.TransferRestrictions.FromCreatorOnly); - - uint256 baseID = 1 << 128; - uint256 tokenIndex = 1; - fractionID = baseID + tokenIndex; - - AcceptedToken[] memory acceptedTokens = new AcceptedToken[](1); - acceptedTokens[0] = AcceptedToken(address(0), 3); - - hypercertMinter.setApprovalForAll(address(this), true); - - vm.expectEmit(true, true, true, true); - emit OfferCreated(alice, address(hypercertMinter), fractionID, 0); - if (full) { - offerID = this.createOffer(address(hypercertMinter), fractionID, 10000, 10, 10000, acceptedTokens); - } else { - offerID = this.createOffer(address(hypercertMinter), fractionID, 1000, 10, 1000, acceptedTokens); - } - } -} - -//TODO cleanup inheritance -contract HypercertTraderBuyOfferTest is HypercertTraderHelper { - HypercertTraderHelper internal hypercertTrader; - - function setUp() public { - hypercertTrader = new HypercertTraderHelper(); - } - - function testBuyOfferFullBid() public { - // Alice has no balance - assertEq(alice.balance, 0); - - // Alice creates a offer - (uint256 offerID, uint256 fractionID) = hypercertTrader.createDefaultOffer(false); - assertEq(hypercertTrader.getOfferCount(), 1); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 1000); - - // Bob buys the full offer - startHoax(bob, 10 ether); - vm.expectEmit(true, true, true, true); - emit Trade(alice, bob, address(hypercertTrader.hypercertMinter()), fractionID, 1000, address(0), 10, offerID); - hypercertTrader.buyUnits{ value: 10000 }(bob, offerID, 1000, address(0), 10); - - // Bob owns the new fraction - assertEq(hypercertTrader.hypercertMinter().ownerOf(fractionID + 1), bob); - - // Alice still owns the old fraction and the units that weren't sold - assertEq(hypercertTrader.hypercertMinter().ownerOf(fractionID), alice); - assertEq(hypercertTrader.hypercertMinter().unitsOf(fractionID), 9000); - - // The offer is closed - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(uint256(offer.status), uint256(OfferStatus.Fulfilled)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 0); - - // And bob can't buy any more of the offer - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong status")); - hypercertTrader.buyUnits{ value: 10000 }(bob, offerID, 10, address(0), 10); - - // Alice received the funds - assertEq(alice.balance, 10000); - } - - function testBuyOfferFullFraction() public { - // Alice has no balance - assertEq(alice.balance, 0); - - // Alice creates a offer - (uint256 offerID, uint256 fractionID) = hypercertTrader.createDefaultOffer(true); - assertEq(hypercertTrader.getOfferCount(), 1); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 10000); - - // Bob buys the full offer for the full token - startHoax(bob, 10 ether); - vm.expectEmit(true, true, true, true); - emit Trade(alice, bob, address(hypercertTrader.hypercertMinter()), fractionID, 10000, address(0), 10, offerID); - hypercertTrader.buyUnits{ value: 100000 }(bob, offerID, 10000, address(0), 10); - - // Bob bought the full fraction and owns it - assertEq(hypercertTrader.hypercertMinter().ownerOf(fractionID), bob); - - // No new fraction was minted - assertEq(hypercertTrader.hypercertMinter().unitsOf(fractionID + 1), 0); - - // The offer is closed - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(uint256(offer.status), uint256(OfferStatus.Fulfilled)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 0); - - // And bob can't buy any more of the offer - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong status")); - hypercertTrader.buyUnits{ value: 10000 }(bob, offerID, 1, address(0), 10); - - // Alice received the funds - assertEq(alice.balance, 100000); - } - - function testBuyOfferPartial() public { - // Alice creates a offer - (uint256 offerID, uint256 fractionID) = hypercertTrader.createDefaultOffer(false); - assertEq(hypercertTrader.getOfferCount(), 1); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 1000); - - // Bob buys part of the offer - startHoax(bob, 10 ether); - vm.expectEmit(true, true, true, true); - emit Trade(alice, bob, address(hypercertTrader.hypercertMinter()), fractionID, 500, address(0), 10, offerID); - hypercertTrader.buyUnits{ value: 5000 }(bob, offerID, 500, address(0), 10); - - // Bob owns the new fraction worth 500 units - assertEq(hypercertTrader.hypercertMinter().ownerOf(fractionID + 1), bob); - assertEq(hypercertTrader.hypercertMinter().unitsOf(fractionID + 1), 500); - - // The offer is still open - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(uint256(offer.status), uint256(OfferStatus.Open)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 500); - - // And bob can buy the rest of the offer - vm.expectEmit(true, true, true, true); - emit Trade(alice, bob, address(hypercertTrader.hypercertMinter()), fractionID, 500, address(0), 10, offerID); - hypercertTrader.buyUnits{ value: 5000 }(bob, offerID, 500, address(0), 10); - - // Bob owns the new fraction worth 500 units - assertEq(hypercertTrader.hypercertMinter().ownerOf(fractionID + 2), bob); - assertEq(hypercertTrader.hypercertMinter().unitsOf(fractionID + 2), 500); - - // The offer is closed - offer = hypercertTrader.getOffer(offerID); - assertEq(uint256(offer.status), uint256(OfferStatus.Fulfilled)); - assertEq(hypercertTrader.totalUnitsForSale(address(hypercertTrader.hypercertMinter()), fractionID), 0); - } - - function testBuyOfferFailsLowBid() public { - // Alice creates a offer - (uint256 offerID, ) = hypercertTrader.createDefaultOffer(false); - assertEq(hypercertTrader.getOfferCount(), 1); - - // Bob tries to buy the offer with a low bid (tokenAmountPerUnit lower than min asking price) - startHoax(bob, 10 ether); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong token/value")); - hypercertTrader.buyUnits{ value: 5000 }(bob, offerID, 500, address(0), 2); - - // Bob tries to buy the offer with a low bid (msg.value too low) - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong token/value")); - hypercertTrader.buyUnits{ value: 4999 }(bob, offerID, 500, address(0), 10); - - // The offer is still open - Offer memory offer = hypercertTrader.getOffer(offerID); - assertEq(uint256(offer.status), uint256(OfferStatus.Open)); - } - - function testBuyOfferValidations() public { - Offer memory offer = Offer({ - offerer: alice, - hypercertContract: address(hypercertMinter), - fractionID: 42, - unitsAvailable: 10, - minUnitsPerTrade: 2, - maxUnitsPerTrade: 5, - status: OfferStatus.Open, - offerType: OfferType.Units, - acceptedTokens: new AcceptedToken[](1) - }); - - vm.mockCall( - address(hypercertMinter), - abi.encodeWithSelector(IHypercertMinter.ownerOf.selector, 42), - abi.encode(alice) - ); - - offer.acceptedTokens[0] = AcceptedToken(address(0), 2); - - // Expect revert on mix/maxUnitsPerTrade - // Bid too low - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong token/value")); - this.exposedValidateBuyOffer(offer, 3, address(0), 1); - - // Units bought too low - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Min/Max units")); - this.exposedValidateBuyOffer(offer, 1, address(0), 3); - - // Too high - // Units bought too high - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Min/Max units")); - this.exposedValidateBuyOffer(offer, 11, address(0), 3); - - // Within range but no value - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong token/value")); - this.exposedValidateBuyOffer(offer, 3, address(0), 3); - - this.exposedValidateBuyOffer{ value: 10 }(offer, 3, address(0), 3); - - // Expect revert on wrong token - offer.acceptedTokens[0] = AcceptedToken(address(1337), 2); - vm.expectRevert(abi.encodeWithSelector(InvalidOffer.selector, "Wrong token/value")); - this.exposedValidateBuyOffer(offer, 3, address(1), 1); - } -} diff --git a/docs/docs/developer/api/contracts/AllowlistMinter.md b/docs/docs/developer/api/contracts/AllowlistMinter.md index 4684053c..068d0fe7 100644 --- a/docs/docs/developer/api/contracts/AllowlistMinter.md +++ b/docs/docs/developer/api/contracts/AllowlistMinter.md @@ -1,11 +1,13 @@ # AllowlistMinter -_bitbeckers_ +*bitbeckers* > Interface for hypercert token interactions This interface declares the required functionality for a hypercert tokenThis interface does not specify the underlying token type (e.g. 721 or 1155) + + ## Methods ### hasBeenClaimed @@ -14,18 +16,22 @@ This interface declares the required functionality for a hypercert tokenThis int function hasBeenClaimed(uint256, bytes32) external view returns (bool) ``` + + + + #### Parameters -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | uint256 | undefined | -| \_1 | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| _1 | bytes32 | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### isAllowedToClaim @@ -33,19 +39,25 @@ function hasBeenClaimed(uint256, bytes32) external view returns (bool) function isAllowedToClaim(bytes32[] proof, uint256 claimID, bytes32 leaf) external view returns (bool isAllowed) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| proof | bytes32[] | undefined | -| claimID | uint256 | undefined | -| leaf | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| proof | bytes32[] | undefined | +| claimID | uint256 | undefined | +| leaf | bytes32 | undefined | #### Returns -| Name | Type | Description | -| --------- | ---- | ----------- | -| isAllowed | bool | undefined | +| Name | Type | Description | +|---|---|---| +| isAllowed | bool | undefined | + + ## Events @@ -55,12 +67,16 @@ function isAllowedToClaim(bytes32[] proof, uint256 claimID, bytes32 leaf) extern event AllowlistCreated(uint256 tokenID, bytes32 root) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | -| root | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | +| root | bytes32 | undefined | ### LeafClaimed @@ -68,12 +84,18 @@ event AllowlistCreated(uint256 tokenID, bytes32 root) event LeafClaimed(uint256 tokenID, bytes32 leaf) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | -| leaf | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | +| leaf | bytes32 | undefined | + + ## Errors @@ -82,3 +104,10 @@ event LeafClaimed(uint256 tokenID, bytes32 leaf) ```solidity error DoesNotExist() ``` + + + + + + + diff --git a/docs/docs/developer/api/contracts/HypercertMinter.md b/docs/docs/developer/api/contracts/HypercertMinter.md index 012e3d60..334fc46f 100644 --- a/docs/docs/developer/api/contracts/HypercertMinter.md +++ b/docs/docs/developer/api/contracts/HypercertMinter.md @@ -1,22 +1,25 @@ # HypercertMinter -_bitbeckers_ +*bitbeckers* > Contract for managing hypercert claims and whitelists Implementation of the HypercertTokenInterface using { SemiFungible1155 } as underlying token.This contract supports whitelisted minting via { AllowlistMinter }. -_Wrapper contract to expose and chain functions._ +*Wrapper contract to expose and chain functions.* ## Methods -### \_\_SemiFungible1155_init +### __SemiFungible1155_init ```solidity function __SemiFungible1155_init() external nonpayable ``` -_see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }_ + + +*see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }* + ### balanceOf @@ -24,20 +27,22 @@ _see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.so function balanceOf(address account, uint256 id) external view returns (uint256) ``` -_See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address._ + + +*See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address.* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| id | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| id | uint256 | undefined | #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | ### balanceOfBatch @@ -45,20 +50,22 @@ _See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address. function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[]) ``` -_See {IERC1155-balanceOfBatch}. Requirements: - `accounts` and `ids` must have the same length._ + + +*See {IERC1155-balanceOfBatch}. Requirements: - `accounts` and `ids` must have the same length.* #### Parameters -| Name | Type | Description | -| -------- | --------- | ----------- | -| accounts | address[] | undefined | -| ids | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| accounts | address[] | undefined | +| ids | uint256[] | undefined | #### Returns -| Name | Type | Description | -| ---- | --------- | ----------- | -| \_0 | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256[] | undefined | ### batchBurnFraction @@ -68,14 +75,14 @@ function batchBurnFraction(address _account, uint256[] _tokenIDs) external nonpa Burn a claimtoken -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ---------- | --------- | ----------- | -| \_account | address | undefined | -| \_tokenIDs | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| _account | address | undefined | +| _tokenIDs | uint256[] | undefined | ### batchMintClaimsFromAllowlists @@ -85,16 +92,16 @@ function batchMintClaimsFromAllowlists(address account, bytes32[][] proofs, uint Mint semi-fungible tokens representing a fraction of the claims in `claimIDs` -_Calls AllowlistMinter to verify `proofs`.Mints the `amount` of units for the hypercert stored under `claimIDs`_ +*Calls AllowlistMinter to verify `proofs`.Mints the `amount` of units for the hypercert stored under `claimIDs`* #### Parameters -| Name | Type | Description | -| -------- | ----------- | ----------- | -| account | address | undefined | -| proofs | bytes32[][] | undefined | -| claimIDs | uint256[] | undefined | -| units | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| proofs | bytes32[][] | undefined | +| claimIDs | uint256[] | undefined | +| units | uint256[] | undefined | ### burn @@ -104,15 +111,15 @@ function burn(address account, uint256 id, uint256 value) external nonpayable Burn a claimtoken; override is needed to update units/values -_see {ERC1155Burnable}_ +*see {ERC1155Burnable}* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| id | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| id | uint256 | undefined | +| value | uint256 | undefined | ### burnBatch @@ -122,15 +129,15 @@ function burnBatch(address account, uint256[] ids, uint256[] values) external no Batch burn claimtokens; override is needed to update units/values -_see {ERC1155Burnable}_ +*see {ERC1155Burnable}* #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| account | address | undefined | -| ids | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| ids | uint256[] | undefined | +| values | uint256[] | undefined | ### burnFraction @@ -140,14 +147,14 @@ function burnFraction(address _account, uint256 _tokenID) external nonpayable Burn a claimtoken -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| --------- | ------- | ----------- | -| \_account | address | undefined | -| \_tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| _account | address | undefined | +| _tokenID | uint256 | undefined | ### createAllowlist @@ -157,17 +164,17 @@ function createAllowlist(address account, uint256 units, bytes32 merkleRoot, str Register a claim and the whitelist for minting token(s) belonging to that claim -_Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `units`Calls AllowlistMinter to store the `merkleRoot` as proof to authorize claims_ +*Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `units`Calls AllowlistMinter to store the `merkleRoot` as proof to authorize claims* #### Parameters -| Name | Type | Description | -| ------------ | ----------------------------------------- | ----------- | -| account | address | undefined | -| units | uint256 | undefined | -| merkleRoot | bytes32 | undefined | -| \_uri | string | undefined | -| restrictions | enum IHypercertToken.TransferRestrictions | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| units | uint256 | undefined | +| merkleRoot | bytes32 | undefined | +| _uri | string | undefined | +| restrictions | enum IHypercertToken.TransferRestrictions | undefined | ### hasBeenClaimed @@ -175,18 +182,22 @@ _Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `u function hasBeenClaimed(uint256, bytes32) external view returns (bool) ``` + + + + #### Parameters -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | uint256 | undefined | -| \_1 | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| _1 | bytes32 | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### initialize @@ -194,7 +205,10 @@ function hasBeenClaimed(uint256, bytes32) external view returns (bool) function initialize() external nonpayable ``` -_see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }_ + + +*see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }* + ### isAllowedToClaim @@ -202,19 +216,23 @@ _see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.so function isAllowedToClaim(bytes32[] proof, uint256 claimID, bytes32 leaf) external view returns (bool isAllowed) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| proof | bytes32[] | undefined | -| claimID | uint256 | undefined | -| leaf | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| proof | bytes32[] | undefined | +| claimID | uint256 | undefined | +| leaf | bytes32 | undefined | #### Returns -| Name | Type | Description | -| --------- | ---- | ----------- | -| isAllowed | bool | undefined | +| Name | Type | Description | +|---|---|---| +| isAllowed | bool | undefined | ### isApprovedForAll @@ -222,20 +240,22 @@ function isAllowedToClaim(bytes32[] proof, uint256 claimID, bytes32 leaf) extern function isApprovedForAll(address account, address operator) external view returns (bool) ``` -_See {IERC1155-isApprovedForAll}._ + + +*See {IERC1155-isApprovedForAll}.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| account | address | undefined | -| operator | address | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| operator | address | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### mergeFractions @@ -245,14 +265,14 @@ function mergeFractions(address _account, uint256[] _fractionIDs) external nonpa Merge the value of tokens belonging to the same claim -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ------------- | --------- | ----------- | -| \_account | address | undefined | -| \_fractionIDs | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| _account | address | undefined | +| _fractionIDs | uint256[] | undefined | ### mintClaim @@ -262,16 +282,16 @@ function mintClaim(address account, uint256 units, string _uri, enum IHypercertT Mint a semi-fungible token for the impact claim referenced via `uri` -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ------------ | ----------------------------------------- | ----------- | -| account | address | undefined | -| units | uint256 | undefined | -| \_uri | string | undefined | -| restrictions | enum IHypercertToken.TransferRestrictions | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| units | uint256 | undefined | +| _uri | string | undefined | +| restrictions | enum IHypercertToken.TransferRestrictions | undefined | ### mintClaimFromAllowlist @@ -281,16 +301,16 @@ function mintClaimFromAllowlist(address account, bytes32[] proof, uint256 claimI Mint a semi-fungible token representing a fraction of the claim -_Calls AllowlistMinter to verify `proof`.Mints the `amount` of units for the hypercert stored under `claimID`_ +*Calls AllowlistMinter to verify `proof`.Mints the `amount` of units for the hypercert stored under `claimID`* #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| account | address | undefined | -| proof | bytes32[] | undefined | -| claimID | uint256 | undefined | -| units | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| proof | bytes32[] | undefined | +| claimID | uint256 | undefined | +| units | uint256 | undefined | ### mintClaimWithFractions @@ -300,17 +320,17 @@ function mintClaimWithFractions(address account, uint256 units, uint256[] fracti Mint semi-fungible tokens for the impact claim referenced via `uri` -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ------------ | ----------------------------------------- | ----------- | -| account | address | undefined | -| units | uint256 | undefined | -| fractions | uint256[] | undefined | -| \_uri | string | undefined | -| restrictions | enum IHypercertToken.TransferRestrictions | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| units | uint256 | undefined | +| fractions | uint256[] | undefined | +| _uri | string | undefined | +| restrictions | enum IHypercertToken.TransferRestrictions | undefined | ### name @@ -318,11 +338,16 @@ _see {IHypercertToken}_ function name() external view returns (string) ``` + + + + + #### Returns -| Name | Type | Description | -| ---- | ------ | ----------- | -| \_0 | string | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | string | undefined | ### owner @@ -330,13 +355,16 @@ function name() external view returns (string) function owner() external view returns (address) ``` -_Returns the address of the current owner._ + + +*Returns the address of the current owner.* + #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | address | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | ### ownerOf @@ -344,19 +372,21 @@ _Returns the address of the current owner._ function ownerOf(uint256 tokenID) external view returns (address _owner) ``` -_Returns the owner of a given token ID._ + + +*Returns the owner of a given token ID.* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------------------------- | +| Name | Type | Description | +|---|---|---| | tokenID | uint256 | The ID of the token to query. | #### Returns -| Name | Type | Description | -| ------- | ------- | -------------------------------------- | -| \_owner | address | The address of the owner of the token. | +| Name | Type | Description | +|---|---|---| +| _owner | address | The address of the owner of the token. | ### pause @@ -366,19 +396,25 @@ function pause() external nonpayable PAUSABLE + + + ### paused ```solidity function paused() external view returns (bool) ``` -_Returns true if the contract is paused, and false otherwise._ + + +*Returns true if the contract is paused, and false otherwise.* + #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### proxiableUUID @@ -386,13 +422,16 @@ _Returns true if the contract is paused, and false otherwise._ function proxiableUUID() external view returns (bytes32) ``` -_Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier._ + + +*Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.* + #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | ### readTransferRestriction @@ -402,17 +441,19 @@ function readTransferRestriction(uint256 tokenID) external view returns (string) TRANSFER RESTRICTIONS + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ---- | ------ | ----------- | -| \_0 | string | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | string | undefined | ### renounceOwnership @@ -420,7 +461,10 @@ TRANSFER RESTRICTIONS function renounceOwnership() external nonpayable ``` -_Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner._ + + +*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.* + ### safeBatchTransferFrom @@ -428,17 +472,19 @@ _Leaves the contract without owner. It will not be possible to call `onlyOwner` function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) external nonpayable ``` -_See {IERC1155-safeBatchTransferFrom}._ + + +*See {IERC1155-safeBatchTransferFrom}.* #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| from | address | undefined | -| to | address | undefined | -| ids | uint256[] | undefined | -| amounts | uint256[] | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| from | address | undefined | +| to | address | undefined | +| ids | uint256[] | undefined | +| amounts | uint256[] | undefined | +| data | bytes | undefined | ### safeTransferFrom @@ -446,17 +492,19 @@ _See {IERC1155-safeBatchTransferFrom}._ function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) external nonpayable ``` -_See {IERC1155-safeTransferFrom}._ + + +*See {IERC1155-safeTransferFrom}.* #### Parameters -| Name | Type | Description | -| ------ | ------- | ----------- | -| from | address | undefined | -| to | address | undefined | -| id | uint256 | undefined | -| amount | uint256 | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| from | address | undefined | +| to | address | undefined | +| id | uint256 | undefined | +| amount | uint256 | undefined | +| data | bytes | undefined | ### setApprovalForAll @@ -464,14 +512,16 @@ _See {IERC1155-safeTransferFrom}._ function setApprovalForAll(address operator, bool approved) external nonpayable ``` -_See {IERC1155-setApprovalForAll}._ + + +*See {IERC1155-setApprovalForAll}.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| operator | address | undefined | -| approved | bool | undefined | +| Name | Type | Description | +|---|---|---| +| operator | address | undefined | +| approved | bool | undefined | ### splitFraction @@ -481,15 +531,15 @@ function splitFraction(address _account, uint256 _tokenID, uint256[] _newFractio Split a claimtokens value into parts with summed value equal to the original -_see {IHypercertToken}_ +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| -------------- | --------- | ----------- | -| \_account | address | undefined | -| \_tokenID | uint256 | undefined | -| \_newFractions | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| _account | address | undefined | +| _tokenID | uint256 | undefined | +| _newFractions | uint256[] | undefined | ### supportsInterface @@ -497,19 +547,21 @@ _see {IHypercertToken}_ function supportsInterface(bytes4 interfaceId) external view returns (bool) ``` -_See {IERC165-supportsInterface}._ + + +*See {IERC165-supportsInterface}.* #### Parameters -| Name | Type | Description | -| ----------- | ------ | ----------- | -| interfaceId | bytes4 | undefined | +| Name | Type | Description | +|---|---|---| +| interfaceId | bytes4 | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### transferOwnership @@ -517,13 +569,15 @@ _See {IERC165-supportsInterface}._ function transferOwnership(address newOwner) external nonpayable ``` -_Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner._ + + +*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| newOwner | address | undefined | +| Name | Type | Description | +|---|---|---| +| newOwner | address | undefined | ### unitsOf @@ -531,20 +585,22 @@ _Transfers ownership of the contract to a new account (`newOwner`). Can only be function unitsOf(address account, uint256 tokenID) external view returns (uint256 units) ``` -_see {IHypercertToken}_ + + +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ----- | ------- | ----------- | -| units | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| units | uint256 | undefined | ### unitsOf @@ -552,19 +608,21 @@ _see {IHypercertToken}_ function unitsOf(uint256 tokenID) external view returns (uint256 units) ``` -_see {IHypercertToken}_ + + +*see {IHypercertToken}* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ----- | ------- | ----------- | -| units | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| units | uint256 | undefined | ### unpause @@ -572,19 +630,26 @@ _see {IHypercertToken}_ function unpause() external nonpayable ``` + + + + + ### upgradeTo ```solidity function upgradeTo(address newImplementation) external nonpayable ``` -_Upgrade the implementation of the proxy to `newImplementation`. Calls {\_authorizeUpgrade}. Emits an {Upgraded} event._ + + +*Upgrade the implementation of the proxy to `newImplementation`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.* #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| newImplementation | address | undefined | +| Name | Type | Description | +|---|---|---| +| newImplementation | address | undefined | ### upgradeToAndCall @@ -592,14 +657,16 @@ _Upgrade the implementation of the proxy to `newImplementation`. Calls {\_author function upgradeToAndCall(address newImplementation, bytes data) external payable ``` -_Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {\_authorizeUpgrade}. Emits an {Upgraded} event._ + + +*Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.* #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| newImplementation | address | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| newImplementation | address | undefined | +| data | bytes | undefined | ### uri @@ -607,19 +674,23 @@ _Upgrade the implementation of the proxy to `newImplementation`, and subsequentl function uri(uint256 tokenID) external view returns (string _uri) ``` -_see { IHypercertMetadata}_ + + +*see { IHypercertMetadata}* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ----- | ------ | ----------- | -| \_uri | string | undefined | +| Name | Type | Description | +|---|---|---| +| _uri | string | undefined | + + ## Events @@ -629,12 +700,16 @@ _see { IHypercertMetadata}_ event AdminChanged(address previousAdmin, address newAdmin) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------- | ------- | ----------- | -| previousAdmin | address | undefined | -| newAdmin | address | undefined | +| Name | Type | Description | +|---|---|---| +| previousAdmin | address | undefined | +| newAdmin | address | undefined | ### AllowlistCreated @@ -642,12 +717,16 @@ event AdminChanged(address previousAdmin, address newAdmin) event AllowlistCreated(uint256 tokenID, bytes32 root) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | -| root | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | +| root | bytes32 | undefined | ### ApprovalForAll @@ -655,13 +734,17 @@ event AllowlistCreated(uint256 tokenID, bytes32 root) event ApprovalForAll(address indexed account, address indexed operator, bool approved) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | ------- | ----------- | -| account `indexed` | address | undefined | -| operator `indexed` | address | undefined | -| approved | bool | undefined | +| Name | Type | Description | +|---|---|---| +| account `indexed` | address | undefined | +| operator `indexed` | address | undefined | +| approved | bool | undefined | ### BatchValueTransfer @@ -669,14 +752,18 @@ event ApprovalForAll(address indexed account, address indexed operator, bool app event BatchValueTransfer(uint256[] claimIDs, uint256[] fromTokenIDs, uint256[] toTokenIDs, uint256[] values) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------ | --------- | ----------- | -| claimIDs | uint256[] | undefined | -| fromTokenIDs | uint256[] | undefined | -| toTokenIDs | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| claimIDs | uint256[] | undefined | +| fromTokenIDs | uint256[] | undefined | +| toTokenIDs | uint256[] | undefined | +| values | uint256[] | undefined | ### BeaconUpgraded @@ -684,11 +771,15 @@ event BatchValueTransfer(uint256[] claimIDs, uint256[] fromTokenIDs, uint256[] t event BeaconUpgraded(address indexed beacon) ``` + + + + #### Parameters -| Name | Type | Description | -| ---------------- | ------- | ----------- | -| beacon `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| beacon `indexed` | address | undefined | ### ClaimStored @@ -696,13 +787,17 @@ event BeaconUpgraded(address indexed beacon) event ClaimStored(uint256 indexed claimID, string uri, uint256 totalUnits) ``` + + + + #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| claimID `indexed` | uint256 | undefined | -| uri | string | undefined | -| totalUnits | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| claimID `indexed` | uint256 | undefined | +| uri | string | undefined | +| totalUnits | uint256 | undefined | ### Initialized @@ -710,11 +805,15 @@ event ClaimStored(uint256 indexed claimID, string uri, uint256 totalUnits) event Initialized(uint8 version) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ----- | ----------- | -| version | uint8 | undefined | +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | ### LeafClaimed @@ -722,12 +821,16 @@ event Initialized(uint8 version) event LeafClaimed(uint256 tokenID, bytes32 leaf) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | -| leaf | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | +| leaf | bytes32 | undefined | ### OwnershipTransferred @@ -735,12 +838,16 @@ event LeafClaimed(uint256 tokenID, bytes32 leaf) event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) ``` + + + + #### Parameters -| Name | Type | Description | -| ----------------------- | ------- | ----------- | -| previousOwner `indexed` | address | undefined | -| newOwner `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| previousOwner `indexed` | address | undefined | +| newOwner `indexed` | address | undefined | ### Paused @@ -748,11 +855,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event Paused(address account) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | ### TransferBatch @@ -760,15 +871,19 @@ event Paused(address account) event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | --------- | ----------- | -| operator `indexed` | address | undefined | -| from `indexed` | address | undefined | -| to `indexed` | address | undefined | -| ids | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| operator `indexed` | address | undefined | +| from `indexed` | address | undefined | +| to `indexed` | address | undefined | +| ids | uint256[] | undefined | +| values | uint256[] | undefined | ### TransferSingle @@ -776,15 +891,19 @@ event TransferBatch(address indexed operator, address indexed from, address inde event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | ------- | ----------- | -| operator `indexed` | address | undefined | -| from `indexed` | address | undefined | -| to `indexed` | address | undefined | -| id | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| operator `indexed` | address | undefined | +| from `indexed` | address | undefined | +| to `indexed` | address | undefined | +| id | uint256 | undefined | +| value | uint256 | undefined | ### URI @@ -792,12 +911,16 @@ event TransferSingle(address indexed operator, address indexed from, address ind event URI(string value, uint256 indexed id) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------ | ------- | ----------- | -| value | string | undefined | -| id `indexed` | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| value | string | undefined | +| id `indexed` | uint256 | undefined | ### Unpaused @@ -805,11 +928,15 @@ event URI(string value, uint256 indexed id) event Unpaused(address account) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | ### Upgraded @@ -817,11 +944,15 @@ event Unpaused(address account) event Upgraded(address indexed implementation) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------------ | ------- | ----------- | -| implementation `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| implementation `indexed` | address | undefined | ### ValueTransfer @@ -829,14 +960,20 @@ event Upgraded(address indexed implementation) event ValueTransfer(uint256 claimID, uint256 fromTokenID, uint256 toTokenID, uint256 value) ``` + + + + #### Parameters -| Name | Type | Description | -| ----------- | ------- | ----------- | -| claimID | uint256 | undefined | -| fromTokenID | uint256 | undefined | -| toTokenID | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| claimID | uint256 | undefined | +| fromTokenID | uint256 | undefined | +| toTokenID | uint256 | undefined | +| value | uint256 | undefined | + + ## Errors @@ -846,50 +983,97 @@ event ValueTransfer(uint256 claimID, uint256 fromTokenID, uint256 toTokenID, uin error AlreadyClaimed() ``` + + + + + ### ArraySize ```solidity error ArraySize() ``` + + + + + ### DoesNotExist ```solidity error DoesNotExist() ``` + + + + + ### DuplicateEntry ```solidity error DuplicateEntry() ``` + + + + + ### Invalid ```solidity error Invalid() ``` + + + + + ### NotAllowed ```solidity error NotAllowed() ``` + + + + + ### NotApprovedOrOwner ```solidity error NotApprovedOrOwner() ``` + + + + + ### TransfersNotAllowed ```solidity error TransfersNotAllowed() ``` + + + + + ### TypeMismatch ```solidity error TypeMismatch() ``` + + + + + + + diff --git a/docs/docs/developer/api/contracts/SemiFungible1155.md b/docs/docs/developer/api/contracts/SemiFungible1155.md index a4086e37..74ef6554 100644 --- a/docs/docs/developer/api/contracts/SemiFungible1155.md +++ b/docs/docs/developer/api/contracts/SemiFungible1155.md @@ -1,22 +1,25 @@ # SemiFungible1155 -_bitbeckers_ +*bitbeckers* > Contract for minting semi-fungible EIP1155 tokens Extends { Upgradeable1155 } token with semi-fungible properties and the concept of `units` -_Adds split bit strategy as described in [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155#non-fungible-tokens)_ +*Adds split bit strategy as described in [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155#non-fungible-tokens)* ## Methods -### \_\_SemiFungible1155_init +### __SemiFungible1155_init ```solidity function __SemiFungible1155_init() external nonpayable ``` -_see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }_ + + +*see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }* + ### balanceOf @@ -24,20 +27,22 @@ _see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.so function balanceOf(address account, uint256 id) external view returns (uint256) ``` -_See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address._ + + +*See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address.* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| id | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| id | uint256 | undefined | #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | ### balanceOfBatch @@ -45,20 +50,22 @@ _See {IERC1155-balanceOf}. Requirements: - `account` cannot be the zero address. function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[]) ``` -_See {IERC1155-balanceOfBatch}. Requirements: - `accounts` and `ids` must have the same length._ + + +*See {IERC1155-balanceOfBatch}. Requirements: - `accounts` and `ids` must have the same length.* #### Parameters -| Name | Type | Description | -| -------- | --------- | ----------- | -| accounts | address[] | undefined | -| ids | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| accounts | address[] | undefined | +| ids | uint256[] | undefined | #### Returns -| Name | Type | Description | -| ---- | --------- | ----------- | -| \_0 | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | uint256[] | undefined | ### burn @@ -66,13 +73,17 @@ _See {IERC1155-balanceOfBatch}. Requirements: - `accounts` and `ids` must have t function burn(address account, uint256 id, uint256 value) external nonpayable ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| id | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| id | uint256 | undefined | +| value | uint256 | undefined | ### burnBatch @@ -80,13 +91,17 @@ function burn(address account, uint256 id, uint256 value) external nonpayable function burnBatch(address account, uint256[] ids, uint256[] values) external nonpayable ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| account | address | undefined | -| ids | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| ids | uint256[] | undefined | +| values | uint256[] | undefined | ### isApprovedForAll @@ -94,20 +109,22 @@ function burnBatch(address account, uint256[] ids, uint256[] values) external no function isApprovedForAll(address account, address operator) external view returns (bool) ``` -_See {IERC1155-isApprovedForAll}._ + + +*See {IERC1155-isApprovedForAll}.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| account | address | undefined | -| operator | address | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| operator | address | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### owner @@ -115,13 +132,16 @@ _See {IERC1155-isApprovedForAll}._ function owner() external view returns (address) ``` -_Returns the address of the current owner._ + + +*Returns the address of the current owner.* + #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | address | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | ### ownerOf @@ -129,19 +149,21 @@ _Returns the address of the current owner._ function ownerOf(uint256 tokenID) external view returns (address _owner) ``` -_Returns the owner of a given token ID._ + + +*Returns the owner of a given token ID.* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------------------------- | +| Name | Type | Description | +|---|---|---| | tokenID | uint256 | The ID of the token to query. | #### Returns -| Name | Type | Description | -| ------- | ------- | -------------------------------------- | -| \_owner | address | The address of the owner of the token. | +| Name | Type | Description | +|---|---|---| +| _owner | address | The address of the owner of the token. | ### proxiableUUID @@ -149,13 +171,16 @@ _Returns the owner of a given token ID._ function proxiableUUID() external view returns (bytes32) ``` -_Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier._ + + +*Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.* + #### Returns -| Name | Type | Description | -| ---- | ------- | ----------- | -| \_0 | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | ### renounceOwnership @@ -163,7 +188,10 @@ _Implementation of the ERC1822 {proxiableUUID} function. This returns the storag function renounceOwnership() external nonpayable ``` -_Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner._ + + +*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.* + ### safeBatchTransferFrom @@ -171,17 +199,19 @@ _Leaves the contract without owner. It will not be possible to call `onlyOwner` function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) external nonpayable ``` -_See {IERC1155-safeBatchTransferFrom}._ + + +*See {IERC1155-safeBatchTransferFrom}.* #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| from | address | undefined | -| to | address | undefined | -| ids | uint256[] | undefined | -| amounts | uint256[] | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| from | address | undefined | +| to | address | undefined | +| ids | uint256[] | undefined | +| amounts | uint256[] | undefined | +| data | bytes | undefined | ### safeTransferFrom @@ -189,17 +219,19 @@ _See {IERC1155-safeBatchTransferFrom}._ function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) external nonpayable ``` -_See {IERC1155-safeTransferFrom}._ + + +*See {IERC1155-safeTransferFrom}.* #### Parameters -| Name | Type | Description | -| ------ | ------- | ----------- | -| from | address | undefined | -| to | address | undefined | -| id | uint256 | undefined | -| amount | uint256 | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| from | address | undefined | +| to | address | undefined | +| id | uint256 | undefined | +| amount | uint256 | undefined | +| data | bytes | undefined | ### setApprovalForAll @@ -207,14 +239,16 @@ _See {IERC1155-safeTransferFrom}._ function setApprovalForAll(address operator, bool approved) external nonpayable ``` -_See {IERC1155-setApprovalForAll}._ + + +*See {IERC1155-setApprovalForAll}.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| operator | address | undefined | -| approved | bool | undefined | +| Name | Type | Description | +|---|---|---| +| operator | address | undefined | +| approved | bool | undefined | ### supportsInterface @@ -222,19 +256,21 @@ _See {IERC1155-setApprovalForAll}._ function supportsInterface(bytes4 interfaceId) external view returns (bool) ``` -_See {IERC165-supportsInterface}._ + + +*See {IERC165-supportsInterface}.* #### Parameters -| Name | Type | Description | -| ----------- | ------ | ----------- | -| interfaceId | bytes4 | undefined | +| Name | Type | Description | +|---|---|---| +| interfaceId | bytes4 | undefined | #### Returns | Name | Type | Description | -| ---- | ---- | ----------- | -| \_0 | bool | undefined | +|---|---|---| +| _0 | bool | undefined | ### transferOwnership @@ -242,13 +278,15 @@ _See {IERC165-supportsInterface}._ function transferOwnership(address newOwner) external nonpayable ``` -_Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner._ + + +*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.* #### Parameters -| Name | Type | Description | -| -------- | ------- | ----------- | -| newOwner | address | undefined | +| Name | Type | Description | +|---|---|---| +| newOwner | address | undefined | ### upgradeTo @@ -256,13 +294,15 @@ _Transfers ownership of the contract to a new account (`newOwner`). Can only be function upgradeTo(address newImplementation) external nonpayable ``` -_Upgrade the implementation of the proxy to `newImplementation`. Calls {\_authorizeUpgrade}. Emits an {Upgraded} event._ + + +*Upgrade the implementation of the proxy to `newImplementation`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.* #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| newImplementation | address | undefined | +| Name | Type | Description | +|---|---|---| +| newImplementation | address | undefined | ### upgradeToAndCall @@ -270,14 +310,16 @@ _Upgrade the implementation of the proxy to `newImplementation`. Calls {\_author function upgradeToAndCall(address newImplementation, bytes data) external payable ``` -_Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {\_authorizeUpgrade}. Emits an {Upgraded} event._ + + +*Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.* #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| newImplementation | address | undefined | -| data | bytes | undefined | +| Name | Type | Description | +|---|---|---| +| newImplementation | address | undefined | +| data | bytes | undefined | ### uri @@ -285,19 +327,23 @@ _Upgrade the implementation of the proxy to `newImplementation`, and subsequentl function uri(uint256 tokenID) external view returns (string _uri) ``` -_Returns the metadata URI for a given token ID.This function retrieves the metadata URI for the specified token ID by calling the `uri` function of the `ERC1155URIStorageUpgradeable` contract.The metadata URI is a string that points to a JSON file containing information about the token, such as its name, symbol, and image.This function always returns the URI for the basetype so that it's managed in one place._ + + +*Returns the metadata URI for a given token ID.This function retrieves the metadata URI for the specified token ID by calling the `uri` function of the `ERC1155URIStorageUpgradeable` contract.The metadata URI is a string that points to a JSON file containing information about the token, such as its name, symbol, and image.This function always returns the URI for the basetype so that it's managed in one place.* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------------------------------------------------- | +| Name | Type | Description | +|---|---|---| | tokenID | uint256 | The ID of the token to retrieve the metadata URI for. | #### Returns -| Name | Type | Description | -| ----- | ------ | -------------------------------------------- | -| \_uri | string | The metadata URI for the specified token ID. | +| Name | Type | Description | +|---|---|---| +| _uri | string | The metadata URI for the specified token ID. | + + ## Events @@ -307,12 +353,16 @@ _Returns the metadata URI for a given token ID.This function retrieves the metad event AdminChanged(address previousAdmin, address newAdmin) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------- | ------- | ----------- | -| previousAdmin | address | undefined | -| newAdmin | address | undefined | +| Name | Type | Description | +|---|---|---| +| previousAdmin | address | undefined | +| newAdmin | address | undefined | ### ApprovalForAll @@ -320,13 +370,17 @@ event AdminChanged(address previousAdmin, address newAdmin) event ApprovalForAll(address indexed account, address indexed operator, bool approved) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | ------- | ----------- | -| account `indexed` | address | undefined | -| operator `indexed` | address | undefined | -| approved | bool | undefined | +| Name | Type | Description | +|---|---|---| +| account `indexed` | address | undefined | +| operator `indexed` | address | undefined | +| approved | bool | undefined | ### BatchValueTransfer @@ -334,16 +388,18 @@ event ApprovalForAll(address indexed account, address indexed operator, bool app event BatchValueTransfer(uint256[] claimIDs, uint256[] fromTokenIDs, uint256[] toTokenIDs, uint256[] values) ``` -_Emitted on transfer of `values` between `fromTokenIDs` to `toTokenIDs` of `claimIDs`_ + + +*Emitted on transfer of `values` between `fromTokenIDs` to `toTokenIDs` of `claimIDs`* #### Parameters -| Name | Type | Description | -| ------------ | --------- | ----------- | -| claimIDs | uint256[] | undefined | -| fromTokenIDs | uint256[] | undefined | -| toTokenIDs | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| claimIDs | uint256[] | undefined | +| fromTokenIDs | uint256[] | undefined | +| toTokenIDs | uint256[] | undefined | +| values | uint256[] | undefined | ### BeaconUpgraded @@ -351,11 +407,15 @@ _Emitted on transfer of `values` between `fromTokenIDs` to `toTokenIDs` of `clai event BeaconUpgraded(address indexed beacon) ``` + + + + #### Parameters -| Name | Type | Description | -| ---------------- | ------- | ----------- | -| beacon `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| beacon `indexed` | address | undefined | ### Initialized @@ -363,11 +423,15 @@ event BeaconUpgraded(address indexed beacon) event Initialized(uint8 version) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | ----- | ----------- | -| version | uint8 | undefined | +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | ### OwnershipTransferred @@ -375,12 +439,16 @@ event Initialized(uint8 version) event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) ``` + + + + #### Parameters -| Name | Type | Description | -| ----------------------- | ------- | ----------- | -| previousOwner `indexed` | address | undefined | -| newOwner `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| previousOwner `indexed` | address | undefined | +| newOwner `indexed` | address | undefined | ### TransferBatch @@ -388,15 +456,19 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | --------- | ----------- | -| operator `indexed` | address | undefined | -| from `indexed` | address | undefined | -| to `indexed` | address | undefined | -| ids | uint256[] | undefined | -| values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| operator `indexed` | address | undefined | +| from `indexed` | address | undefined | +| to `indexed` | address | undefined | +| ids | uint256[] | undefined | +| values | uint256[] | undefined | ### TransferSingle @@ -404,15 +476,19 @@ event TransferBatch(address indexed operator, address indexed from, address inde event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------ | ------- | ----------- | -| operator `indexed` | address | undefined | -| from `indexed` | address | undefined | -| to `indexed` | address | undefined | -| id | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| operator `indexed` | address | undefined | +| from `indexed` | address | undefined | +| to `indexed` | address | undefined | +| id | uint256 | undefined | +| value | uint256 | undefined | ### URI @@ -420,12 +496,16 @@ event TransferSingle(address indexed operator, address indexed from, address ind event URI(string value, uint256 indexed id) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------ | ------- | ----------- | -| value | string | undefined | -| id `indexed` | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| value | string | undefined | +| id `indexed` | uint256 | undefined | ### Upgraded @@ -433,11 +513,15 @@ event URI(string value, uint256 indexed id) event Upgraded(address indexed implementation) ``` + + + + #### Parameters -| Name | Type | Description | -| ------------------------ | ------- | ----------- | -| implementation `indexed` | address | undefined | +| Name | Type | Description | +|---|---|---| +| implementation `indexed` | address | undefined | ### ValueTransfer @@ -445,13 +529,18 @@ event Upgraded(address indexed implementation) event ValueTransfer(uint256 claimID, uint256 fromTokenID, uint256 toTokenID, uint256 value) ``` -_Emitted on transfer of `value` between `fromTokenID` to `toTokenID` of the same `claimID`_ + + +*Emitted on transfer of `value` between `fromTokenID` to `toTokenID` of the same `claimID`* #### Parameters -| Name | Type | Description | -| ----------- | ------- | ----------- | -| claimID | uint256 | undefined | -| fromTokenID | uint256 | undefined | -| toTokenID | uint256 | undefined | -| value | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| claimID | uint256 | undefined | +| fromTokenID | uint256 | undefined | +| toTokenID | uint256 | undefined | +| value | uint256 | undefined | + + + diff --git a/docs/docs/developer/api/contracts/interfaces/IAllowlist.md b/docs/docs/developer/api/contracts/interfaces/IAllowlist.md index 72455b43..cbdf10e1 100644 --- a/docs/docs/developer/api/contracts/interfaces/IAllowlist.md +++ b/docs/docs/developer/api/contracts/interfaces/IAllowlist.md @@ -1,11 +1,13 @@ # IAllowlist -_bitbeckers_ +*bitbeckers* > Interface for allowlist This interface declares the required functionality for a hypercert tokenThis interface does not specify the underlying token type (e.g. 721 or 1155) + + ## Methods ### isAllowedToClaim @@ -14,16 +16,24 @@ This interface declares the required functionality for a hypercert tokenThis int function isAllowedToClaim(bytes32[] proof, uint256 tokenID, bytes32 leaf) external view returns (bool isAllowed) ``` + + + + #### Parameters -| Name | Type | Description | -| ------- | --------- | ----------- | -| proof | bytes32[] | undefined | -| tokenID | uint256 | undefined | -| leaf | bytes32 | undefined | +| Name | Type | Description | +|---|---|---| +| proof | bytes32[] | undefined | +| tokenID | uint256 | undefined | +| leaf | bytes32 | undefined | #### Returns -| Name | Type | Description | -| --------- | ---- | ----------- | -| isAllowed | bool | undefined | +| Name | Type | Description | +|---|---|---| +| isAllowed | bool | undefined | + + + + diff --git a/docs/docs/developer/api/contracts/interfaces/IHypercertToken.md b/docs/docs/developer/api/contracts/interfaces/IHypercertToken.md index 30ed6f95..3c2ecba7 100644 --- a/docs/docs/developer/api/contracts/interfaces/IHypercertToken.md +++ b/docs/docs/developer/api/contracts/interfaces/IHypercertToken.md @@ -1,11 +1,13 @@ # IHypercertToken -_bitbeckers_ +*bitbeckers* > Interface for hypercert token interactions This interface declares the required functionality for a hypercert tokenThis interface does not specify the underlying token type (e.g. 721 or 1155) + + ## Methods ### batchBurnFraction @@ -16,14 +18,14 @@ function batchBurnFraction(address account, uint256[] tokenIDs) external nonpaya Operator must be allowed by `creator` and the tokens must represent the total amount of available units. -_Function to burn the tokens at `tokenIDs` for `account`_ +*Function to burn the tokens at `tokenIDs` for `account`* #### Parameters -| Name | Type | Description | -| -------- | --------- | ----------- | -| account | address | undefined | -| tokenIDs | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenIDs | uint256[] | undefined | ### burnFraction @@ -33,14 +35,14 @@ function burnFraction(address account, uint256 tokenID) external nonpayable Operator must be allowed by `creator` and the token must represent the total amount of available units. -_Function to burn the token at `tokenID` for `account`_ +*Function to burn the token at `tokenID` for `account`* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenID | uint256 | undefined | ### mergeFractions @@ -50,14 +52,14 @@ function mergeFractions(address account, uint256[] tokenIDs) external nonpayable Tokens that have been merged are burned. -_Function called to merge tokens within `tokenIDs`._ +*Function called to merge tokens within `tokenIDs`.* #### Parameters -| Name | Type | Description | -| -------- | --------- | ----------- | -| account | address | undefined | -| tokenIDs | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenIDs | uint256[] | undefined | ### mintClaim @@ -65,16 +67,18 @@ _Function called to merge tokens within `tokenIDs`._ function mintClaim(address account, uint256 units, string uri, enum IHypercertToken.TransferRestrictions restrictions) external nonpayable ``` -_Function called to store a claim referenced via `uri` with a maximum number of fractions `units`._ + + +*Function called to store a claim referenced via `uri` with a maximum number of fractions `units`.* #### Parameters -| Name | Type | Description | -| ------------ | ----------------------------------------- | ----------- | -| account | address | undefined | -| units | uint256 | undefined | -| uri | string | undefined | -| restrictions | enum IHypercertToken.TransferRestrictions | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| units | uint256 | undefined | +| uri | string | undefined | +| restrictions | enum IHypercertToken.TransferRestrictions | undefined | ### mintClaimWithFractions @@ -82,17 +86,19 @@ _Function called to store a claim referenced via `uri` with a maximum number of function mintClaimWithFractions(address account, uint256 units, uint256[] fractions, string uri, enum IHypercertToken.TransferRestrictions restrictions) external nonpayable ``` -_Function called to store a claim referenced via `uri` with a set of `fractions`.Fractions are internally summed to total units._ + + +*Function called to store a claim referenced via `uri` with a set of `fractions`.Fractions are internally summed to total units.* #### Parameters -| Name | Type | Description | -| ------------ | ----------------------------------------- | ----------- | -| account | address | undefined | -| units | uint256 | undefined | -| fractions | uint256[] | undefined | -| uri | string | undefined | -| restrictions | enum IHypercertToken.TransferRestrictions | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| units | uint256 | undefined | +| fractions | uint256[] | undefined | +| uri | string | undefined | +| restrictions | enum IHypercertToken.TransferRestrictions | undefined | ### splitFraction @@ -102,15 +108,15 @@ function splitFraction(address account, uint256 tokenID, uint256[] _values) exte The sum of `values` must equal the current value of `_tokenID`. -_Function called to split `tokenID` owned by `account` into units declared in `values`._ +*Function called to split `tokenID` owned by `account` into units declared in `values`.* #### Parameters -| Name | Type | Description | -| -------- | --------- | ----------- | -| account | address | undefined | -| tokenID | uint256 | undefined | -| \_values | uint256[] | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenID | uint256 | undefined | +| _values | uint256[] | undefined | ### unitsOf @@ -118,20 +124,22 @@ _Function called to split `tokenID` owned by `account` into units declared in `v function unitsOf(address account, uint256 tokenID) external view returns (uint256 units) ``` -_Returns the `units` held by `account` of a (fractional) token at `claimID`If `tokenID` is a base type, the total amount of `units` held by `account` for the claim is returned.If `tokenID` is a fractional token, the `units` held by `account` the token is returned_ + + +*Returns the `units` held by `account` of a (fractional) token at `claimID`If `tokenID` is a base type, the total amount of `units` held by `account` for the claim is returned.If `tokenID` is a fractional token, the `units` held by `account` the token is returned* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| account | address | undefined | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| account | address | undefined | +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ----- | ------- | ----------- | -| units | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| units | uint256 | undefined | ### unitsOf @@ -139,19 +147,21 @@ _Returns the `units` held by `account` of a (fractional) token at `claimID`If `t function unitsOf(uint256 tokenID) external view returns (uint256 units) ``` -_Returns the `units` held by a (fractional) token at `claimID`If `tokenID` is a base type, the total amount of `units` for the claim is returned.If `tokenID` is a fractional token, the `units` held by the token is returned_ + + +*Returns the `units` held by a (fractional) token at `claimID`If `tokenID` is a base type, the total amount of `units` for the claim is returned.If `tokenID` is a fractional token, the `units` held by the token is returned* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| ----- | ------- | ----------- | -| units | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| units | uint256 | undefined | ### uri @@ -159,19 +169,23 @@ _Returns the `units` held by a (fractional) token at `claimID`If `tokenID` is a function uri(uint256 tokenID) external view returns (string metadata) ``` -_Returns the `uri` for metadata of the claim represented by `tokenID`Metadata must conform to { Hypercert Metadata } spec (based on ERC1155 Metadata)_ + + +*Returns the `uri` for metadata of the claim represented by `tokenID`Metadata must conform to { Hypercert Metadata } spec (based on ERC1155 Metadata)* #### Parameters -| Name | Type | Description | -| ------- | ------- | ----------- | -| tokenID | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| tokenID | uint256 | undefined | #### Returns -| Name | Type | Description | -| -------- | ------ | ----------- | -| metadata | string | undefined | +| Name | Type | Description | +|---|---|---| +| metadata | string | undefined | + + ## Events @@ -181,12 +195,17 @@ _Returns the `uri` for metadata of the claim represented by `tokenID`Metadata mu event ClaimStored(uint256 indexed claimID, string uri, uint256 totalUnits) ``` -_Emitted when token with tokenID `claimID` is stored, with external data reference via `uri`._ + + +*Emitted when token with tokenID `claimID` is stored, with external data reference via `uri`.* #### Parameters -| Name | Type | Description | -| ----------------- | ------- | ----------- | -| claimID `indexed` | uint256 | undefined | -| uri | string | undefined | -| totalUnits | uint256 | undefined | +| Name | Type | Description | +|---|---|---| +| claimID `indexed` | uint256 | undefined | +| uri | string | undefined | +| totalUnits | uint256 | undefined | + + + diff --git a/docs/docs/developer/api/contracts/libs/Errors.md b/docs/docs/developer/api/contracts/libs/Errors.md index 93fe0124..52031321 100644 --- a/docs/docs/developer/api/contracts/libs/Errors.md +++ b/docs/docs/developer/api/contracts/libs/Errors.md @@ -1,6 +1,14 @@ # Errors -_bitbeckers_ +*bitbeckers* + + + + + + + + ## Errors @@ -10,50 +18,97 @@ _bitbeckers_ error AlreadyClaimed() ``` + + + + + ### ArraySize ```solidity error ArraySize() ``` + + + + + ### DoesNotExist ```solidity error DoesNotExist() ``` + + + + + ### DuplicateEntry ```solidity error DuplicateEntry() ``` + + + + + ### Invalid ```solidity error Invalid() ``` + + + + + ### NotAllowed ```solidity error NotAllowed() ``` + + + + + ### NotApprovedOrOwner ```solidity error NotApprovedOrOwner() ``` + + + + + ### TransfersNotAllowed ```solidity error TransfersNotAllowed() ``` + + + + + ### TypeMismatch ```solidity error TypeMismatch() ``` + + + + + + + From 95ff7f628bc355316585f2c96f587783a899f808 Mon Sep 17 00:00:00 2001 From: jipstavenuiter Date: Thu, 26 Oct 2023 16:57:15 +0200 Subject: [PATCH 14/15] fix(build): ignore cors-proxy type errors --- cors-proxy/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cors-proxy/src/index.ts b/cors-proxy/src/index.ts index e88e6e3d..aaa9a400 100644 --- a/cors-proxy/src/index.ts +++ b/cors-proxy/src/index.ts @@ -69,6 +69,9 @@ export default { // Rewrite request to point to API URL. This also makes the request mutable // so you can add the correct Origin header to make the API server think // that this request is not cross-site. + // TODO: Never use ts ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore request = new Request(apiUrl, request); request.headers.set("Origin", new URL(apiUrl).origin); let response = await fetch(request); From db876cec01ab500a4eea9da759bd95721ee4eba7 Mon Sep 17 00:00:00 2001 From: bitbeckers Date: Fri, 27 Oct 2023 17:52:55 +0200 Subject: [PATCH 15/15] chore(gha): add rpc env vars for fork testing --- .github/workflows/ci-default.yml | 2 ++ graph/tests/.latest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-default.yml b/.github/workflows/ci-default.yml index cbc79fa4..c25b49e0 100644 --- a/.github/workflows/ci-default.yml +++ b/.github/workflows/ci-default.yml @@ -13,6 +13,8 @@ env: NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} NEXT_PUBLIC_SUPABASE_TABLE: ${{ vars.NEXT_PUBLIC_SUPABASE_TABLE }} NEXT_PUBLIC_WALLETCONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_ID }} + GOERLI_RPC_URL: ${{ vars.GOERLI_RPC_URL }} + MAINNET_RPC_URL: ${{ vars.MAINNET_RPC_URL }} DOCKER_PLATFORM: "amd64" # Trigger the workflow when: diff --git a/graph/tests/.latest.json b/graph/tests/.latest.json index ab8a5ffe..9f77af1a 100644 --- a/graph/tests/.latest.json +++ b/graph/tests/.latest.json @@ -1,4 +1,4 @@ { "version": "0.6.0", - "timestamp": 1698330729535 + "timestamp": 1698421837557 }