// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; import "./IListingStrategyRegistry.sol"; import "../../contract-registry/ContractEntity.sol"; import "../../contract-registry/Contracts.sol"; import "../../acl/direct/AccessControlledUpgradeable.sol"; import "./ListingStrategyRegistryStorage.sol"; import "../../tax/tax-strategy-registry/ITaxStrategyRegistry.sol"; import "../../listing/listing-strategies/IListingController.sol"; contract ListingStrategyRegistry is IListingStrategyRegistry, UUPSUpgradeable, ContractEntity, AccessControlledUpgradeable, ListingStrategyRegistryStorage, Multicall { using ERC165CheckerUpgradeable for address; /** * @dev ListingStrategyRegistry initialization params. * @param acl ACL contract address. * @param metahub Metahub contract address. */ struct ListingStrategyRegistryInitParams { IACL acl; IMetahub metahub; } /** * @dev Modifier to make a function callable only for the registered listing strategy. */ modifier onlyRegisteredListingStrategy(bytes4 listingStrategyId) { checkRegisteredListingStrategy(listingStrategyId); _; } /** * @custom:oz-upgrades-unsafe-allow constructor */ constructor() initializer { // solhint-disable-previous-line no-empty-blocks } /** * @dev Contract initializer. * @param params Listing Strategy Registry initialization params. */ function initialize(ListingStrategyRegistryInitParams calldata params) external initializer { __UUPSUpgradeable_init(); _aclContract = IACL(params.acl); _metahub = IMetahub(params.metahub); } /** * @inheritdoc IListingStrategyRegistry */ function registerListingStrategy(bytes4 listingStrategyId, ListingStrategyConfig calldata config) external onlyAdmin { ITaxStrategyRegistry(_metahub.getContract(Contracts.TAX_STRATEGY_REGISTRY)).checkRegisteredTaxStrategy( config.taxStrategyId ); _checkValidListingController(listingStrategyId, config.controller); if (isRegisteredListingStrategy(listingStrategyId)) { revert ListingStrategyIsAlreadyRegistered(listingStrategyId); } _listingStrategies[listingStrategyId] = config; emit ListingStrategyRegistered(listingStrategyId, config.taxStrategyId, config.controller); } /** * @inheritdoc IListingStrategyRegistry */ function setListingController(bytes4 listingStrategyId, address controller) external onlySupervisor onlyRegisteredListingStrategy(listingStrategyId) { _checkValidListingController(listingStrategyId, controller); _listingStrategies[listingStrategyId].controller = controller; emit ListingStrategyControllerChanged(listingStrategyId, controller); } /** * @inheritdoc IListingStrategyRegistry */ function listingController(bytes4 listingStrategyId) external view onlyRegisteredListingStrategy(listingStrategyId) returns (address) { return _listingStrategies[listingStrategyId].controller; } /** * @inheritdoc IListingStrategyRegistry */ function listingTaxId(bytes4 listingStrategyId) external view onlyRegisteredListingStrategy(listingStrategyId) returns (bytes4) { return _listingStrategies[listingStrategyId].taxStrategyId; } /** * @inheritdoc IListingStrategyRegistry */ function listingStrategy(bytes4 listingStrategyId) external view onlyRegisteredListingStrategy(listingStrategyId) returns (ListingStrategyConfig memory) { return _listingStrategies[listingStrategyId]; } /** * @inheritdoc IListingStrategyRegistry */ function listingTaxController(bytes4 listingStrategyId) external view onlyRegisteredListingStrategy(listingStrategyId) returns (address) { bytes4 taxStrategyId = _listingStrategies[listingStrategyId].taxStrategyId; return ITaxStrategyRegistry(_metahub.getContract(Contracts.TAX_STRATEGY_REGISTRY)).taxController(taxStrategyId); } /** * @inheritdoc IContractEntity */ function contractKey() external pure override returns (bytes4) { return Contracts.LISTING_STRATEGY_REGISTRY; } /** * @inheritdoc IListingStrategyRegistry */ function isRegisteredListingStrategy(bytes4 listingStrategyId) public view returns (bool) { return _listingStrategies[listingStrategyId].controller != address(0); } /** * @inheritdoc IListingStrategyRegistry */ function checkRegisteredListingStrategy(bytes4 listingStrategyId) public view { if (!isRegisteredListingStrategy(listingStrategyId)) revert UnregisteredListingStrategy(listingStrategyId); } /** * @inheritdoc IERC165 */ function supportsInterface(bytes4 interfaceId) public view override(ContractEntity, IERC165) returns (bool) { return interfaceId == type(IListingStrategyRegistry).interfaceId || super.supportsInterface(interfaceId); } /** * @inheritdoc UUPSUpgradeable */ function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { // solhint-disable-previous-line no-empty-blocks } /** * @dev Reverts if provided address is not a valid listing controller. * @param listingStrategyId Listing strategy ID. * @param controller Listing controller address. */ function _checkValidListingController(bytes4 listingStrategyId, address controller) internal view { if (!controller.supportsInterface(type(IListingController).interfaceId)) revert InvalidListingControllerInterface(); bytes4 contractStrategyId = IListingController(controller).strategyId(); if (contractStrategyId != listingStrategyId) { revert ListingStrategyMismatch(contractStrategyId, listingStrategyId); } } /** * @inheritdoc AccessControlledUpgradeable */ function _acl() internal view override returns (IACL) { return _aclContract; } }