// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts/utils/Multicall.sol"; import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; import "./IListingWizardV1.sol"; import "./../utils/ListingWizardsHelperV1.sol"; import "./../utils/GeneralWizardsHelperV1.sol"; import "../../../contract-registry/ContractEntity.sol"; import "../../../listing/listing-manager/IListingManager.sol"; contract ListingWizardV1 is IListingWizardV1, ContractEntity, EIP712, Multicall { using Assets for Assets.Asset; using Address for address; using CountersUpgradeable for CountersUpgradeable.Counter; /** * @dev Delegate listing hashed data signature. */ bytes32 private constant _DELEGATED_LISTING_TYPEHASH = keccak256("DelegatedListing(uint256 nonce)"); // @dev Mapping from lister's address to nonces counter. mapping(address => CountersUpgradeable.Counter) internal _listerDelegatedListingNonces; modifier onlyCallerIsLister(Listings.Params calldata params) { if (msg.sender != params.lister) revert CallerIsNotLister(); _; } /** * @dev Listing Wizard constructor */ constructor(address metahub) EIP712("IQProtocol", "1") { _metahub = IMetahub(metahub); } /** * @inheritdoc IListingWizardV1 */ function createListingWithTerms( Assets.Asset[] calldata assets, Listings.Params calldata params, IListingTermsRegistry.ListingTerms calldata terms, uint32 maxLockPeriod, bool immediatePayout, uint256 universeId ) external onlyCallerIsLister(params) returns (uint256 listingId, uint256 listingTermsId) { (listingId, listingTermsId) = _createListingWithTerms( assets, params, terms, maxLockPeriod, immediatePayout, universeId ); } /** * @inheritdoc IListingWizardV1 */ function delegatedCreateListingWithTerms( Assets.Asset[] calldata assets, Listings.Params calldata params, IListingTermsRegistry.ListingTerms calldata terms, uint32 maxLockPeriod, bool immediatePayout, uint256 universeId, bytes calldata delegatedListingSignature ) external returns (uint256 listingId, uint256 listingTermsId) { bytes32 delegatedListingHash = keccak256( abi.encode( _DELEGATED_LISTING_TYPEHASH, // is sufficient to call _useNonce() to prevent multiple usage _useNonce(params.lister) ) ); _validateSigner(params.lister, delegatedListingHash, delegatedListingSignature); (listingId, listingTermsId) = _createListingWithTerms( assets, params, terms, maxLockPeriod, immediatePayout, universeId ); } /** * @inheritdoc IListingWizardV1 */ function getDelegatedListingCurrentNonce(address lister) external view returns (uint256) { return _listerDelegatedListingNonces[lister].current(); } /** * @inheritdoc IListingWizardV1 */ // solhint-disable func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { // solhint-disable-line return _domainSeparatorV4(); // solhint-disable-line } /** * @inheritdoc IListingWizardV1 */ function getChainId() external view returns (uint256) { return block.chainid; } /** * @inheritdoc IContractEntity */ function contractKey() external pure override returns (bytes4) { return Contracts.LISTING_WIZARD_V1; } /** * @inheritdoc IERC165 */ function supportsInterface(bytes4 interfaceId) public view override(ContractEntity, IERC165) returns (bool) { return interfaceId == type(IListingWizardV1).interfaceId || super.supportsInterface(interfaceId); } /** * @dev "Consume a nonce": return the current value and increment. * * _Available since v4.1._ */ function _useNonce(address lister) internal returns (uint256 current) { CountersUpgradeable.Counter storage delegatedListingNonces = _listerDelegatedListingNonces[lister]; current = delegatedListingNonces.current(); delegatedListingNonces.increment(); } function _createListingWithTerms( Assets.Asset[] calldata assets, Listings.Params calldata params, IListingTermsRegistry.ListingTerms calldata terms, uint32 maxLockPeriod, bool immediatePayout, uint256 universeId ) internal returns (uint256 listingId, uint256 listingTermsId) { listingId = IListingManager(_metahub.getContract(Contracts.LISTING_MANAGER)).createListing( assets, params, maxLockPeriod, immediatePayout ); GeneralWizardsHelperV1.checkListingTermsAreValid(terms); listingTermsId = IListingTermsRegistry(_metahub.getContract(Contracts.LISTING_TERMS_REGISTRY)) .registerUniverseListingTerms(listingId, universeId, terms); // Detecting address of Original Assets Collection (all Assets are from the same Collection) address originalCollection = assets[0].token(); ListingWizardsHelperV1.validateMatchWithUniverse(universeId, originalCollection, terms, _metahub); } function _validateSigner( address declaredLister, bytes32 delegatedListingHash, bytes calldata delegatedListingSignature ) internal view { // getting fully encoded EIP712 message bytes32 hash = _hashTypedDataV4(delegatedListingHash); // decoding ECDSA signature (uint8 v, bytes32 r, bytes32 s) = abi.decode(delegatedListingSignature, (uint8, bytes32, bytes32)); // recovering ECDSA signature and getting message signer address delegatedListingSigner = ECDSAUpgradeable.recover(hash, v, r, s); // validating that declared lister is matching the Delegated Listing signer if (declaredLister != delegatedListingSigner) { revert DeclaredListerIsDifferentFromDelegatedListingSigner(declaredLister, delegatedListingSigner); } } }