// File: contracts/BridgedOracleArtBlocks.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./PriceOracle.sol";
import "./ChainlinkOracle/ChainlinkOracleArtBlocks.sol";

contract BridgedOracleArtBlocks is PriceOracle {
    address public admin;
    ChainlinkOracleArtBlocks public chainlink;
    PriceOracle public uniswap;

    mapping(address => bool) chainlinkAssets;
    mapping(address => bool) chainlinkETHs;

    constructor(address _chainlink, address _uniswap) public {
        admin = msg.sender;
        chainlink = ChainlinkOracleArtBlocks(_chainlink);
        uniswap = PriceOracle(_uniswap);
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin may call");
        _;
    }

    function getUnderlyingPriceView(address cToken)
        public
        view
        override
        returns (uint256)
    {
        if (chainlinkAssets[cToken]) {
            if (chainlinkETHs[cToken]) {
                return chainlink.getUnderlyingPriceETH();
            } else {
                return chainlink.getUnderlyingPriceView(cToken);
            }
        } else {
            return uniswap.getUnderlyingPriceView(cToken);
        }
    }

    function getUnderlyingPrice(address cToken)
        public
        override
        returns (uint256)
    {
        if (chainlinkAssets[cToken]) {
            if (chainlinkETHs[cToken]) {
                return chainlink.getUnderlyingPriceETH();
            } else {
                return chainlink.getUnderlyingPrice(cToken);
            }
        } else {
            return uniswap.getUnderlyingPrice(cToken);
        }
    }

    function registerChainlinkAsset(
        address token,
        string calldata symbol,
        address feed,
        uint256 decimal,
        bool base,
        bool isETH
    ) public onlyAdmin {
        require(!chainlinkAssets[token], "Already registered");
        chainlinkAssets[token] = true;
        chainlinkETHs[token] = isETH;
        chainlink.setFeed(token, symbol, feed, decimal, base);
    }

    function deregisterChainlinkAsset(address token) public onlyAdmin {
        require(chainlinkAssets[token], "Already deregistered");
        chainlinkAssets[token] = false;
    }

    function getChainlinkAsset(address token) public view returns (bool) {
        return chainlinkAssets[token];
    }

    function releaseChainlink(address newAdmin) public onlyAdmin {
        chainlink.setAdmin(newAdmin);
    }

    function updateChainlink(address newChainlink) public onlyAdmin {
        chainlink = ChainlinkOracleArtBlocks(newChainlink);
        chainlink.setAdmin(address(this));
    }

    function updateUniswap(address newUniswap) public onlyAdmin {
        uniswap = PriceOracle(newUniswap);
    }

    function compareStrings(string memory a, string memory b)
        internal
        pure
        returns (bool)
    {
        return (keccak256(abi.encodePacked((a))) ==
            keccak256(abi.encodePacked((b))));
    }
}

// File: contracts/ChainlinkOracle/AggregatorV2V3Interface.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

/**
 * @title The V2 & V3 Aggregator Interface
 * @notice Solidity V0.5 does not allow interfaces to inherit from other
 * interfaces so this contract is a combination of v0.5 AggregatorInterface.sol
 * and v0.5 AggregatorV3Interface.sol.
 */
interface AggregatorV2V3Interface {
  //
  // V2 Interface:
  //
  function latestAnswer() external view returns (int256);
  function latestTimestamp() external view returns (uint256);
  function latestRound() external view returns (uint256);
  function getAnswer(uint256 roundId) external view returns (int256);
  function getTimestamp(uint256 roundId) external view returns (uint256);

  event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp);
  event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);

  //
  // V3 Interface:
  //
  function decimals() external view returns (uint8);
  function description() external view returns (string memory);
  function version() external view returns (uint256);

  // getRoundData and latestRoundData should both raise "No data present"
  // if they do not have data to report, instead of returning unset values
  // which could be misinterpreted as actual reported values.
  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/ChainlinkOracle/ChainlinkOracleArtBlocks.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "../PriceOracle.sol";
import "../SafeMath.sol";
import "./AggregatorV2V3Interface.sol";

interface IToken {
    function symbol() external view returns (string memory);

    function decimals() external view returns (uint256);
}

interface ICToken {
    function symbol() external view returns (string memory);

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

contract ChainlinkOracleArtBlocks is PriceOracle {
    using SafeMath for uint256;
    address public admin;

    mapping(address => uint256) internal prices;
    mapping(bytes32 => AggregatorV2V3Interface) internal feeds;
    mapping(bytes32 => uint256) internal decimals;
    mapping(bytes32 => bool) internal bases;
    mapping(address => string) internal symbols;
    event PricePosted(
        address asset,
        uint256 previousPriceMantissa,
        uint256 requestedPriceMantissa,
        uint256 newPriceMantissa
    );
    event NewAdmin(address oldAdmin, address newAdmin);
    event FeedSet(address feed, string symbol);

    constructor() public {
        admin = msg.sender;
    }

    function getUnderlyingPriceETH() public view returns (uint) {
        return getChainlinkPrice(getFeed("ETH"));
    }

    function getUnderlyingPriceView(address cToken)
        public
        view
        override
        returns (uint256)
    {
        return getPrice(cToken);
    }

    function getUnderlyingPrice(address cToken)
        public
        override
        returns (uint256)
    {
        return getUnderlyingPriceView(cToken);
    }

    function getPrice(address cToken) public view returns (uint256 price) {
        address token = ICToken(cToken).underlying();
        string memory symbol = symbols[cToken];
        if (bytes(symbol).length == 0) {
            symbol = IToken(token).symbol();
        }

        if (prices[token] != 0) {
            price = prices[token];
        } else {
            price = getChainlinkPrice(getFeed(symbol));
        }

        if (!getBase(symbol)) {
            AggregatorV2V3Interface baseFeed = getFeed("ETH");
            price = getChainlinkPrice(baseFeed).mul(price).div(10**18);
        }

        uint256 tokenDecimals = 18;
        if (decimals[keccak256(abi.encodePacked(symbol))] > 0) {
            tokenDecimals = uint256(IToken(token).decimals());
        }

        uint256 decimalDelta = uint256(36)
            .sub(tokenDecimals)
            .sub(getDecimal(symbol));
        // Ensure that we don't multiply the result by 0
        if (decimalDelta > 0) {
            return price.mul(10**decimalDelta);
        } else {
            return price;
        }
    }

    function getChainlinkPrice(AggregatorV2V3Interface feed)
        internal
        view
        returns (uint256)
    {
        // Chainlink USD-denominated feeds store answers at 8 decimals
        uint256 decimalDelta = uint256(18).sub(feed.decimals());
        // Ensure that we don't multiply the result by 0
        if (decimalDelta > 0) {
            return uint256(feed.latestAnswer()).mul(10**decimalDelta);
        } else {
            return uint256(feed.latestAnswer());
        }
    }

    function setUnderlyingPrice(address cToken, uint256 underlyingPriceMantissa)
        external
        onlyAdmin
    {
        address asset = ICToken(cToken).underlying();
        emit PricePosted(
            asset,
            prices[asset],
            underlyingPriceMantissa,
            underlyingPriceMantissa
        );
        prices[asset] = underlyingPriceMantissa;
    }

    function setDirectPrice(address asset, uint256 price) external onlyAdmin {
        emit PricePosted(asset, prices[asset], price, price);
        prices[asset] = price;
    }

    function setFeed(
        address cToken,
        string calldata symbol,
        address feed,
        uint256 decimal,
        bool base
    ) external onlyAdmin {
        require(
            feed != address(0) && feed != address(this),
            "invalid feed address"
        );
        emit FeedSet(feed, symbol);
        feeds[keccak256(abi.encodePacked(symbol))] = AggregatorV2V3Interface(
            feed
        );
        decimals[keccak256(abi.encodePacked(symbol))] = decimal;
        bases[keccak256(abi.encodePacked(symbol))] = base;
        symbols[cToken] = symbol;
    }

    function getFeed(string memory symbol)
        public
        view
        returns (AggregatorV2V3Interface)
    {
        return feeds[keccak256(abi.encodePacked(symbol))];
    }

    function getBase(string memory symbol) public view returns (bool) {
        return bases[keccak256(abi.encodePacked(symbol))];
    }

    function getDecimal(string memory symbol) public view returns (uint256) {
        return decimals[keccak256(abi.encodePacked(symbol))];
    }

    function assetPrices(address asset) external view returns (uint256) {
        return prices[asset];
    }

    function compareStrings(string memory a, string memory b)
        internal
        pure
        returns (bool)
    {
        return (keccak256(abi.encodePacked((a))) ==
            keccak256(abi.encodePacked((b))));
    }

    function setAdmin(address newAdmin) external onlyAdmin {
        address oldAdmin = admin;
        admin = newAdmin;

        emit NewAdmin(oldAdmin, newAdmin);
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin may call");
        _;
    }
}

// File: contracts/PriceOracle.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

abstract contract PriceOracle {
    /// @notice Indicator that this is a PriceOracle contract (for inspection)
    bool public constant isPriceOracle = true;

    /**
      * @notice Get the underlying price of a cToken asset
      * @param cToken The cToken to get the underlying price of
      * @return The underlying asset price mantissa (scaled by 1e18).
      *  Zero means the price is unavailable.
      */
    function getUnderlyingPrice(address cToken) external virtual returns (uint);

    function getUnderlyingPriceView(address cToken) public view virtual returns (uint);
}

// File: contracts/SafeMath.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol
// Subject to the MIT license.

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting with custom message on overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, errorMessage);

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on underflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot underflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction underflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on underflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot underflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, errorMessage);

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers.
     * Reverts on division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers.
     * Reverts with custom message on division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}