// 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 "./IRentingManager.sol"; import "../../contract-registry/ContractEntity.sol"; import "../../acl/direct/AccessControlledUpgradeable.sol"; import "./RentingManagerStorage.sol"; contract RentingManager is IRentingManager, Initializable, UUPSUpgradeable, ContractEntity, AccessControlledUpgradeable, RentingManagerStorage { using Address for address; using Assets for Assets.Asset; using Assets for Assets.Asset[]; using Rentings for Rentings.Registry; using Rentings for Rentings.Agreement; using Listings for Listings.Listing; using Listings for Listings.Registry; /** * @dev RentingManager initialization params. * @param acl ACL contract address. * @param metahub Metahub contract address. */ struct RentingManagerInitParams { IACL acl; IMetahub metahub; } /** * @dev Renting Manager initializer. * @param params Initialization params. */ function initialize(RentingManagerInitParams calldata params) external initializer { __UUPSUpgradeable_init(); _aclContract = IACL(params.acl); _metahub = IMetahub(params.metahub); } /** * @inheritdoc IRentingManager */ function rent( Rentings.Params calldata rentingParams, bytes calldata tokenQuote, bytes calldata tokenQuoteSignature, uint256 maxPaymentAmount ) external returns (uint256 rentalId) { // Validate renting parameters. Rentings.validateRentingParams(rentingParams, _metahub); // Warp the asset and deliver to to the renter. (bytes32 warpedCollectionId, Assets.Asset[] memory warpedAssets) = _warpListedAssets( rentingParams.listingId, rentingParams.warper, rentingParams.renter ); // Register new rental agreement. Rentings.Agreement memory rentalAgreement; rentalAgreement.listingId = rentingParams.listingId; rentalAgreement.renter = rentingParams.renter; rentalAgreement.startTime = uint32(block.timestamp); rentalAgreement.endTime = rentalAgreement.startTime + rentingParams.rentalPeriod; rentalAgreement.collectionId = warpedCollectionId; rentalAgreement.agreementTerms.listingTerms = _emptyListingTerms(); // Filled in _handleRentalPayment() rentalAgreement.agreementTerms.universeTaxTerms = _emptyTaxTerms(); // Filled in _handleRentalPayment() rentalAgreement.agreementTerms.protocolTaxTerms = _emptyTaxTerms(); // Filled in _handleRentalPayment() rentalAgreement.warpedAssets = warpedAssets; // Register new rental agreement. rentalId = _rentingRegistry.register(rentalAgreement); // Update listing lock time. IListingManager(_metahub.getContract(Contracts.LISTING_MANAGER)).addLock( rentingParams.listingId, rentalAgreement.endTime ); // Clean up x2 expired rental agreements. _rentingRegistry.deleteExpiredUserRentalAgreements(rentingParams.renter, warpedCollectionId, 2); // Handle rental payments and Warper Hook Mechanics. // NB! rentalAgreement only has partial information available in this scope, // since it is being update further as well. _handleRentalPaymentAndExecuteWarperHook( rentalAgreement, rentingParams, _msgSender(), maxPaymentAmount, rentalId, tokenQuote, tokenQuoteSignature ); emit AssetRented( rentalId, rentalAgreement.renter, rentalAgreement.listingId, rentalAgreement.warpedAssets, rentalAgreement.startTime, rentalAgreement.endTime ); } /** * @inheritdoc IRentingManager */ function rentalAgreementInfo(uint256 rentalId) external view returns (Rentings.Agreement memory) { Rentings.Agreement storage presentRentalAgreement = _rentingRegistry.agreements[rentalId]; if (presentRentalAgreement.isRegistered()) { return presentRentalAgreement; } Rentings.Agreement storage historicalRentalAgreement = _rentingRegistry.agreementsHistory[rentalId]; if (historicalRentalAgreement.isRegistered()) { return historicalRentalAgreement; } revert RentalAgreementNeverExisted(rentalId); } /** * @inheritdoc IRentingManager */ function userRentalCount(address renter) external view returns (uint256) { return _rentingRegistry.userRentalCount(renter); } /** * @inheritdoc IRentingManager */ function userRentalAgreements( address renter, uint256 offset, uint256 limit ) external view returns (uint256[] memory, Rentings.Agreement[] memory) { return _rentingRegistry.userRentalAgreements(renter, offset, limit); } /** * @inheritdoc IRentingManager */ function estimateRent(Rentings.Params calldata rentingParams) external view returns (Rentings.RentalFees memory) { Rentings.validateRentingParams(rentingParams, _metahub); Warpers.Warper memory warper = IWarperManager(_metahub.getContract(Contracts.WARPER_MANAGER)).warperInfo( rentingParams.warper ); return Rentings.calculateRentalFees(rentingParams, warper, _metahub); } /** * @inheritdoc IRentingManager */ function collectionRentedValue(bytes32 warpedCollectionId, address renter) external view returns (uint256) { return _rentingRegistry.collectionRentedValue(renter, warpedCollectionId); } /** * @inheritdoc IRentingManager */ function assetRentalStatus(Assets.AssetId calldata warpedAssetId) external view returns (Rentings.RentalStatus) { return _rentingRegistry.assetRentalStatus(warpedAssetId); } /** * @inheritdoc IContractEntity */ function contractKey() external pure override returns (bytes4) { return Contracts.RENTING_MANAGER; } /** * @inheritdoc IERC165 */ function supportsInterface(bytes4 interfaceId) public view override(ContractEntity, IERC165) returns (bool) { return interfaceId == type(IRentingManager).interfaceId || super.supportsInterface(interfaceId); } /** * @dev Finds the listed asset and warps it, using corresponding warper controller. * @param listingId Listing ID. * @param warper Warper address. * @param renter Renter address. * @return collectionId Warped collection ID. * @return warpedAssets Warped asset structure. */ function _warpListedAssets( uint256 listingId, address warper, address renter ) internal returns (bytes32 collectionId, Assets.Asset[] memory warpedAssets) { address controller = IWarperManager(_metahub.getContract(Contracts.WARPER_MANAGER)).warperController(warper); Assets.Asset[] memory assets = IListingManager(_metahub.getContract(Contracts.LISTING_MANAGER)) .listingInfo(listingId) .assets; (collectionId, warpedAssets) = abi.decode( controller.functionDelegateCall( abi.encodeWithSelector(IWarperController.warp.selector, assets, warper, renter) ), (bytes32, Assets.Asset[]) ); } /** * @dev Executes warper rental hook using the corresponding controller. * @param warper Warper address. * @param rentalId Rental Agreement ID. * @param rentalAgreement Newly registered rental agreement details. * @param rentalEarnings The rental earnings breakdown. */ function _executeWarperRentalHook( address warper, uint256 rentalId, Rentings.Agreement memory rentalAgreement, Accounts.RentalEarnings memory rentalEarnings ) internal { address controller = IWarperManager(_metahub.getContract(Contracts.WARPER_MANAGER)).warperController(warper); controller.functionDelegateCall( abi.encodeWithSelector( IWarperController.executeRentingHooks.selector, rentalId, rentalAgreement, rentalEarnings ) ); } /** * @dev Handles all rental payments. */ function _handleRentalPaymentAndExecuteWarperHook( Rentings.Agreement memory rentalAgreement, Rentings.Params calldata rentingParams, address payer, uint256 maxPaymentAmount, uint256 rentalId, bytes calldata tokenQuote, bytes calldata tokenQuoteSignature ) internal { Warpers.Warper memory warper = IWarperManager(_metahub.getContract(Contracts.WARPER_MANAGER)).warperInfo( rentingParams.warper ); // Get precise estimation. Rentings.RentalFees memory fees = Rentings.calculateRentalFees(rentingParams, warper, _metahub); // Creating instance of Payment Token Data and Rental Earnings. ITokenQuote.PaymentTokenData memory paymentTokenData; Accounts.RentalEarnings memory rentalEarnings; // Handle rental payment. (rentalEarnings, paymentTokenData) = _metahub.handleRentalPayment( rentingParams, fees, payer, maxPaymentAmount, tokenQuote, tokenQuoteSignature ); // Update agreement config with rental fees and payment token data. rentalAgreement = _rentingRegistry.updateAgreementConfig( rentalAgreement, rentalId, fees, warper, paymentTokenData ); // Execute rental hook. _executeWarperRentalHook(rentingParams.warper, rentalId, rentalAgreement, rentalEarnings); } /** * @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; } /** * @dev Returns empty listing terms. * @return listingTerms Empty listing terms structure. */ function _emptyListingTerms() internal pure returns (IListingTermsRegistry.ListingTerms memory listingTerms) { listingTerms = IListingTermsRegistry.ListingTerms(0x00, ""); } /** * @dev Returns empty tax terms. * @return taxTerms Empty tax terms structure. */ function _emptyTaxTerms() internal pure returns (ITaxTermsRegistry.TaxTerms memory taxTerms) { taxTerms = ITaxTermsRegistry.TaxTerms(0x00, ""); } }