// File: contracts/cash/external/openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "contracts/cash/external/openzeppelin/contracts/utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
  address private _owner;

  event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
  );

  /**
   * @dev Initializes the contract setting the deployer as the initial owner.
   */
  constructor() {
    _transferOwnership(_msgSender());
  }

  /**
   * @dev Returns the address of the current owner.
   */
  function owner() public view virtual returns (address) {
    return _owner;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(owner() == _msgSender(), "Ownable: caller is not the owner");
    _;
  }

  /**
   * @dev Leaves the contract without owner. It will not be possible to call
   * `onlyOwner` functions anymore. Can only be called by the current owner.
   *
   * NOTE: Renouncing ownership will leave the contract without an owner,
   * thereby removing any functionality that is only available to the owner.
   */
  function renounceOwnership() public virtual onlyOwner {
    _transferOwnership(address(0));
  }

  /**
   * @dev Transfers ownership of the contract to a new account (`newOwner`).
   * Can only be called by the current owner.
   */
  function transferOwnership(address newOwner) public virtual onlyOwner {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    _transferOwnership(newOwner);
  }

  /**
   * @dev Transfers ownership of the contract to a new account (`newOwner`).
   * Internal function without access restriction.
   */
  function _transferOwnership(address newOwner) internal virtual {
    address oldOwner = _owner;
    _owner = newOwner;
    emit OwnershipTransferred(oldOwner, newOwner);
  }
}

// File: contracts/cash/external/openzeppelin/contracts/access/Ownable2Step.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.0;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}
// File: contracts/cash/external/openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
  function _msgSender() internal view virtual returns (address) {
    return msg.sender;
  }

  function _msgData() internal view virtual returns (bytes calldata) {
    return msg.data;
  }
}

// File: contracts/lending/HelperInterfaces.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;

/// @notice Helper interface for checking fTokens.
interface ICTokenLike {
  function isCToken() external returns (bool);

  function underlying() external view returns (address);
}

/// @notice Helper interface for interacting with underlying assets
///         that are ERC20 compliant
interface IERC20Like {
  function decimals() external view returns (uint8);
}

// File: contracts/lending/chainlink/AggregatorV3Interface.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  )
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}

// File: contracts/lending/fTokenOracle/IFTokenOracle.sol



/**SPDX-License-Identifier: MIT

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐

 */

pragma solidity 0.8.16;

import {IERC20Like} from "contracts/lending/HelperInterfaces.sol";

/// @notice Helper interface for standardizing common calls to
///         fTokens
interface IFToken {
  function isCToken() external returns (bool);

  function exchangeRateCurrent() external returns (uint256);

  function underlying() external view returns (address);
}

interface IFTokenOracle {
  function description() external returns (string memory);

  function decimals() external returns (uint8);

  function getLatestPrice() external returns (uint256);

  function getPrice() external returns (uint256);

  function setMaxOracleTimeDelay(uint256 newMaxOracleTimeDelay) external;

  /**
   * @dev Event for when oracle max time delay is set
   *
   * @param oldMaxOracleTimeDelay The old max time delay for the chainlink oracle
   * @param newMaxOracleTimeDelay The new max time delay for the chainlink oracle
   */
  event MaxOracleTimeDelaySet(
    uint256 oldMaxOracleTimeDelay,
    uint256 newMaxOracleTimeDelay
  );

  // Errors
  error UnsupportedPriceFeed();
  error CorruptedChainlinkResponse();
  error StalePrice();
  error InvalidPrice();
  error InvalidFToken();
}

// File: contracts/lending/fTokenOracle/fTokenOracle.sol



/**SPDX-License-Identifier: MIT

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐

 */

pragma solidity 0.8.16;

import "contracts/lending/fTokenOracle/IFTokenOracle.sol";
import "contracts/lending/chainlink/AggregatorV3Interface.sol";
import "contracts/cash/external/openzeppelin/contracts/access/Ownable2Step.sol";

contract FTokenOracle is IFTokenOracle, Ownable2Step {
  /// @notice fToken price given by this Oracle
  IFToken public immutable fToken;
  /// @notice Chainlink price feed
  AggregatorV3Interface public immutable priceFeed;
  /// @notice Description of oracle in `fToken/Underlying Token Symbol` format
  string public override description;
  /// @notice Max delay when getting non-stale price
  uint256 public maxOracleTimeDelay;
  /// @notice Decimals of price returned by `getPrice ` and `getLatestPrice`
  uint8 public constant override decimals = 18;
  /// @notice Scale factor that the fToken exchange rate is scaled by from its
  /// underlying. Eg Initial exhcange rate for an 18 decimal underlying token
  /// is 18 + 8 = 26 decimals
  uint8 public constant FTOKEN_EXCHANGE_RATE_SCALE_FACTOR = 8;
  /// @notice Factor to scale by to return price in `decimals` of precision
  uint256 public immutable scaleFactor;

  constructor(
    address _fToken,
    address _priceFeed,
    string memory _description,
    uint256 _maxOracleTimeDelay
  ) {
    require(IFToken(_fToken).isCToken());

    fToken = IFToken(_fToken);
    priceFeed = AggregatorV3Interface(_priceFeed);
    description = _description;
    maxOracleTimeDelay = _maxOracleTimeDelay;

    // Set Scale Factor
    // The smallest number of decimals for an fTokenExchangeRate is 14.
    // The smallest number of decimals for a Chainlink price feed is 8 (USD).
    // Thus, we will always be scaling DOWN to 18 decimals.
    uint8 priceFeedDecimals = priceFeed.decimals();
    if (priceFeedDecimals < 8) {
      revert UnsupportedPriceFeed();
    }
    address fTokenUnderlying = fToken.underlying();
    uint8 fTokenUnderlyingDecimals = IERC20Like(fTokenUnderlying).decimals();
    if (fTokenUnderlying == address(0) || fTokenUnderlyingDecimals == 0) {
      revert InvalidFToken();
    }
    scaleFactor =
      10 **
        (priceFeedDecimals -
          FTOKEN_EXCHANGE_RATE_SCALE_FACTOR +
          fTokenUnderlyingDecimals);
  }

  /**
   * @notice Get a non-stale price of fToken denominated in asset specified by
   *         underlyingpriceFeed
   *
   * @dev Reverts when price <= 0 or when price is stale
   */
  function getLatestPrice() external override returns (uint256) {
    // Get underlying price and check for staleness and validity
    (
      uint80 roundId,
      int256 answer,
      ,
      uint256 updatedAt,
      uint80 answeredInRound
    ) = priceFeed.latestRoundData();
    if (
      answer < 0 ||
      roundId != answeredInRound ||
      roundId == 0 ||
      updatedAt == 0 ||
      updatedAt > block.timestamp
    ) {
      revert CorruptedChainlinkResponse();
    }

    if (answer == 0) {
      revert InvalidPrice();
    }

    if (updatedAt < block.timestamp - maxOracleTimeDelay) {
      revert StalePrice();
    }

    // Get fToken exchange Rate
    uint256 exchangeRate = fToken.exchangeRateCurrent();

    // Return fToken price denominated in underlying of price feed
    return _scale(uint256(answer), exchangeRate);
  }

  /**
   * @notice Get the price of fToken denominated in asset specified by
   *         underlyingpriceFeed
   *
   * @dev Function does not check for Chainlin oracle staleness. Use
   *      `getLatestPrice` for a non-stale price
   * @dev Reverts when price <= 0
   */
  function getPrice() external override returns (uint256) {
    // Get underlying price and check for validity
    (, int256 answer, , , ) = priceFeed.latestRoundData();

    if (answer <= 0) {
      revert InvalidPrice();
    }

    // Get fToken ExchangeRate
    uint256 exchangeRate = fToken.exchangeRateCurrent();

    // Return fToken price denominated in underlying of price feed
    return _scale(uint256(answer), exchangeRate);
  }

  /**
   * @notice Return the scaled price of fToken in 18 decimal units of
   *         priceFeed
   *
   * @param price Price of fTokenUnderlying denominated in priceFeed token
   * @param fTokenExchangeRate Exchange Rate of fToken
   */
  function _scale(
    uint256 price,
    uint256 fTokenExchangeRate
  ) private view returns (uint256) {
    return (price * fTokenExchangeRate) / scaleFactor;
  }

  /**
   * @notice Set time delay for oracle price feed
   *
   * @param newMaxOracleTimeDelay New max oracle time delay
   */
  function setMaxOracleTimeDelay(
    uint256 newMaxOracleTimeDelay
  ) external override onlyOwner {
    uint256 oldMaxOracleTimeDelay = maxOracleTimeDelay;
    maxOracleTimeDelay = newMaxOracleTimeDelay;
    emit MaxOracleTimeDelaySet(oldMaxOracleTimeDelay, newMaxOracleTimeDelay);
  }
}