contract Exchange is Pausable, RBAC {
    using SafeMath for uint256;

    string constant ROLE_ORACLE = "oracle";

    ERC20 baseToken;
    ERC20 dai;  // 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
    address public oracle;
    R.Rational public ethRate;
    R.Rational public daiRate;

    event TradeETH(uint256 amountETH, uint256 amountBaseToken);
    event TradeDAI(uint256 amountDAI, uint256 amountBaseToken);
    event RateUpdatedETH(uint256 n, uint256 d);
    event RateUpdatedDAI(uint256 n, uint256 d);
    event OracleSet(address oracle);

    /**
     * Constructor for exchange.
     *
     * @param _baseToken Address of the token to exchange for
     * @param _dai Address of DAI token
     * @param _oracle Address of oracle tasked with periodically setting market rates
     * @param _ethRateN Numerator of the ETH to token exchange rate
     * @param _ethRateD Denominator of the ETH to token exchange rate
     * @param _daiRateN Numerator of the DAI to token exchange rate
     * @param _daiRateD Denominator of the DAI to token exchange rate
     */
    function Exchange(
        address _baseToken,
        address _dai,
        address _oracle,
        uint256 _ethRateN,
        uint256 _ethRateD,
        uint256 _daiRateN,
        uint256 _daiRateD
    ) public {
        baseToken = ERC20(_baseToken);
        dai = ERC20(_dai);
        addRole(_oracle, ROLE_ORACLE);
        oracle = _oracle;
        ethRate = R.Rational(_ethRateN, _ethRateD);
        daiRate = R.Rational(_daiRateN, _daiRateD);
    }

    /**
     * Trades ETH for tokens at ethRate.
     *
     * @param expectedAmountBaseToken Amount of tokens expected to receive.
     * This prevents front-running race conditions from occurring when ethRate
     * is updated.
     */
    function tradeETH(uint256 expectedAmountBaseToken) public whenNotPaused() payable {
        uint256 amountBaseToken = calculateAmountForETH(msg.value);
        require(amountBaseToken == expectedAmountBaseToken);
        require(baseToken.transfer(msg.sender, amountBaseToken));
        emit TradeETH(msg.value, amountBaseToken);
    }

    /**
     * Trades DAI for tokens at daiRate. User must first approve DAI to be
     * transferred by Exchange.
     *
     * @param amountDAI Amount of DAI to exchange
     * @param expectedAmountBaseToken Amount of tokens expected to receive.
     * This prevents front-running race conditions from occurring when daiRate
     * is updated.
     */
    function tradeDAI(uint256 amountDAI, uint256 expectedAmountBaseToken) public whenNotPaused() {
        uint256 amountBaseToken = calculateAmountForDAI(amountDAI);
        require(amountBaseToken == expectedAmountBaseToken);
        require(dai.transferFrom(msg.sender, address(this), amountDAI));
        require(baseToken.transfer(msg.sender, amountBaseToken));
        emit TradeDAI(amountDAI, amountBaseToken);
    }

    /**
     * Calculates exchange amount for ETH to token.
     *
     * @param amountETH Amount of ETH, in base units
     */
    function calculateAmountForETH(uint256 amountETH) public view returns (uint256) {
        return Rationals.rmul(amountETH, ethRate);
    }

    /**
     * Calculates exchange amount for DAI to token.
     *
     * @param amountDAI Amount of DAI, in base units
     */
    function calculateAmountForDAI(uint256 amountDAI) public view returns (uint256) {
        return Rationals.rmul(amountDAI, daiRate);
    }

    /**
     * Sets the exchange rate from ETH to token.
     *
     * @param n Numerator for ethRate
     * @param d Denominator for ethRate
     */
    function setETHRate(uint256 n, uint256 d) external onlyRole(ROLE_ORACLE) {
        ethRate = R.Rational(n, d);
        emit RateUpdatedETH(n, d);
    }

    /**
     * Sets the exchange rate from ETH to token.
     *
     * @param n Numerator for daiRate
     * @param d Denominator for daiRate
     */
    function setDAIRate(uint256 n, uint256 d) external onlyRole(ROLE_ORACLE) {
        daiRate = R.Rational(n, d);
        emit RateUpdatedDAI(n, d);
    }

    /**
     * Recovers DAI, leftover tokens, or other.
     *
     * @param token Address of token to withdraw
     * @param amount Amount of tokens to withdraw
     */
    function withdrawERC20s(address token, uint256 amount) external onlyOwner {
        ERC20 erc20 = ERC20(token);
        require(erc20.transfer(owner, amount));
    }

    /**
     * Changes the oracle.
     *
     * @param _oracle Address of new oracle
     */
    function setOracle(address _oracle) external onlyOwner {
        removeRole(oracle, ROLE_ORACLE);
        addRole(_oracle, ROLE_ORACLE);
        oracle = _oracle;
        emit OracleSet(_oracle);
    }

    /// @notice Owner: Withdraw Ether
    function withdrawEther() external onlyOwner {
        owner.transfer(address(this).balance);
    }

}
/**
 * @title Atomic exchange to facilitate swaps from ETH or DAI to a token.
 * Users an oracle bot to update market prices.
 */
function tradeETH(uint256 expectedAmountBaseToken) public whenNotPaused() payable {
    uint256 amountBaseToken = calculateAmountForETH(msg.value);
    require(amountBaseToken == expectedAmountBaseToken);
    require(baseToken.transfer(msg.sender, amountBaseToken));
    emit TradeETH(msg.value, amountBaseToken);
}

/**
 * Trades ETH for tokens at ethRate.
 *
 * @param expectedAmountBaseToken Amount of tokens expected to receive.
 * This prevents front-running race conditions from occurring when ethRate
 * is updated.
 */