// SPDX-License-Identifier: MIT // solhint-disable code-complexity pragma solidity ^0.8.13; import "../IERC721WarperController.sol"; import "../../../asset/ERC721/v1-controller/ERC721AssetController.sol"; import "../../mechanics/v1-controller/renting-hook/IRentingHookMechanics.sol"; import "../../mechanics/v1-controller/availability-period/IAvailabilityPeriodMechanics.sol"; import "../../mechanics/v1-controller/rental-period/IRentalPeriodMechanics.sol"; import "../../mechanics/v1-controller/asset-rentability/IAssetRentabilityMechanics.sol"; import "../../mechanics/v1-controller/rental-fee-premium/IRentalFeePremiumMechanics.sol"; import "../IERC721Warper.sol"; import "../../../renting/renting-manager/IRentingManager.sol"; contract ERC721WarperController is IERC721WarperController, ERC721AssetController { using Assets for Assets.Asset; using Warpers for Warpers.Warper; /** * @inheritdoc IWarperController * @dev Needs to be called with `delegatecall` from Renting Manager, * otherwise warpers will reject the call. */ function warp( Assets.Asset[] memory assets, address warper, address to ) external onlyDelegatecall returns (bytes32 warpedCollectionId, Assets.Asset[] memory warpedAssets) { warpedCollectionId = _collectionId(warper); warpedAssets = new Assets.Asset[](assets.length); for (uint256 i = 0; i < assets.length; i++) { _validateAsset(assets[i]); (address original, uint256 tokenId) = _decodeAssetId(assets[i].id); // Make sure the correct warper is used for the asset. if (original != IWarper(warper).__original()) revert InvalidAssetForWarper(warper, original); // Encode warped asset. The tokenId of the warped asset is identical to the original one, // but the address is changed to warper contract. warpedAssets[i] = Assets.Asset(_encodeAssetId(warper, tokenId), assets[i].value); // If the warped asset has never been rented before, create new instance, otherwise transfer existing one. if (_rentalStatus(IRentingManager(address(this)), warper, tokenId) == Rentings.RentalStatus.NONE) { IERC721Warper(warper).mint(to, tokenId, new bytes(0)); } else { _transferAsset(warpedAssets[i], address(this), to, new bytes(0)); } } } /** * @inheritdoc IWarperController */ function executeRentingHooks( uint256 rentalId, Rentings.Agreement memory rentalAgreement, Accounts.RentalEarnings memory rentalEarnings ) external onlyDelegatecall { // All Warped Assets are from the same Warped Collection. _validateAsset(rentalAgreement.warpedAssets[0]); (address warper, ) = _decodeAssetId(rentalAgreement.warpedAssets[0].id); if (IWarper(warper).supportsInterface(type(IRentingHookMechanics).interfaceId)) { (bool success, string memory errorMessage) = IRentingHookMechanics(warper).__onRent( rentalId, rentalAgreement, rentalEarnings ); if (!success) revert IRentingHookMechanics.RentingHookError(errorMessage); } } /** * @inheritdoc IWarperController */ function checkCompatibleWarper(address warper) external view { if (!isCompatibleWarper(warper)) revert IncompatibleWarperInterface(); } /** * @inheritdoc IWarperController */ function validateRentingParams( Warpers.Warper memory warper, Assets.Asset[] memory assets, Rentings.Params calldata rentingParams ) external view { for (uint256 i = 0; i < assets.length; i++) { warper.checkCompatibleAsset(assets[i]); _validateAsset(assets[i]); // Ensure the warped asset is not rented. address warperAddress = rentingParams.warper; (, uint256 tokenId) = _decodeAssetId(assets[i].id); if ( rentalStatus(IWarper(warperAddress).__metahub(), warperAddress, tokenId) == Rentings.RentalStatus.RENTED ) { revert AlreadyRented(); } // Analyse warper functionality by checking the supported mechanics. bytes4[] memory mechanics = new bytes4[](3); mechanics[0] = type(IAvailabilityPeriodMechanics).interfaceId; mechanics[1] = type(IRentalPeriodMechanics).interfaceId; mechanics[2] = type(IAssetRentabilityMechanics).interfaceId; bool[] memory supportedMechanics = IWarper(warperAddress).__supportedInterfaces(mechanics); // Handle availability period mechanics. if (supportedMechanics[0]) { (uint32 start, uint32 end) = IAvailabilityPeriodMechanics(warperAddress).__availabilityPeriodRange(); if (block.timestamp < start || (block.timestamp + rentingParams.rentalPeriod) > end) { revert IAvailabilityPeriodMechanics.WarperIsNotAvailableForRenting(block.timestamp, start, end); } } // Handle rental period mechanics. if (supportedMechanics[1]) { (uint32 min, uint32 max) = IRentalPeriodMechanics(warperAddress).__rentalPeriodRange(); if (rentingParams.rentalPeriod < min || rentingParams.rentalPeriod > max) { revert IRentalPeriodMechanics.WarperRentalPeriodIsOutOfRange(rentingParams.rentalPeriod, min, max); } } // Handle asset rentability mechanics. if (supportedMechanics[2]) { (bool isRentable, string memory errorMessage) = IAssetRentabilityMechanics(warperAddress) .__isRentableAsset(rentingParams.renter, tokenId, assets[i].value); if (!isRentable) revert IAssetRentabilityMechanics.AssetIsNotRentable(errorMessage); } } } /** * @inheritdoc IWarperController */ function calculatePremiums( Assets.Asset[] memory assets, Rentings.Params calldata rentingParams, uint256 universeFee, uint256 listerFee ) external view virtual returns (uint256 universePremiumTotal, uint256 listerPremiumTotal) { for (uint256 i = 0; i < assets.length; i++) { _validateAsset(assets[i]); if (IWarper(rentingParams.warper).supportsInterface(type(IRentalFeePremiumMechanics).interfaceId)) { (, uint256 tokenId) = _decodeAssetId(assets[i].id); (uint256 universePremium, uint256 listerPremium) = IRentalFeePremiumMechanics(rentingParams.warper) .__calculatePremiums( rentingParams.renter, tokenId, assets[i].value, rentingParams.rentalPeriod, universeFee, listerFee ); universePremiumTotal += universePremium; listerPremiumTotal += listerPremium; } } } /** * @inheritdoc IERC721WarperController */ function rentalBalance( address metahub, address warper, address renter ) external view returns (uint256) { return IRentingManager(IMetahub(metahub).getContract(Contracts.RENTING_MANAGER)).collectionRentedValue( _collectionId(warper), renter ); } /** * @inheritdoc IERC165 */ function supportsInterface(bytes4 interfaceId) public view virtual override(AssetController, IERC165) returns (bool) { return interfaceId == type(IERC721WarperController).interfaceId || super.supportsInterface(interfaceId); } /** * @inheritdoc IWarperController */ function isCompatibleWarper(address warper) public view returns (bool) { return IWarper(warper).supportsInterface(type(IERC721Warper).interfaceId); } /** * @inheritdoc IERC721WarperController */ function rentalStatus( address metahub, address warper, uint256 tokenId ) public view returns (Rentings.RentalStatus) { return _rentalStatus(IRentingManager(IMetahub(metahub).getContract(Contracts.RENTING_MANAGER)), warper, tokenId); } function _rentalStatus( IRentingManager rentingManager, address warper, uint256 tokenId ) internal view returns (Rentings.RentalStatus) { return rentingManager.assetRentalStatus(_encodeAssetId(warper, tokenId)); } }