// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; import "./IListingManager.sol"; import "../../contract-registry/ContractEntity.sol"; import "../../contract-registry/Contracts.sol"; import "../../acl/direct/AccessControlledUpgradeable.sol"; import "./ListingManagerStorage.sol"; import "../listing-configurator/IListingConfiguratorController.sol"; import "../listing-configurator/registry/IListingConfiguratorRegistry.sol"; contract ListingManager is IListingManager, UUPSUpgradeable, ContractEntity, Multicall, AccessControlledUpgradeable, ListingManagerStorage { using Address for address; using Assets for Assets.Asset; using Assets for Assets.Asset[]; using Listings for Listings.Listing; using Listings for Listings.Registry; /** * @dev ListingManager initialization params. * @param acl ACL contract address. * @param metahub Metahub contract address. */ struct ListingManagerInitParams { IACL acl; IMetahub metahub; } /** * @dev Modifier to make a function callable only by the Renting Manager contract */ modifier onlyRentingManager() { if (_msgSender() != _metahub.getContract(Contracts.RENTING_MANAGER)) revert CallerIsNotRentingManager(); _; } /** * @dev Modifier to make a function callable only by the account with LISTING_WIZARD role. */ modifier onlyListingWizard() { checkIsListingWizard(_msgSender()); _; } /** * @dev Modifier to make a function callable only by the Operator authorized for Listing management. * @param listingId Listing ID. */ modifier onlyIsAuthorizedOperatorForListingManagement(uint256 listingId) { address account = _msgSender(); if (!_isListingWizard(account) && !_isAssetLister(listingId, account)) { revert AccountIsNotAuthorizedOperatorForListingManagement(listingId, account); } _; } /** * @dev Modifier to make sure the function is called for a Listing * that has been registered and is listed. */ modifier onlyRegisteredAndListed(uint256 listingId) { checkRegisteredAndListed(listingId); _; } /** * @dev Modifier to make sure the function is called for a Listing * that has been registered. */ modifier onlyRegistered(uint256 listingId) { _checkRegistered(listingId); _; } /** * @dev Constructor that gets called for the implementation contract. * @custom:oz-upgrades-unsafe-allow constructor */ constructor() { _disableInitializers(); } /** * @dev Listing Manager initializer. * @param params Initialization params. */ function initialize(ListingManagerInitParams calldata params) external initializer { __UUPSUpgradeable_init(); _aclContract = IACL(params.acl); _metahub = IMetahub(params.metahub); } /** * @inheritdoc IListingManager */ function createListing( Assets.Asset[] calldata assets, Listings.Params calldata params, uint32 maxLockPeriod, bool immediatePayout ) external onlyListingWizard returns (uint256 listingId) { // Check that assets array is not empty if (assets.length == 0) { revert EmptyAssetsArray(); } // Since we have assets in batch we need them be sorted in increasing order // to guarantee stable hashing IAssetController(_metahub.assetClassController(assets[0].id.class)).ensureSorted(assets.toIds()); // Create listing record Listings.Listing memory listing; listing.lister = params.lister; listing.beneficiary = listing.lister; listing.maxLockPeriod = maxLockPeriod; listing.lockedTill = 0; listing.immediatePayout = immediatePayout; listing.enabled = true; listing.paused = false; listing.configurator = params.configurator; listing.assets = assets; address originalCollectionAddress = assets[0].token(); // batch deposit for (uint256 i = 0; i < assets.length; i++) { // All assets should be from the same Original Asset Collection. if (assets[i].token() != originalCollectionAddress) revert AssetCollectionMismatch(); // Transfer asset from lister account to the vault _metahub.depositAsset(assets[i], params.lister); } // validation from listing configurator perspective if (params.configurator != address(0)) { IListingConfiguratorController listingConfiguratorController = IListingConfiguratorController( IListingConfiguratorRegistry(_metahub.getContract(Contracts.LISTING_CONFIGURATOR_REGISTRY)) .getController(params.configurator) ); listingConfiguratorController.validateListing(assets, params, maxLockPeriod, immediatePayout); address beneficiary = listingConfiguratorController.getERC20RewardTarget(listing); listing.beneficiary = beneficiary; if (beneficiary != listing.lister && !listing.immediatePayout) { revert OnlyImmediatePayoutSupported(); } } // registering newly created listing record listingId = _listingRegistry.register(listing); // emitting event emit ListingCreated(listingId, listing.lister, listing.assets, params, listing.maxLockPeriod); } /** * @inheritdoc IListingManager */ function addLock(uint256 listingId, uint32 unlockTimestamp) external onlyRegisteredAndListed(listingId) onlyRentingManager { Listings.Listing storage listing = _listingRegistry.listings[listingId]; listing.addLock(unlockTimestamp); } /** * @inheritdoc IListingManager */ function disableListing(uint256 listingId) external onlyRegisteredAndListed(listingId) onlyIsAuthorizedOperatorForListingManagement(listingId) { Listings.Listing storage listing = _listingRegistry.listings[listingId]; listing.enabled = false; emit ListingDisabled(listingId, listing.lister, listing.lockedTill); } /** * @inheritdoc IListingManager */ function withdrawListingAssets(uint256 listingId) external onlyRegistered(listingId) onlyIsAuthorizedOperatorForListingManagement(listingId) { Listings.Listing memory listing = _listingRegistry.listings[listingId]; // Check whether the assets can be returned to the owner. if (uint32(block.timestamp) < listing.lockedTill) revert AssetIsLocked(); // Delete listing record. _listingRegistry.remove(listingId); // Transfer assets from the vault to the original owner. for (uint256 i = 0; i < listing.assets.length; i++) { _metahub.withdrawAsset(listing.assets[i]); } emit ListingWithdrawal(listingId, listing.lister, listing.assets); } /** * @inheritdoc IListingManager */ function pauseListing(uint256 listingId) external onlyRegisteredAndListed(listingId) onlyIsAuthorizedOperatorForListingManagement(listingId) { _listingRegistry.listings[listingId].pause(); emit ListingPaused(listingId); } /** * @inheritdoc IListingManager */ function unpauseListing(uint256 listingId) external onlyRegisteredAndListed(listingId) onlyIsAuthorizedOperatorForListingManagement(listingId) { _listingRegistry.listings[listingId].unpause(); emit ListingUnpaused(listingId); } /** * @inheritdoc IListingManager */ function listingInfo(uint256 listingId) external view returns (Listings.Listing memory listing) { Listings.Listing storage presentListing = _listingRegistry.listings[listingId]; if (presentListing.isRegistered()) { return presentListing; } Listings.Listing storage historicalListing = _listingRegistry.listingsHistory[listingId]; if (historicalListing.isRegistered()) { return historicalListing; } revert ListingNeverExisted(listingId); } /** * @inheritdoc IListingManager */ function listingCount() external view returns (uint256) { return _listingRegistry.listingCount(); } /** * @inheritdoc IListingManager */ function listings(uint256 offset, uint256 limit) external view returns (uint256[] memory, Listings.Listing[] memory) { return _listingRegistry.allListings(offset, limit); } /** * @inheritdoc IListingManager */ function userListingCount(address lister) external view returns (uint256) { return _listingRegistry.userListingCount(lister); } /** * @inheritdoc IListingManager */ function userListings( address lister, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Listings.Listing[] memory) { return _listingRegistry.userListings(lister, offset, limit); } /** * @inheritdoc IListingManager */ function assetListingCount(address original) external view returns (uint256) { return _listingRegistry.assetListingCount(original); } /** * @inheritdoc IListingManager */ function assetListings( address original, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Listings.Listing[] memory) { return _listingRegistry.assetListings(original, offset, limit); } /** * @inheritdoc IContractEntity */ function contractKey() external pure override returns (bytes4) { return Contracts.LISTING_MANAGER; } /** * @inheritdoc IListingManager */ function checkRegisteredAndListed(uint256 listingId) public view { return _listingRegistry.checkRegisteredAndListed(listingId); } /** * @inheritdoc IListingManager */ function checkIsListingWizard(address account) public view { if (!_isListingWizard(account)) { revert AccountIsNotListingWizard(account); } } /** * @inheritdoc IERC165 */ function supportsInterface(bytes4 interfaceId) public view override(ContractEntity, IERC165) returns (bool) { return interfaceId == type(IListingManager).interfaceId || super.supportsInterface(interfaceId); } /** * @inheritdoc UUPSUpgradeable * @dev Checks whether the caller is authorized to upgrade the Metahub implementation. */ function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { // solhint-disable-previous-line no-empty-blocks } /** * @inheritdoc AccessControlledUpgradeable */ function _acl() internal view override returns (IACL) { return _aclContract; } function _isListingWizard(address account) internal view returns (bool) { return _aclContract.hasRole(Roles.LISTING_WIZARD, account); } function _isAssetLister(uint256 listingId, address account) internal view returns (bool) { return _listingRegistry.isAssetLister(listingId, account); } function _checkRegistered(uint256 listingId) internal view { return _listingRegistry.checkRegistered(listingId); } }