// File: contracts/ethereum/Manager.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { ITokenMetadataRenderer } from './interfaces/ITokenMetadataRenderer.sol';
import { IManager } from './interfaces/IManager.sol';
import { Ownable } from './lib/utils/Ownable.sol';
import { IHouse } from './interfaces/IHouse.sol';
import { IRound } from './interfaces/IRound.sol';

/// @title Manager
/// @notice This contract allows an account to manage house and round implementations,
/// metadata renderers for all protocol contracts, and the security council address.
contract Manager is IManager, Ownable {
    /// @notice Determine if a contract is a registered house implementation
    /// @dev House Address => Registered
    mapping(address => bool) private houses;

    /// @notice Determine if a contract is a registered round for a house implementation
    /// @dev House Address => Round Address => Is Registered
    mapping(address => mapping(address => bool)) private rounds;

    /// @notice A mapping of contract addresses to their metadata renderers
    /// @dev Contract Address => Metadata Renderer Address
    mapping(address => address) private renderers;

    /// @notice A council that has permission to execute emergency actions
    address private securityCouncil;

    constructor() initializer {
        __Ownable_init(msg.sender);
    }

    /// @notice Determine if a house implementation is registered
    /// @param houseImpl The house implementation address
    function isHouseRegistered(address houseImpl) external view returns (bool) {
        return houses[houseImpl];
    }

    /// @notice Determine if a round implementation is registered on the provided house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    function isRoundRegistered(address houseImpl, address roundImpl) external view returns (bool) {
        return rounds[houseImpl][roundImpl];
    }

    /// @notice Get the metadata renderer for a contract.
    /// @param contract_ The contract address
    function getMetadataRenderer(address contract_) external view returns (ITokenMetadataRenderer) {
        return ITokenMetadataRenderer(renderers[contract_]);
    }

    /// @notice Get the security council address
    function getSecurityCouncil() external view returns (address) {
        return securityCouncil;
    }

    /// @notice Register a house implementation contract
    /// @param houseImpl The house implementation address
    function registerHouse(address houseImpl) external onlyOwner {
        houses[houseImpl] = true;

        emit HouseRegistered(houseImpl, IHouse(houseImpl).kind());
    }

    /// @notice Unregister a house implementation contract
    /// @param houseImpl The house implementation address
    function unregisterHouse(address houseImpl) external onlyOwner {
        delete houses[houseImpl];

        emit HouseUnregistered(houseImpl);
    }

    /// @notice Register a round implementation contract for a house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    function registerRound(address houseImpl, address roundImpl) external onlyOwner {
        rounds[houseImpl][roundImpl] = true;

        emit RoundRegistered(houseImpl, roundImpl, IRound(roundImpl).kind());
    }

    /// @notice Unregister a round implementation contract for a house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    function unregisterRound(address houseImpl, address roundImpl) external onlyOwner {
        delete rounds[houseImpl][roundImpl];

        emit RoundUnregistered(houseImpl, roundImpl);
    }

    /// @notice Set a metadata renderer for a contract.
    /// @param contract_ The contract address
    /// @param renderer The renderer address
    function setMetadataRenderer(address contract_, address renderer) external onlyOwner {
        renderers[contract_] = renderer;

        emit MetadataRendererSet(contract_, renderer);
    }

    /// @notice Sets the security council to a new account (`newSecurityCouncil`)
    /// @param newSecurityCouncil The new security council address
    function setSecurityCouncil(address newSecurityCouncil) external onlyOwner {
        securityCouncil = newSecurityCouncil;

        emit SecurityCouncilSet(newSecurityCouncil);
    }
}

// File: contracts/ethereum/interfaces/ITokenMetadataRenderer.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

interface ITokenMetadataRenderer {
    /// @notice Returns metadata for `tokenId` as a Base64-JSON blob
    /// @param tokenId The token ID
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

// File: contracts/ethereum/interfaces/IManager.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { ITokenMetadataRenderer } from './ITokenMetadataRenderer.sol';

/// @notice Interface for the Manager contract
interface IManager {
    /// @notice Emitted when a house implementation is registered
    /// @param houseImpl The house implementation address
    /// @param houseType The house implementation type
    event HouseRegistered(address houseImpl, bytes32 houseType);

    /// @notice Emitted when a house implementation is unregistered
    /// @param houseImpl The house implementation address
    event HouseUnregistered(address houseImpl);

    /// @notice Emitted when a round implementation is registered on a house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    /// @param roundType The round implementation type
    event RoundRegistered(address houseImpl, address roundImpl, bytes32 roundType);

    /// @notice Emitted when a round implementation is unregistered on a house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    event RoundUnregistered(address houseImpl, address roundImpl);

    /// @notice Emitted when a metadata renderer is set for a contract
    /// @param addr The contract address
    /// @param renderer The renderer address
    event MetadataRendererSet(address addr, address renderer);

    /// @notice Emitted when the security council address is set
    /// @param securityCouncil The security council address
    event SecurityCouncilSet(address securityCouncil);

    /// @notice Determine if a house implementation is registered
    /// @param houseImpl The house implementation address
    function isHouseRegistered(address houseImpl) external view returns (bool);

    /// @notice Determine if a round implementation is registered on the provided house
    /// @param houseImpl The house implementation address
    /// @param roundImpl The round implementation address
    function isRoundRegistered(address houseImpl, address roundImpl) external view returns (bool);

    /// @notice Get the metadata renderer for a contract
    /// @param contract_ The contract address
    function getMetadataRenderer(address contract_) external view returns (ITokenMetadataRenderer);

    /// @notice Get the security council address
    function getSecurityCouncil() external view returns (address);
}

// File: contracts/ethereum/lib/utils/Ownable.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { IOwnable } from '../../interfaces/IOwnable.sol';
import { Initializable } from './Initializable.sol';

/// @title Ownable
/// @notice Modified from Rohan Kulkarni's work for Nouns Builder
/// Originally Modified from OpenZeppelin Contracts v4.7.3 (access/OwnableUpgradeable.sol)
/// - Uses custom errors declared in IOwnable
/// - Adds optional two-step ownership transfer (`safeTransferOwnership` + `acceptOwnership`)
abstract contract Ownable is IOwnable, Initializable {
    /// @dev The address of the owner
    address internal _owner;

    /// @dev The address of the pending owner
    address internal _pendingOwner;

    /// @dev Ensures the caller is the owner
    modifier onlyOwner() {
        if (msg.sender != _owner) revert ONLY_OWNER();
        _;
    }

    /// @dev Ensures the caller is the pending owner
    modifier onlyPendingOwner() {
        if (msg.sender != _pendingOwner) revert ONLY_PENDING_OWNER();
        _;
    }

    /// @dev Initializes contract ownership
    /// @param initialOwner The initial owner address
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        _owner = initialOwner;

        emit OwnerUpdated(address(0), initialOwner);
    }

    /// @notice The address of the owner
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /// @notice The address of the pending owner
    function pendingOwner() public view returns (address) {
        return _pendingOwner;
    }

    /// @notice Forces an ownership transfer from the last owner
    /// @param newOwner The new owner address
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /// @notice Forces an ownership transfer from any sender
    /// @param newOwner New owner to transfer contract to
    /// @dev Ensure is called only from trusted internal code, no access control checks.
    function _transferOwnership(address newOwner) internal {
        emit OwnerUpdated(_owner, newOwner);

        _owner = newOwner;

        if (_pendingOwner != address(0)) delete _pendingOwner;
    }

    /// @notice Initiates a two-step ownership transfer
    /// @param newOwner The new owner address
    function safeTransferOwnership(address newOwner) public onlyOwner {
        _pendingOwner = newOwner;

        emit OwnerPending(_owner, newOwner);
    }

    /// @notice Accepts an ownership transfer
    function acceptOwnership() public onlyPendingOwner {
        emit OwnerUpdated(_owner, msg.sender);

        _owner = _pendingOwner;

        delete _pendingOwner;
    }

    /// @notice Cancels a pending ownership transfer
    function cancelOwnershipTransfer() public onlyOwner {
        emit OwnerCanceled(_owner, _pendingOwner);

        delete _pendingOwner;
    }
}

// File: contracts/ethereum/interfaces/IHouse.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { IERC721 } from './IERC721.sol';

/// @notice Common House interface
interface IHouse is IERC721 {
    /// @notice Thrown when the caller is not the prop house contract
    error ONLY_PROP_HOUSE();

    /// @notice Thrown when the caller does not hold the house ownership token
    error ONLY_HOUSE_OWNER();

    /// @notice Thrown when the provided value does not fit into 8 bits
    error VALUE_DOES_NOT_FIT_IN_8_BITS();

    /// @notice The house type
    function kind() external view returns (bytes32);

    /// @notice Initialize the house
    /// @param data Initialization data
    function initialize(bytes calldata data) external;

    /// @notice Returns `true` if the provided address is a valid round on the house
    /// @param round The round to validate
    function isRound(address round) external view returns (bool);

    /// @notice Create a new round
    /// @param impl The round implementation contract
    /// @param title The round title
    /// @param creator The round creator address
    function createRound(address impl, string calldata title, address creator) external returns (address);
}

// File: contracts/ethereum/interfaces/IRound.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { IERC165 } from './IERC165.sol';

/// @notice Interface that must be implemented by all round types
interface IRound is IERC165 {
    /// @notice Thrown when the caller of a guarded function is not the prop house contract
    error ONLY_PROP_HOUSE();

    /// @notice Thrown when the caller of a guarded function is not the round manager
    error ONLY_ROUND_MANAGER();

    /// @notice Thrown when the address of a provided strategy is zero
    /// @param strategy The address of the strategy
    error INVALID_STRATEGY(uint256 strategy);

    /// @notice The round type
    function kind() external view returns (bytes32);

    /// @notice Get the round title
    function title() external pure returns (string memory);

    /// @notice Initialize the round
    /// @param data The optional round data. If empty, round registration is deferred.
    function initialize(bytes calldata data) external payable;

    /// @notice The house that the round belongs to
    function house() external view returns (address);

    /// @notice The round ID
    function id() external view returns (uint256);
}

// File: contracts/ethereum/interfaces/IOwnable.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

/// @title IOwnable
/// @notice The external Ownable events, errors, and functions
interface IOwnable {
    /// @dev Thrown when an unauthorized user calls an owner function
    error ONLY_OWNER();

    /// @dev Thrown when an unauthorized user calls a pending owner function
    error ONLY_PENDING_OWNER();

    /// @notice Emitted when ownership has been updated
    /// @param prevOwner The previous owner address
    /// @param newOwner The new owner address
    event OwnerUpdated(address indexed prevOwner, address indexed newOwner);

    /// @notice Emitted when an ownership transfer is pending
    /// @param owner The current owner address
    /// @param pendingOwner The pending new owner address
    event OwnerPending(address indexed owner, address indexed pendingOwner);

    /// @notice Emitted when a pending ownership transfer has been canceled
    /// @param owner The current owner address
    /// @param canceledOwner The canceled owner address
    event OwnerCanceled(address indexed owner, address indexed canceledOwner);

    /// @notice The address of the owner
    function owner() external view returns (address);

    /// @notice The address of the pending owner
    function pendingOwner() external view returns (address);

    /// @notice Forces an ownership transfer
    /// @param newOwner The new owner address
    function transferOwnership(address newOwner) external;

    /// @notice Initiates a two-step ownership transfer
    /// @param newOwner The new owner address
    function safeTransferOwnership(address newOwner) external;

    /// @notice Accepts an ownership transfer
    function acceptOwnership() external;

    /// @notice Cancels a pending ownership transfer
    function cancelOwnershipTransfer() external;
}

// File: contracts/ethereum/lib/utils/Initializable.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

import { IInitializable } from '../../interfaces/IInitializable.sol';
import { Address } from './Address.sol';

/// @title Initializable
/// @notice Modified from Rohan Kulkarni's work for Nouns Builder
/// Originally modified from OpenZeppelin Contracts v4.7.3 (proxy/utils/Initializable.sol)
/// - Uses custom errors declared in IInitializable
abstract contract Initializable is IInitializable {
    /// @dev Indicates the contract has been initialized
    uint8 internal _initialized;

    /// @dev Indicates the contract is being initialized
    bool internal _initializing;

    /// @dev Ensures an initialization function is only called within an `initializer` or `reinitializer` function
    modifier onlyInitializing() {
        if (!_initializing) revert NOT_INITIALIZING();
        _;
    }

    /// @dev Enables initializing upgradeable contracts
    modifier initializer() {
        bool isTopLevelCall = !_initializing;

        if ((!isTopLevelCall || _initialized != 0) && (Address.isContract(address(this)) || _initialized != 1)) {
            revert ALREADY_INITIALIZED();
        }

        _initialized = 1;

        if (isTopLevelCall) {
            _initializing = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;

            emit Initialized(1);
        }
    }

    /// @dev Enables initializer versioning
    /// @param version The version to set
    modifier reinitializer(uint8 version) {
        if (_initializing || _initialized >= version) revert ALREADY_INITIALIZED();

        _initialized = version;

        _initializing = true;

        _;

        _initializing = false;

        emit Initialized(version);
    }

    /// @dev Prevents future initialization
    function _disableInitializers() internal virtual {
        if (_initializing) revert INITIALIZING();

        if (_initialized < type(uint8).max) {
            _initialized = type(uint8).max;

            emit Initialized(type(uint8).max);
        }
    }
}

// File: contracts/ethereum/interfaces/IERC721.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

/// @title IERC721
/// @notice The external ERC721 events, errors, and functions
interface IERC721 {
    /// @dev Thrown when a caller is not authorized to approve or transfer a token
    error INVALID_APPROVAL();

    /// @dev Thrown when a transfer is called with the incorrect token owner
    error INVALID_OWNER();

    /// @dev Thrown when a transfer is attempted to address(0)
    error INVALID_RECIPIENT();

    /// @dev Thrown when an existing token is called to be minted
    error ALREADY_MINTED();

    /// @dev Thrown when a non-existent token is called to be burned
    error NOT_MINTED();

    /// @dev Thrown when address(0) is incorrectly provided
    error ADDRESS_ZERO();

    /// @notice Emitted when a token is transferred from sender to recipient
    /// @param from The sender address
    /// @param to The recipient address
    /// @param tokenId The ERC-721 token id
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /// @notice Emitted when an owner approves an account to manage a token
    /// @param owner The owner address
    /// @param approved The account address
    /// @param tokenId The ERC-721 token id
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /// @notice Emitted when an owner sets an approval for a spender to manage all tokens
    /// @param owner The owner address
    /// @param operator The spender address
    /// @param approved If the approval is being set or removed
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /// @notice Emitted when the contract URI is updated
    /// @param uri The updated contract URI
    event ContractURIUpdated(string uri);

    /// @notice Contract-level metadata
    function contractURI() external view returns (string memory);

    /// @notice The number of tokens owned
    /// @param owner The owner address
    function balanceOf(address owner) external view returns (uint256);

    /// @notice The owner of a token
    /// @param tokenId The ERC-721 token id
    function ownerOf(uint256 tokenId) external view returns (address);

    /// @notice The account approved to manage a token
    /// @param tokenId The ERC-721 token id
    function getApproved(uint256 tokenId) external view returns (address);

    /// @notice If an operator is authorized to manage all of an owner's tokens
    /// @param owner The owner address
    /// @param operator The operator address
    function isApprovedForAll(address owner, address operator) external view returns (bool);

    /// @notice Authorizes an account to manage a token
    /// @param to The account address
    /// @param tokenId The ERC-721 token id
    function approve(address to, uint256 tokenId) external;

    /// @notice Authorizes an account to manage all tokens
    /// @param operator The account address
    /// @param approved If permission is being given or removed
    function setApprovalForAll(address operator, bool approved) external;

    /// @notice Safe transfers a token from sender to recipient with additional data
    /// @param from The sender address
    /// @param to The recipient address
    /// @param tokenId The ERC-721 token id
    /// @param data The additional data sent in the call to the recipient
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /// @notice Safe transfers a token from sender to recipient
    /// @param from The sender address
    /// @param to The recipient address
    /// @param tokenId The ERC-721 token id
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /// @notice Transfers a token from sender to recipient
    /// @param from The sender address
    /// @param to The recipient address
    /// @param tokenId The ERC-721 token id
    function transferFrom(address from, address to, uint256 tokenId) external;
}

// File: contracts/ethereum/interfaces/IERC165.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

/// @title IERC165
/// @notice Interface of the ERC165 standard, as defined in the https://eips.ethereum.org/EIPS/eip-165[EIP].
interface IERC165 {
    /// @dev Returns true if this contract implements the interface defined by
    /// `interfaceId`. See the corresponding
    /// https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
    /// to learn more about how these ids are created.
    /// This function call must use less than 30000 gas.
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: contracts/ethereum/interfaces/IInitializable.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

/// @title IInitializable
/// @notice The external Initializable events and errors
interface IInitializable {
    /// @dev Thrown when incorrectly initialized with address(0)
    error ADDRESS_ZERO();

    /// @dev Thrown when disabling initializers during initialization
    error INITIALIZING();

    /// @dev Thrown when calling an initialization function outside of initialization
    error NOT_INITIALIZING();

    /// @dev Thrown when reinitializing incorrectly
    error ALREADY_INITIALIZED();

    /// @notice Emitted when the contract has been initialized or reinitialized
    event Initialized(uint256 version);
}

// File: contracts/ethereum/lib/utils/Address.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.17;

/// @title Address
/// @notice Modified from Rohan Kulkarni's work for Nouns Builder
/// Originally modified from OpenZeppelin Contracts v4.7.3 (utils/Address.sol)
/// - Uses custom errors
/// - Adds util converting address to bytes32
library Address {
    /// @dev Thrown when the target of a delegatecall is not a contract
    error INVALID_TARGET();

    /// @dev Thrown when a delegatecall has failed
    error DELEGATE_CALL_FAILED();

    /// @dev If an address is a contract
    function isContract(address _account) internal view returns (bool rv) {
        assembly {
            rv := gt(extcodesize(_account), 0)
        }
    }

    /// @dev Performs a delegatecall on an address
    function functionDelegateCall(address _target, bytes memory _data) internal returns (bytes memory) {
        if (!isContract(_target)) revert INVALID_TARGET();

        (bool success, bytes memory returndata) = _target.delegatecall(_data);

        return verifyCallResult(success, returndata);
    }

    /// @dev Verifies a delegatecall was successful
    function verifyCallResult(bool _success, bytes memory _returndata) internal pure returns (bytes memory) {
        if (_success) {
            return _returndata;
        } else {
            if (_returndata.length > 0) {
                assembly {
                    let returndata_size := mload(_returndata)

                    revert(add(32, _returndata), returndata_size)
                }
            } else {
                revert DELEGATE_CALL_FAILED();
            }
        }
    }
}