contract ManagedCADChainlinkRateOracle is ICADConversionOracle, WhitelistedRole {
    using SafeMath for uint256;

    event ManagedRateUpdated(uint256 value, uint256 timestamp);

    // exchange rate stored as an integer
    uint256 public _USDToCADRate;

    // specifies how many decimal places have been converted into integer
    uint256 public _granularity;

    // specifies the time the exchange was last updated
    uint256 public _timestamp;

    // Chainlink price feed for Dai/Eth pair
    AggregatorV3Interface public daiEthPriceFeed;

    // Chainlink price feed for USDC/Eth pair
    AggregatorV3Interface public usdcEthPriceFeed;

    // Chainlink price feed for USDT/Eth pair
    AggregatorV3Interface public usdtEthPriceFeed;


    constructor(
        uint256 value,
        uint256 granularity,

        address daiEthAggregatorAddress,
        address usdcEthAggregatorAddress,
        address usdtEthAggregatorAddress
    ) public {
        _USDToCADRate = value;
        _granularity = granularity;
        _timestamp = block.timestamp;

        daiEthPriceFeed = AggregatorV3Interface(daiEthAggregatorAddress);
        usdcEthPriceFeed = AggregatorV3Interface(usdcEthAggregatorAddress);
        usdtEthPriceFeed = AggregatorV3Interface(usdtEthAggregatorAddress);

        _addWhitelisted(msg.sender);
    }

    /**
     * @notice admin can update the exchange rate
     * @param value         the new exchange rate
     * @param granularity   number of decimal places the exchange value is accurate to
     * @return  true if success
     */
    function updateManagedRate(uint256 value, uint256 granularity) external onlyWhitelisted returns (bool) {
        require(value > 0, "Exchange rate cannot be zero");
        require(granularity > 0, "Granularity cannot be zero");

        _USDToCADRate = value;
        _granularity = granularity;
        _timestamp = block.timestamp;

        emit ManagedRateUpdated(value, granularity);
        return true;
    }

     /**
     * @notice return the current managed values
     * @return latest USD to CAD exchange rate, granularity, and timestamp
     */
    function getManagedRate() external view returns (uint256, uint256, uint256) {
        return (_USDToCADRate, _granularity, _timestamp);
    }

    /**
     * @notice convert USD amount to CAD amount
     * @param amount     amount of USD in 18 decimal places
     * @return amount of CAD in 18 decimal places
     */
    function usdToCad(uint256 amount) public view virtual override returns (uint256) {
        return amount.mul(_USDToCADRate).div(10 ** _granularity);
    }

    /**
     * @notice convert Dai amount to CAD amount
     * @param amount     amount of dai in 18 decimal places
     * @return amount of CAD in 18 decimal places
     */
    function daiToCad(uint256 amount) external view virtual override returns (uint256) {
        (, int256 daiEthPrice, , uint256 daiEthTimeStamp,) = daiEthPriceFeed.latestRoundData();
        require(daiEthTimeStamp > 0, "Dai Chainlink Oracle data temporarily incomplete");
        require(daiEthPrice > 0, "Invalid Chainlink Oracle Dai price");

        (, int256 usdcEthPrice, , uint256 usdcEthTimeStamp,) = usdcEthPriceFeed.latestRoundData();
        require(usdcEthTimeStamp > 0, "USDC conversion Chainlink Oracle data temporarily incomplete");
        require(usdcEthPrice > 0, "Invalid Chainlink Oracle USDC conversion price");

        return amount.mul(_USDToCADRate).mul(uint256(daiEthPrice)).div(uint256(usdcEthPrice)).div(10 ** _granularity);
    }

    /**
     * @notice convert USDC amount to CAD amount
     * @param amount     amount of USDC in 6 decimal places
     * @return amount of CAD in 18 decimal places
     */
    function usdcToCad(uint256 amount) external view virtual override returns (uint256) {
        // USDT has 6 decimals
        return usdToCad(amount.mul(1e12));
    }

    /**
     * @notice convert USDT amount to CAD amount
     * @param amount     amount of USDT in 6 decimal places
     * @return amount of CAD in 18 decimal places
     */
    function usdtToCad(uint256 amount) external view virtual override returns (uint256) {
        (, int256 usdtEthPrice, , uint256 usdtEthTimeStamp,) = usdtEthPriceFeed.latestRoundData();
        require(usdtEthTimeStamp > 0, "USDT Chainlink Oracle data temporarily incomplete");
        require(usdtEthPrice > 0, "Invalid Chainlink Oracle USDT price");

        (, int256 usdcEthPrice, , uint256 usdcEthTimeStamp,) = usdcEthPriceFeed.latestRoundData();
        require(usdcEthTimeStamp > 0, "USDC conversion Chainlink Oracle data temporarily incomplete");
        require(usdcEthPrice > 0, "Invalid Chainlink Oracle USDC conversion price");

        // USDT has 6 decimals
        return amount.mul(1e12).mul(_USDToCADRate).mul(uint256(usdtEthPrice)).div(uint256(usdcEthPrice)).div(10 ** _granularity);
    }


    /**
     * @notice convert CAD amount to USD amount
     * @param amount     amount of CAD in 18 decimal places
     * @return amount of USD in 18 decimal places
     */
    function cadToUsd(uint256 amount) public view virtual override returns (uint256) {
        return amount.mul(10 ** _granularity).div(_USDToCADRate);
    }

    /**
     * @notice convert CAD amount to Dai amount
     * @param amount     amount of CAD in 18 decimal places
     * @return amount of Dai in 18 decimal places
     */
    function cadToDai(uint256 amount) external view virtual override returns (uint256) {
        (, int256 daiEthPrice, , uint256 daiEthTimeStamp,) = daiEthPriceFeed.latestRoundData();
        require(daiEthTimeStamp > 0, "Dai Chainlink Oracle data temporarily incomplete");
        require(daiEthPrice > 0, "Invalid Chainlink Oracle Dai price");

        (, int256 usdcEthPrice, , uint256 usdcEthTimeStamp,) = usdcEthPriceFeed.latestRoundData();
        require(usdcEthTimeStamp > 0, "USDC conversion Chainlink Oracle data temporarily incomplete");
        require(usdcEthPrice > 0, "Invalid Chainlink Oracle USDC conversion price");

        return amount.mul(10 ** _granularity).mul(uint256(usdcEthPrice)).div(uint256(daiEthPrice)).div(_USDToCADRate);
    }

    /**
     * @notice convert CAD amount to USDC amount
     * @param amount     amount of CAD in 18 decimal places
     * @return amount of USDC in 6 decimal places
     */
    function cadToUsdc(uint256 amount) external view virtual override returns (uint256) {
        return cadToUsd(amount).div(1e12);
    }

    /**
     * @notice convert CAD amount to USDT amount
     * @param amount     amount of CAD in 18 decimal places
     * @return amount of USDT in 6 decimal places
     */
    function cadToUsdt(uint256 amount) external view virtual override returns (uint256) {
        (, int256 usdtEthPrice, , uint256 usdtEthTimeStamp,) = usdtEthPriceFeed.latestRoundData();
        require(usdtEthTimeStamp > 0, "USDT Chainlink Oracle data temporarily incomplete");
        require(usdtEthPrice > 0, "Invalid Chainlink Oracle USDT price");

        (, int256 usdcEthPrice, , uint256 usdcEthTimeStamp,) = usdcEthPriceFeed.latestRoundData();
        require(usdcEthTimeStamp > 0, "USDC conversion Chainlink Oracle data temporarily incomplete");
        require(usdcEthPrice > 0, "Invalid Chainlink Oracle USDC conversion price");

        return amount.mul(10 ** _granularity).mul(uint256(usdcEthPrice)).div(uint256(usdtEthPrice)).div(_USDToCADRate).div(1e12);
    }
}
/**
 * @title ManagedCADChainlinkRateOracle
 * @notice Provides a USD/CAD rate source managed by admin, and Chainlink powered DAI/USDC/USDT conversion rates.
 *  USDC is treated as always 1 USD, and used as anchor to calculate Dai and USDT rates
 */
function usdToCad(uint256 amount) public view virtual override returns (uint256) {
    return amount.mul(_USDToCADRate).div(10 ** _granularity);
}

/**
 * @notice convert USD amount to CAD amount
 * @param amount     amount of USD in 18 decimal places
 * @return amount of CAD in 18 decimal places
 */