// File: contracts/X2Reader.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "./libraries/math/SafeMath.sol";
import "./interfaces/IX2Market.sol";
import "./interfaces/IX2ETHFactory.sol";
import "./interfaces/IX2PriceFeed.sol";
import "./interfaces/IX2Token.sol";
import "./interfaces/IX2RewardDistributor.sol";
import "./libraries/token/IERC20.sol";

contract X2Reader {
    using SafeMath for uint256;
    uint256 constant PRECISION = 1e20;

    function getMarkets(address _factory, uint256 _start) public view returns (uint256[] memory, address[] memory) {
        uint256 length = IX2ETHFactory(_factory).marketsLength();
        uint256[] memory leverages = new uint256[](length - _start);
        address[] memory priceFeeds = new address[](length - _start);
        for (uint256 i = _start; i < length; i++) {
            address market = IX2ETHFactory(_factory).markets(i);
            uint256 multiplierBasisPoints = IX2Market(market).multiplierBasisPoints();
            address priceFeed = IX2Market(market).priceFeed();
            leverages[i - _start] = multiplierBasisPoints;
            priceFeeds[i - _start] = priceFeed;
        }

        return (leverages, priceFeeds);
    }

    function getFees(address[] memory _markets) public view returns (uint256) {
        uint256 totalFees = 0;
        for (uint256 i = 0; i < _markets.length; i++) {
            address market = _markets[i];
            totalFees = totalFees.add(IX2Market(market).feeReserve());
        }

        return totalFees;
    }

    function distributeFees(address[] memory _markets) public returns (uint256) {
        for (uint256 i = 0; i < _markets.length; i++) {
            address market = _markets[i];
            IX2Market(market).distributeFees();
        }
    }

    function getAppFees(address[] memory _markets, address app) public view returns (uint256) {
        uint256 totalFees = 0;
        for (uint256 i = 0; i < _markets.length; i++) {
            address market = _markets[i];
            totalFees = totalFees.add(IX2Market(market).appFees(app));
        }

        return totalFees;
    }

    function distributeAppFees(address[] memory _markets, address app) public returns (uint256) {
        for (uint256 i = 0; i < _markets.length; i++) {
            address market = _markets[i];
            IX2Market(market).distributeAppFees(app);
        }
    }

    function getMarketInfo(address _market) public view returns (uint256[] memory) {
        address bullToken = IX2Market(_market).bullToken();
        address bearToken = IX2Market(_market).bearToken();
        (uint256 bullFunding, uint256 bearFunding) = IX2Market(_market).getFunding();

        uint256[] memory amounts = new uint256[](5);

        amounts[0] = IX2Market(_market).latestPrice();
        amounts[1] = IERC20(bullToken).totalSupply();
        amounts[2] = IERC20(bearToken).totalSupply();
        amounts[3] = bullFunding;
        amounts[4] = bearFunding;

        return amounts;
    }

    function getRewards(address _token, address _account) public view returns (uint256[] memory) {
        uint256[] memory amounts = new uint256[](6);
        address distributor = IX2Token(_token).distributor();
        if (distributor == address(0)) {
            return amounts;
        }

        amounts[0] = IX2RewardDistributor(distributor).tokensPerInterval(_token);

        uint256 rewards = IX2Token(_token).getReward(_account);
        uint256 pendingRewards = IX2RewardDistributor(distributor).getDistributionAmount(_token);
        uint256 balance = IX2Token(_token)._balanceOf(_account);
        uint256 cumulativeRewardPerToken = IX2Token(_token).cumulativeRewardPerToken();

        uint256 supply = IX2Token(_token)._totalSupply();
        if (supply > 0) {
            amounts[1] = rewards.add(pendingRewards.mul(balance).div(supply));
        }
        amounts[2] = IERC20(_token).totalSupply();

        amounts[3] = balance;
        amounts[4] = cumulativeRewardPerToken;
        amounts[5] = PRECISION;

        return amounts;
    }

    function getBalanceInfo(address _market, address _account) public view returns (uint256[] memory) {
        address bullToken = IX2Market(_market).bullToken();
        address bearToken = IX2Market(_market).bearToken();

        uint256[] memory amounts = new uint256[](9);

        amounts[0] = _account.balance;
        amounts[1] = IERC20(bullToken).balanceOf(_account);
        amounts[2] = IERC20(bearToken).balanceOf(_account);
        amounts[3] = IX2Token(bullToken).lastBoughtAt(_account);
        amounts[4] = IX2Token(bearToken).lastBoughtAt(_account);
        amounts[5] = IX2Token(bullToken).getPendingProfit(_account);
        amounts[6] = IX2Token(bearToken).getPendingProfit(_account);
        amounts[7] = IX2Token(bullToken).costOf(_account);
        amounts[8] = IX2Token(bearToken).costOf(_account);

        return amounts;
    }
}

// File: contracts/libraries/math/SafeMath.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

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

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    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 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) {
        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;
    }
}

// File: contracts/interfaces/IX2Market.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

interface IX2Market {
    function feeReserve() external view returns (uint256);
    function appFees(address app) external view returns (uint256);
    function distributeFees() external returns (uint256);
    function distributeAppFees(address app) external returns (uint256);
    function priceFeed() external view returns (address);
    function multiplierBasisPoints() external view returns (uint256);
    function bullToken() external view returns (address);
    function bearToken() external view returns (address);
    function latestPrice() external view returns (uint256);
    function lastPrice() external view returns (uint256);
    function getFunding() external view returns (uint256, uint256);
    function getDivisor(address token) external view returns (uint256);
    function getDivisors(uint256 _lastPrice, uint256 _nextPrice) external view returns (uint256, uint256);
    function setAppFee(uint256 feeBasisPoints) external;
    function setFunding(uint256 divisor) external;
    function cachedBullDivisor() external view returns (uint128);
    function cachedBearDivisor() external view returns (uint128);
}

// File: contracts/interfaces/IX2ETHFactory.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

interface IX2ETHFactory {
    function marketsLength() external view returns (uint256);
    function markets(uint256 index) external view returns (address);
    function feeReceiver() external view returns (address);
    function interestReceiver() external view returns (address);
    function setAppFee(address market, uint256 appFeeBasisPoints) external;
    function createMarket(
        address _priceFeed,
        uint256 _multiplierBasisPoints,
        uint256 _maxProfitBasisPoints,
        uint256 _fundingDivisor,
        uint256 _appFeeBasisPoints
    ) external returns (address, address, address);
}

// File: contracts/interfaces/IX2PriceFeed.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

interface IX2PriceFeed {
    function description() external view returns (string memory);
    function aggregator() external view returns (address);
    function latestAnswer() external view returns (int256);
    function latestRound() external view returns (uint80);
    function getRoundData(uint80 roundId) external view returns (uint80, int256, uint256, uint256, uint80);
}

// File: contracts/interfaces/IX2Token.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

interface IX2Token {
    function cumulativeRewardPerToken() external view returns (uint256);
    function lastBoughtAt(address account) external view returns (uint256);
    function getPendingProfit(address account) external view returns (uint256);
    function distributor() external view returns (address);
    function rewardToken() external view returns (address);
    function _totalSupply() external view returns (uint256);
    function _balanceOf(address account) external view returns (uint256);
    function market() external view returns (address);
    function getDivisor() external view returns (uint256);
    function getReward(address account) external view returns (uint256);
    function costOf(address account) external view returns (uint256);
    function mint(address account, uint256 amount, uint256 divisor) external;
    function burn(address account, uint256 amount, bool distribute) external returns (uint256);
    function setDistributor(address _distributor, address _rewardToken) external;
    function setInfo(string memory name, string memory symbol) external;
}

// File: contracts/interfaces/IX2RewardDistributor.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

interface IX2RewardDistributor {
    function getDistributionAmount(address receiver) external view returns (uint256);
    function tokensPerInterval(address receiver) external view returns (uint256);
    function lastDistributionTime(address receiver) external view returns (uint256);
}

// File: contracts/libraries/token/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}