// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol"; import "../asset/Assets.sol"; import "./listing-terms-registry/IListingTermsRegistry.sol"; library Listings { using CountersUpgradeable for CountersUpgradeable.Counter; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.UintSet; using Listings for Registry; using Listings for Listing; using Assets for Assets.Asset; /** * @dev Thrown when the Listing with `listingId` * is neither registered among present ones nor listed (disabled). */ error ListingIsNeitherRegisteredNorListed(uint256 listingId); /** * @dev Thrown when the Listing with `listingId` is not registered among present ones. */ error ListingIsNotRegistered(uint256 listingId); /** * @dev Thrown when the operation is not allowed due to the listing being paused. */ error ListingIsPaused(); /** * @dev Thrown when the operation is not allowed due to the listing not being paused. */ error ListingIsNotPaused(); /** * @dev Thrown when attempting to lock listed assets for the period longer than the lister allowed. */ error InvalidLockPeriod(uint32 period); /** * @dev Listing params. * The layout of `config.data` might vary for different listing strategies. * For example, in case of FIXED_RATE strategy, the `config.data` might contain only base rate, * and for more advanced auction strategies it might include period, min bid step etc. * @param lister Listing creator. * @param configurator Optional listing configurator address which may customize renting conditions. */ struct Params { address lister; address configurator; } /** * @dev Listing structure. * @param assets Listed assets structure. * @param lister Lister account address. * @param beneficiary The target to receive payments or other various rewards from rentals. * @param maxLockPeriod The maximum amount of time the assets owner can wait before getting the assets back. * @param lockedTill The earliest possible time when the assets can be returned to the owner. * @param configurator Optional listing configurator address which may customize renting conditions * @param immediatePayout Indicates whether the rental fee must be transferred to the lister on every renting. * If FALSE, the rental fees get accumulated until withdrawn manually. * @param enabled Indicates whether listing is enabled. * @param paused Indicates whether the listing is paused. */ struct Listing { Assets.Asset[] assets; address lister; address beneficiary; uint32 maxLockPeriod; uint32 lockedTill; address configurator; bool immediatePayout; bool enabled; bool paused; } /** * @dev Listing related data associated with the specific account. * @param listingIndex The set of listing IDs. */ struct ListerInfo { EnumerableSetUpgradeable.UintSet listingIndex; } /** * @dev Listing related data associated with the specific account. * @param listingIndex The set of listing IDs. */ struct AssetInfo { EnumerableSetUpgradeable.UintSet listingIndex; } /** * @dev Listing registry. * @param idTracker Listing ID tracker (incremental counter). * @param listingIndex The global set of registered listing IDs. * @param listings Mapping from listing ID to the listing info. * @param listers Mapping from lister address to the lister info. * @param assetCollections Mapping from an Asset Collection's address to the asset info. */ struct Registry { CountersUpgradeable.Counter listingIdTracker; EnumerableSetUpgradeable.UintSet listingIndex; mapping(uint256 => Listing) listings; mapping(uint256 => Listing) listingsHistory; mapping(address => ListerInfo) listers; mapping(address => AssetInfo) assetCollections; } /** * @dev Puts the listing on pause. */ function pause(Listing storage self) internal { if (self.paused) revert ListingIsPaused(); self.paused = true; } /** * @dev Lifts the listing pause. */ function unpause(Listing storage self) internal { if (!self.paused) revert ListingIsNotPaused(); self.paused = false; } /** * Determines whether the listing is registered and active. */ function isRegisteredAndListed(Listing storage self) internal view returns (bool) { return self.isRegistered() && self.enabled; } function isRegistered(Listing storage self) internal view returns (bool) { return self.lister != address(0); } /** * @dev Reverts if the listing is paused. */ function checkNotPaused(Listing memory self) internal pure { if (self.paused) revert ListingIsPaused(); } /* * @dev Validates lock period. */ function isValidLockPeriod(Listing memory self, uint32 lockPeriod) internal pure returns (bool) { return (lockPeriod > 0 && lockPeriod <= self.maxLockPeriod); } /** * Determines whether the caller address is assets lister. */ function isAssetLister( Registry storage self, uint256 listingId, address caller ) internal view returns (bool) { return self.listings[listingId].lister == caller; } /** * @dev Reverts if the lock period is not valid. */ function checkValidLockPeriod(Listing memory self, uint32 lockPeriod) internal pure { if (!self.isValidLockPeriod(lockPeriod)) revert InvalidLockPeriod(lockPeriod); } /** * @dev Extends listing lock time. * Does not modify the state if current lock time is larger. */ function addLock(Listing storage self, uint32 unlockTimestamp) internal { // Listing is already locked till later time, no need to extend locking period. if (self.lockedTill >= unlockTimestamp) return; // Extend listing lock. self.lockedTill = unlockTimestamp; } /** * @dev Registers new listing. * @return listingId New listing ID. */ function register(Registry storage self, Listing memory listing) external returns (uint256 listingId) { // Generate new listing ID. self.listingIdTracker.increment(); listingId = self.listingIdTracker.current(); // Add new listing ID to the global index. self.listingIndex.add(listingId); // Add user listing data. self.listers[listing.lister].listingIndex.add(listingId); // Creating an instance of listing record Listing storage listingRecord = self.listings[listingId]; // Store new listing record. listingRecord.lister = listing.lister; listingRecord.beneficiary = listing.beneficiary; listingRecord.maxLockPeriod = listing.maxLockPeriod; listingRecord.lockedTill = listing.lockedTill; listingRecord.immediatePayout = listing.immediatePayout; listingRecord.enabled = listing.enabled; listingRecord.paused = listing.paused; listingRecord.configurator = listing.configurator; // Extract collection address. All Original Assets are from the same Original Asset Collection. address originalCollectionAddress = listing.assets[0].token(); self.assetCollections[originalCollectionAddress].listingIndex.add(listingId); // Add assets to listing record and listing data. for (uint256 i = 0; i < listing.assets.length; i++) { listingRecord.assets.push(listing.assets[i]); } } /** * @dev Removes listing data. * @param listingId The ID of the listing to be deleted. */ function remove(Registry storage self, uint256 listingId) external { // Creating an instance of listing record Listing storage listingRecord = self.listings[listingId]; // Remove the listing ID from the global index. self.listingIndex.remove(listingId); // Remove user listing data. self.listers[listingRecord.lister].listingIndex.remove(listingId); // All Original Assets are from the same Original Assets Collection. address originalCollectionAddress = listingRecord.assets[0].token(); self.assetCollections[originalCollectionAddress].listingIndex.remove(listingId); listingRecord.enabled = false; self.listingsHistory[listingId] = listingRecord; // Delete Listing. delete self.listings[listingId]; } /** * @dev Returns the paginated list of currently registered listings. */ function allListings( Registry storage self, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Listing[] memory) { return self.paginateIndexedListings(self.listingIndex, offset, limit); } /** * @dev Returns the paginated list of currently registered listings for the particular lister account. */ function userListings( Registry storage self, address lister, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Listing[] memory) { return self.paginateIndexedListings(self.listers[lister].listingIndex, offset, limit); } /** * @dev Returns the paginated list of currently registered listings for the original asset. */ function assetListings( Registry storage self, address original, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Listing[] memory) { return self.paginateIndexedListings(self.assetCollections[original].listingIndex, offset, limit); } /** * @dev Reverts if Listing is * neither registered among present Listings nor enabled. * @param listingId Listing ID. */ function checkRegisteredAndListed(Registry storage self, uint256 listingId) internal view { if (!self.listings[listingId].isRegisteredAndListed()) revert ListingIsNeitherRegisteredNorListed(listingId); } function checkRegistered(Registry storage self, uint256 listingId) internal view { if (!self.listings[listingId].isRegistered()) revert ListingIsNotRegistered(listingId); } /** * @dev Returns the number of currently registered listings. */ function listingCount(Registry storage self) internal view returns (uint256) { return self.listingIndex.length(); } /** * @dev Returns the number of currently registered listings for a particular lister account. */ function userListingCount(Registry storage self, address lister) internal view returns (uint256) { return self.listers[lister].listingIndex.length(); } /** * @dev Returns the number of currently registered listings for a particular original asset. */ function assetListingCount(Registry storage self, address original) internal view returns (uint256) { return self.assetCollections[original].listingIndex.length(); } /** * @dev Returns the paginated list of currently registered listing using provided index reference. */ function paginateIndexedListings( Registry storage self, EnumerableSetUpgradeable.UintSet storage listingIndex, uint256 offset, uint256 limit ) internal view returns (uint256[] memory, Listing[] memory) { uint256 indexSize = listingIndex.length(); if (offset >= indexSize) return (new uint256[](0), new Listing[](0)); if (limit > indexSize - offset) { limit = indexSize - offset; } Listing[] memory listings = new Listing[](limit); uint256[] memory listingIds = new uint256[](limit); for (uint256 i = 0; i < limit; i++) { listingIds[i] = listingIndex.at(offset + i); listings[i] = self.listings[listingIds[i]]; } return (listingIds, listings); } /** * @dev Returns the hash of listing terms strategy ID and data. * @param listingTerms Listing Terms. */ function hash(IListingTermsRegistry.ListingTerms memory listingTerms) internal pure returns (bytes32) { return keccak256(abi.encodePacked(listingTerms.strategyId, listingTerms.strategyData)); } }