// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "../../acl/direct/AccessControlledUpgradeable.sol"; import "./MetahubStorage.sol"; import "../../renting/Rentings.sol"; contract Metahub is IMetahub, Initializable, UUPSUpgradeable, AccessControlledUpgradeable, MetahubStorage { using ERC165CheckerUpgradeable for address; using Address for address; using Accounts for Accounts.Account; using Accounts for Accounts.Registry; using Assets for Assets.Asset; using Protocol for Protocol.Config; using Assets for Assets.Registry; using Listings for Listings.Listing; using Listings for Listings.Registry; using Rentings for Rentings.Registry; using Warpers for Warpers.Warper; using Warpers for Warpers.Registry; /** * @dev Metahub initialization params. * @param acl Protocol access control contract address. * @param baseToken Protocol->Config->baseToken. * @param protocolExternalFeesCollector Protocol->Config->protocolExternalFeesCollector. * @param assetClassRegistry The Asset Class Registry contract. */ struct MetahubInitParams { IACL acl; IERC20Upgradeable baseToken; address protocolExternalFeesCollector; IAssetClassRegistry assetClassRegistry; } /** * @dev Modifier to make a function callable only by the universe owner. */ modifier onlyUniverseOwner(uint256 universeId) { IUniverseRegistry(_getContract(Contracts.UNIVERSE_REGISTRY)).checkUniverseOwner(universeId, _msgSender()); _; } /** * @dev Modifier to make a function callable when contract with certain key exists. */ modifier onlyExistingContract(bytes4 contractKey) { _checkContractExists(contractKey); _; } /** * @dev Modifier to make a function callable only by the WarperManager contract. */ modifier onlyWarperManager() { if (_msgSender() != _getContract(Contracts.WARPER_MANAGER)) revert CallerIsNotWarperManager(); _; } /** * @dev Modifier to make a function callable only by the ListingManager contract. */ modifier onlyListingManager() { if (_msgSender() != _getContract(Contracts.LISTING_MANAGER)) revert CallerIsNotListingManager(); _; } /** * @dev Modifier to make a function callable only by the RentingManager contract. */ modifier onlyRentingManager() { if (_msgSender() != _getContract(Contracts.RENTING_MANAGER)) revert CallerIsNotRentingManager(); _; } /** * @dev Modifier to make a function callable only by the ERC20RewardDistributor contract. */ modifier onlyERC20RewardDistributor() { if (_msgSender() != _getContract(Contracts.ERC20_REWARD_DISTRIBUTOR)) revert CallerIsNotERC20RewardDistributor(); _; } /** * @custom:oz-upgrades-unsafe-allow constructor */ constructor() { _disableInitializers(); } /** * @dev Metahub initializer. * ACL and AssetClassRegistry are self-registered from here and THEY DO NOT OWN a reference to Metahub! * @param params Initialization params. */ function initialize(MetahubInitParams calldata params) external initializer { __UUPSUpgradeable_init(); _aclContract = params.acl; _protocolConfig = Protocol.Config({ baseToken: params.baseToken, protocolExternalFeesCollector: params.protocolExternalFeesCollector }); _assetRegistry.classRegistry = params.assetClassRegistry; _registerContract(_aclContract.contractKey(), address(_aclContract)); _registerContract(params.assetClassRegistry.contractKey(), address(params.assetClassRegistry)); } /** * @inheritdoc IContractRegistry */ function registerContract(bytes4 contractKey, address contractAddress) external onlyAdmin { _registerContract(contractKey, contractAddress); } /** * @inheritdoc IAssetManager */ function registerAsset(bytes4 assetClass, address original) external onlyWarperManager { // Register the original asset if it is seen for the first time. _assetRegistry.registerAsset(assetClass, original); } /** * @inheritdoc IAssetManager */ function depositAsset(Assets.Asset calldata asset, address from) external onlyListingManager { // Transfer asset from lister account to the vault. _assetRegistry.transferAssetToVault(asset, from); } /** * @inheritdoc IAssetManager */ function withdrawAsset(Assets.Asset calldata asset) external onlyListingManager { // Transfer asset from lister account to the vault. _assetRegistry.returnAssetFromVault(asset); } /** * @inheritdoc IPaymentManager */ function handleRentalPayment( Rentings.Params calldata rentingParams, Rentings.RentalFees calldata fees, address payer, uint256 maxPaymentAmount, bytes calldata tokenQuote, bytes calldata tokenQuoteSignature ) external onlyRentingManager returns ( Accounts.RentalEarnings memory rentalFixedEarnings, ITokenQuote.PaymentTokenData memory paymentTokenData ) { Rentings.RentalFees memory rentalFees; if (rentingParams.paymentToken != address(_protocolConfig.baseToken)) { (rentalFees, paymentTokenData) = ITokenQuote(_getContract(Contracts.TOKEN_QUOTE)).useTokenQuote( rentingParams, fees, tokenQuote, tokenQuoteSignature ); } else { rentalFees = fees; paymentTokenData.paymentToken = address(_protocolConfig.baseToken); paymentTokenData.paymentTokenQuote = 10**_baseTokenDecimals(); } rentalFixedEarnings = _accountRegistry.handleRentalPayment(rentingParams, rentalFees, payer, maxPaymentAmount); _emitRentalEarningsEvents(rentalFixedEarnings); } function handleExternalERC20Reward( Listings.Listing memory listing, Rentings.Agreement memory agreement, ERC20RewardDistributionHelper.RentalExternalERC20RewardFees memory rentalExternalERC20RewardFees ) external onlyERC20RewardDistributor returns (Accounts.RentalEarnings memory rentalExternalRewardEarnings) { rentalExternalRewardEarnings = _accountRegistry.handleExternalERC20Reward( listing, agreement, rentalExternalERC20RewardFees, _msgSender() ); _emitRentalEarningsEvents(rentalExternalRewardEarnings); } /** * @inheritdoc IPaymentManager */ function withdrawProtocolFunds( address token, uint256 amount, address to ) external onlyAdmin { _accountRegistry.protocol.withdraw(token, amount, to); } /** * @inheritdoc IPaymentManager */ function withdrawUniverseFunds( uint256 universeId, address token, uint256 amount, address to ) external onlyUniverseOwner(universeId) { _accountRegistry.universes[universeId].withdraw(token, amount, to); } /** * @inheritdoc IPaymentManager */ function withdrawFunds( address token, uint256 amount, address to ) external { _accountRegistry.users[_msgSender()].withdraw(token, amount, to); } /** * @inheritdoc IProtocolConfigManager */ function changeProtocolExternalFeesCollector(address newProtocolExternalFeesCollector) external onlyAdmin { address oldProtocolExternalFeesCollector = _protocolConfig.protocolExternalFeesCollector; _protocolConfig.protocolExternalFeesCollector = newProtocolExternalFeesCollector; emit ProtocolExternalFeesCollectorChanged(oldProtocolExternalFeesCollector, newProtocolExternalFeesCollector); } /** * @inheritdoc IAssetManager */ function assetClassController(bytes4 assetClass) external view returns (address) { return _assetRegistry.assetClassController(assetClass); } /** * @inheritdoc IAssetManager */ function supportedAssetCount() external view returns (uint256) { return _assetRegistry.assetCount(); } /** * @inheritdoc IAssetManager */ function supportedAssets(uint256 offset, uint256 limit) external view returns (address[] memory, Assets.AssetConfig[] memory) { return _assetRegistry.supportedAssets(offset, limit); } /** * @inheritdoc IProtocolConfigManager */ function baseToken() external view returns (address) { return address(_protocolConfig.baseToken); } /** * @inheritdoc IProtocolConfigManager */ function baseTokenDecimals() external view returns (uint8) { return _baseTokenDecimals(); } /** * @inheritdoc IProtocolConfigManager */ function protocolExternalFeesCollector() external view returns (address) { return _protocolConfig.protocolExternalFeesCollector; } /** * @inheritdoc IPaymentManager */ function protocolBalance(address token) external view returns (uint256) { return _accountRegistry.protocol.balance(token); } /** * @inheritdoc IPaymentManager */ function protocolBalances() external view returns (Accounts.Balance[] memory) { return _accountRegistry.protocol.balances(); } /** * @inheritdoc IPaymentManager */ function universeBalance(uint256 universeId, address token) external view returns (uint256) { return _accountRegistry.universes[universeId].balance(token); } /** * @inheritdoc IPaymentManager */ function universeBalances(uint256 universeId) external view returns (Accounts.Balance[] memory) { return _accountRegistry.universes[universeId].balances(); } /** * @inheritdoc IPaymentManager */ function balance(address account, address token) external view returns (uint256) { return _accountRegistry.users[account].balance(token); } /** * @inheritdoc IPaymentManager */ function balances(address account) external view returns (Accounts.Balance[] memory) { return _accountRegistry.users[account].balances(); } /** * @inheritdoc IContractRegistry */ function getContract(bytes4 contractKey) external view returns (address) { return _getContract(contractKey); } /** * @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 } function _registerContract(bytes4 contractKey, address contractAddress) internal { _checkValidContractEntity(contractKey, contractAddress); _contractRegistry[contractKey] = contractAddress; emit ContractRegistered(contractKey, contractAddress); } /** * @dev Emits necessary events about rental related earnings. * @param rentalEarnings The rental earnings spread. */ function _emitRentalEarningsEvents(Accounts.RentalEarnings memory rentalEarnings) internal { for (uint256 i = 0; i < rentalEarnings.userEarnings.length; i++) { Accounts.UserEarning memory userEarning = rentalEarnings.userEarnings[i]; if (userEarning.value == 0) continue; emit UserEarned(userEarning.account, userEarning.earningType, userEarning.token, userEarning.value); } if (rentalEarnings.universeEarning.value > 0) { emit UniverseEarned( rentalEarnings.universeEarning.universeId, rentalEarnings.universeEarning.earningType, rentalEarnings.universeEarning.token, rentalEarnings.universeEarning.value ); } if (rentalEarnings.protocolEarning.value > 0) { emit ProtocolEarned( rentalEarnings.protocolEarning.earningType, rentalEarnings.protocolEarning.token, rentalEarnings.protocolEarning.value ); } } /** * @dev Reverts if the contract with a key does not exists. * @param contractKey Key of the contract. */ function _checkContractExists(bytes4 contractKey) internal view { if (!_isExistingContract(contractKey)) revert ContractDoesNotExist(contractKey); } /** * @dev Reverts if provided address is not a valid contract entity. * @param contractKey Contract entity key. * @param contractAddress Contract entity address. */ function _checkValidContractEntity(bytes4 contractKey, address contractAddress) internal view { if (!IContractEntity(contractAddress).supportsInterface(type(IContractEntity).interfaceId)) { revert InvalidContractEntityInterface(); } bytes4 contractEntityKey = IContractEntity(contractAddress).contractKey(); if (contractKey != contractEntityKey) { revert ContractKeyMismatch(contractKey, contractEntityKey); } } /** * @dev Returns the base token decimals. * @return The base token decimals. */ function _baseTokenDecimals() internal view returns (uint8) { return ERC20Upgradeable(address(_protocolConfig.baseToken)).decimals(); } /** * @dev Get contract address with a key. * @param contractKey Key of the contract. * @return Contract address. */ function _getContract(bytes4 contractKey) internal view onlyExistingContract(contractKey) returns (address) { return _contractRegistry[contractKey]; } /** * @dev Checks if the contract with a key exists. * @param contractKey Key of the contract. * @return True if exists. */ function _isExistingContract(bytes4 contractKey) internal view returns (bool) { return _contractRegistry[contractKey] != address(0); } /** * @inheritdoc AccessControlledUpgradeable */ function _acl() internal view override returns (IACL) { return _aclContract; } }