creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "contracts/PriceOracle/PriceOracle.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.7;\n\nimport { Ownable2Step } from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\nimport { AggregatorV3Interface } from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\n\ncontract PriceOracle is Ownable2Step {\n\n    /// @dev Configuration used to return the USD price for the associated cToken asset and base unit needed for formatting\n    struct TokenConfig {\n        // Decimals of the underlying asset (e.g. 18 for ETH)\n        uint8 underlyingAssetDecimals;\n        // Address of the feed used to retrieve the asset's price\n        address priceFeed;\n    }\n\n    /// @dev Type used to load the contract with configs during deployment\n    /// There should be 1 LoadConfig object for each supported asset, passed in the constructor.\n    struct LoadConfig {\n        // Decimals of the underlying asset (e.g. 18 for ETH)\n        uint8 underlyingAssetDecimals;\n        // Address of the Compound Token\n        address cToken;\n        // Address of the feed used to retrieve the asset's price\n        address priceFeed;\n    }\n\n    /// @dev Mapping of cToken address to TokenConfig used to maintain the supported assets\n    mapping (address => TokenConfig) tokenConfigs;\n\n    /// @notice The event emitted when a new asset is added to the mapping\n    /// @param cToken cToken address that the config was added for\n    /// @param underlyingAssetDecimals Decimals of the underlying asset\n    /// @param priceFeed Address of the feed used to retrieve the asset's price\n    event PriceOracleAssetAdded(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed);\n\n    /// @notice The event emitted when the price feed for an existing config is updated\n    /// @param cToken cToken address that the config was updated for\n    /// @param oldPriceFeed The existing price feed address configured in the token config\n    /// @param newPriceFeed The new price feed address the token config is being updated to\n    event PriceOracleAssetPriceFeedUpdated(address indexed cToken, address oldPriceFeed, address newPriceFeed);\n\n    /// @notice The event emitted when an asset is removed to the mapping\n    /// @param cToken cToken address that the config was removed for\n    /// @param underlyingAssetDecimals Decimals of the underlying asset in the removed config.\n    /// @param priceFeed Address price feed set in the removed config\n    event PriceOracleAssetRemoved(address indexed cToken, uint8 underlyingAssetDecimals, address priceFeed);\n\n    /// @notice The max decimals value allowed for price feed\n    uint8 internal constant MAX_DECIMALS = 72;\n\n    /// @notice The number of digits the price is scaled to before adjusted by the base units\n    uint8 internal constant PRICE_SCALE = 36;\n\n    /// @notice cToken address for config not provided\n    error MissingCTokenAddress();\n\n    /// @notice UnderlyingAssetDecimals is missing or set to value 0\n    error InvalidUnderlyingAssetDecimals();\n\n    /// @notice Sum of price feed's decimals and underlyingAssetDecimals is greater than MAX_DECIMALS\n    error FormattingDecimalsTooHigh(uint16 decimals);\n\n    /// @notice Price feed missing or duplicated\n    /// @param priceFeed Price feed address provided\n    error InvalidPriceFeed(address priceFeed);\n\n    /// @notice Config already exists\n    /// @param cToken cToken address provided\n    error DuplicateConfig(address cToken);\n\n    /// @notice Config does not exist in the mapping\n    /// @param cToken cToken address provided\n    error ConfigNotFound(address cToken);\n\n    /// @notice Same price feed as the existing one was provided when updating the price feed config\n    /// @param cToken cToken address that the price feed update is for\n    /// @param existingPriceFeed Price feed address set in the existing config\n    /// @param newPriceFeed Price feed address provided to update to\n    error UnchangedPriceFeed(address cToken, address existingPriceFeed, address newPriceFeed);\n\n    /**\n     * @notice Construct a Price Oracle contract for a set of token configurations\n     * @param configs The token configurations that define which price feed and base unit to use for each asset\n     */\n    constructor(LoadConfig[] memory configs) {\n        // Populate token config mapping \n        for (uint i = 0; i < configs.length; i++) {\n            LoadConfig memory config = configs[i];\n            addConfig(config);\n        }\n    }\n\n    /**\n     * @notice Get the underlying price of a cToken, in the format expected by the Comptroller.\n     * @dev Comptroller needs prices in the format: ${raw price} * 1e(36 - feedDecimals - underlyingAssetDecimals)\n     *      'underlyingAssetDecimals' is the decimals of the underlying asset for the corresponding cToken.\n     *      'feedDecimals' is a value supplied by the price feed that represent the number of decimals the price feed reports with.\n     *      For example, the underlyingAssetDecimals of ETH is 18 and its price feed provides 8 decimal places\n     *      We must scale the price such as: ${raw price} * 1e(36 - 8 - 18).\n     * @param cToken The cToken address for price retrieval\n     * @return Price denominated in USD for the given cToken address, in the format expected by the Comptroller.\n     */\n    function getUnderlyingPrice(address cToken)\n        external\n        view\n        returns (uint256)\n    {\n        TokenConfig memory config = tokenConfigs[cToken];\n        if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);\n        // Initialize the aggregator to read the price from\n        AggregatorV3Interface priceFeed = AggregatorV3Interface(config.priceFeed);\n        // Retrieve decimals from feed for formatting\n        uint8 feedDecimals = priceFeed.decimals();\n        // Retrieve price from feed\n        (\n            /* uint80 roundID */,\n            int256 answer,\n            /*uint256 startedAt*/,\n            /*uint256 updatedAt*/,\n            /*uint80 answeredInRound*/\n        ) = priceFeed.latestRoundData();\n        // Invalid price returned by feed. Comptroller expects 0 price on error.\n        if (answer <= 0) return 0;\n        uint256 price = uint256(answer);\n\n        // Number of decimals determines whether the price needs to be multiplied or divided for scaling\n        // Handle the 2 scenarios separately to ensure a non-fractional scale value\n        if (feedDecimals + config.underlyingAssetDecimals <= PRICE_SCALE) {\n            // Decimals is always >=0 so the scale max value is 1e36 here and not at risk of overflowing\n            uint256 scale = 10 ** (PRICE_SCALE - feedDecimals - config.underlyingAssetDecimals);\n            return price * scale;\n        } else {\n            // Sum of feed and underlying asset decimals is capped at 72 by earlier validation so scale max value is 1e36 here\n            // and not at risk of overflowing\n            uint256 scale = 10 ** (feedDecimals + config.underlyingAssetDecimals - PRICE_SCALE);\n            return price / scale;\n        }\n    }\n\n    /**\n     * @notice Retrieves the token config for a particular cToken address\n     * @param cToken The cToken address that the token config should be returned for\n     */\n    function getConfig(address cToken) external view returns (TokenConfig memory) {\n        TokenConfig memory config = tokenConfigs[cToken];\n        // Check if config exists for cToken\n        if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);\n        return config;\n    }\n\n    /**\n     * @notice Adds a new token config to enable the contract to provide prices for a new asset\n     * @param config Token config struct that contains the info for a new asset configuration\n     */\n    function addConfig(LoadConfig memory config) public onlyOwner {\n        _validateTokenConfig(config);\n        TokenConfig memory tokenConfig = TokenConfig(config.underlyingAssetDecimals, config.priceFeed);\n        tokenConfigs[config.cToken] = tokenConfig;\n        emit PriceOracleAssetAdded(config.cToken, config.underlyingAssetDecimals, config.priceFeed);\n    }\n\n    /**\n     * @notice Updates the price feed in the token config for a particular cToken\n     * @param cToken The cToken address that the config needs to be updated for\n     * @param priceFeed The address of the new price feed the config needs to be updated to\n     */\n    function updateConfigPriceFeed(address cToken, address priceFeed) external onlyOwner {\n        TokenConfig memory config = tokenConfigs[cToken];\n        // Check if config exists for cToken\n        if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);\n        // Validate price feed\n        if (priceFeed == address(0)) revert InvalidPriceFeed(priceFeed);\n        // Check if existing price feed is the same as the new one sent\n        if (config.priceFeed == priceFeed) revert UnchangedPriceFeed(cToken, config.priceFeed, priceFeed);\n        // Validate the decimals for the price feed since it could differ from the previous one\n        _validateDecimals(priceFeed, config.underlyingAssetDecimals);\n\n        address existingPriceFeed = config.priceFeed;\n        tokenConfigs[cToken].priceFeed = priceFeed;\n        emit PriceOracleAssetPriceFeedUpdated(cToken, existingPriceFeed, priceFeed);\n    }\n\n    /**\n     * @notice Removes a token config to no longer support the asset\n     * @param cToken The cToken address that the token config should be removed for\n     */\n    function removeConfig(address cToken) external onlyOwner {\n        TokenConfig memory config = tokenConfigs[cToken];\n        // Check if config exists for cToken\n        if (config.priceFeed == address(0)) revert ConfigNotFound(cToken);\n\n        delete tokenConfigs[cToken];\n        emit PriceOracleAssetRemoved(cToken, config.underlyingAssetDecimals, config.priceFeed);\n    }\n\n    /**\n     * @notice Validates a token config and confirms one for the cToken does not already exist in mapping\n     * @dev All fields are required\n     * @param config TokenConfig struct that needs to be validated\n     */\n    function _validateTokenConfig(LoadConfig memory config) internal view {\n        if (config.cToken == address(0)) revert MissingCTokenAddress();\n        if (config.priceFeed == address(0)) revert InvalidPriceFeed(config.priceFeed);\n        // Check if duplicate configs were submitted for the same cToken\n        if (tokenConfigs[config.cToken].priceFeed != address(0)) revert DuplicateConfig(config.cToken);\n        _validateDecimals(config.priceFeed, config.underlyingAssetDecimals);\n    }\n\n    /**\n     * @notice Validates the combination of price feed decimals and the underlying asset decimals in the config\n     * @param priceFeed The price feed the decimals need to be validated for\n     * @param underlyingAssetDecimals The underlying asset decimals set in the config\n     */\n     function _validateDecimals(address priceFeed, uint8 underlyingAssetDecimals) internal view {\n        // Check underlyingAssetDecimals exists and non-zero\n        if (underlyingAssetDecimals == 0) revert InvalidUnderlyingAssetDecimals();\n        AggregatorV3Interface aggregator = AggregatorV3Interface(priceFeed);\n        // Retrieve decimals from feed for formatting\n        uint8 feedDecimals = aggregator.decimals();\n        // Cap the sum of feed decimals and underlying asset decimals to avoid overflows when formatting prices.\n        if (feedDecimals + underlyingAssetDecimals > MAX_DECIMALS) revert FormattingDecimalsTooHigh(feedDecimals + underlyingAssetDecimals);\n     }\n}\n"
      },
      "@openzeppelin/contracts/access/Ownable2Step.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./Ownable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n    address private _pendingOwner;\n\n    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Returns the address of the pending owner.\n     */\n    function pendingOwner() public view virtual returns (address) {\n        return _pendingOwner;\n    }\n\n    /**\n     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual override onlyOwner {\n        _pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner(), newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual override {\n        delete _pendingOwner;\n        super._transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev The new owner accepts the ownership transfer.\n     */\n    function acceptOwnership() public virtual {\n        address sender = _msgSender();\n        require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n        _transferOwnership(sender);\n    }\n}\n"
      },
      "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n  function decimals() external view returns (uint8);\n\n  function description() external view returns (string memory);\n\n  function version() external view returns (uint256);\n\n  function getRoundData(\n    uint80 _roundId\n  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n\n  function latestRoundData()\n    external\n    view\n    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n}\n"
      },
      "@openzeppelin/contracts/access/Ownable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the deployer as the initial owner.\n     */\n    constructor() {\n        _transferOwnership(_msgSender());\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
      },
      "@openzeppelin/contracts/utils/Context.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {}
    }
  }}