// File: /Users/djagya/projects/erc20-contract/contracts/BoostCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";
import "./crowdsale/WithrawableCrowdsale.sol";
import "./crowdsale/ReferralsCrowdsale.sol";
import "./crowdsale/IndividualCrowdsale.sol";
import "./crowdsale/RoundsCrowdsale.sol";
import "./crowdsale/PostDeliveryCrowdsale.sol";
import "./crowdsale/MinMaxCrowdsale.sol";
import "./crowdsale/WhitelistCrowdsale.sol";
import "./crowdsale/TimedCrowdsale.sol";

/**
 *     ____  ____  ____  ___________
 *    / __ )/ __ \/ __ \/ ___/_  __/
 *   / __  / / / / / / /\__ \ / /
 *  / /_/ / /_/ / /_/ /___/ // /
 * /_____/\____/\____//____//_/
 */
contract BoostCrowdsale is
Ownable,
Crowdsale,
WhitelistAdminRole,
TimedCrowdsale,
WhitelistCrowdsale,
WithrawableCrowdsale,
MinMaxCrowdsale,
PostDeliveryCrowdsale,
RoundsCrowdsale,
ReferralsCrowdsale,
IndividualCrowdsale
{
    using SafeMath for uint256;

    uint constant ROUNDS = 2;
    uint256 constant ROUND_CAP = 300000 ether;
    uint256 constant INIT_RATE = 650;
    uint256 constant RATE_DECREMENT = 150;

    constructor(
        IERC20 token,
        uint256 openingTime,
        uint256 closingTime,
        address whitelister
    )
    Crowdsale(INIT_RATE, msg.sender, token)
    WhitelistCrowdsale(whitelister)
    RoundsCrowdsale(ROUNDS, ROUND_CAP, INIT_RATE, RATE_DECREMENT)
    TimedCrowdsale(openingTime, closingTime)
    public {}
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/IndividualCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";

/**
 * Track individual contributions and enforce the cap per address.
 */
contract IndividualCrowdsale is Crowdsale {
    using SafeMath for uint256;

    mapping(address => uint256) private _contributions;
    uint public contributorsCount;

    uint256 public individualCap = 50e18;

    function getContribution(address beneficiary) public view returns (uint256) {
        return _contributions[beneficiary];
    }

    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        super._updatePurchasingState(beneficiary, weiAmount);

        if (_contributions[beneficiary] == 0) {
            contributorsCount += 1;
        }

        _contributions[beneficiary] = _contributions[beneficiary].add(weiAmount);

        require(_contributions[beneficiary] < individualCap, "IndividualCrowdsale: contributions cap is reached");
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/MinMaxCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

contract MinMaxCrowdsale is Crowdsale, WhitelistAdminRole {
    using SafeMath for uint256;

    // Min-max contribution limits: 0.02 and 50
    uint256 private _minContribution = 1e17;
    uint256 private _maxContribution = 10e18;

    function getContributionLimits() public view returns (uint256, uint256) {
        return (_minContribution, _maxContribution);
    }

    function setMinContribution(uint256 min) public onlyWhitelistAdmin {
        require(min > 0, 'MinMaxCrowdsale: min is 0');
        require(_maxContribution > min, 'MinMaxCrowdsale: max is less than min');
        _minContribution = min;
    }

    function setMaxContribution(uint256 max) public onlyWhitelistAdmin {
        require(max > 0, 'MinMaxCrowdsale: max is 0');
        require(max > _minContribution, 'MinMaxCrowdsale: max is less than min');
        _maxContribution = max;
    }

    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        super._preValidatePurchase(beneficiary, weiAmount);
        require(weiAmount >= _minContribution, "MinMaxCrowdsale: weiAmount is less than allowed minimum");
        require(weiAmount <= _maxContribution, "MinMaxCrowdsale: weiAmount is bigger than allowed maximum");
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/PostDeliveryCrowdsale.sol



pragma solidity ^0.5.0;

import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./TimedCrowdsale.sol";
import "@openzeppelin/contracts/ownership/Secondary.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

/**
 * @title PostDeliveryCrowdsale
 * @dev Crowdsale that locks tokens from withdrawal until it ends.
 */
contract PostDeliveryCrowdsale is WhitelistAdminRole, TimedCrowdsale {
    using SafeMath for uint256;

    mapping(address => uint256) private _balances;
    __unstable__TokenVault private _vault;

    constructor() public {
        _vault = new __unstable__TokenVault();
    }

    /**
     * @dev Withdraw tokens only after crowdsale ends.
     * @param beneficiary Whose tokens will be withdrawn.
     */
    function withdrawTokens(address beneficiary) public {
        require(hasClosed(), "PostDeliveryCrowdsale: not closed");
        uint256 amount = _balances[beneficiary];
        require(amount > 0, "PostDeliveryCrowdsale: beneficiary is not due any tokens");

        _balances[beneficiary] = 0;
        _vault.transfer(token(), beneficiary, amount);
    }

    /**
     * @return the balance of an account.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    function getVaultAddress() public view onlyWhitelistAdmin returns (address) {
        return address(_vault);
    }

    /**
     * @dev Overrides parent by storing due balances, and delivering tokens to the vault instead of the end user. This
     * ensures that the tokens will be available by the time they are withdrawn (which may not be the case if
     * `_deliverTokens` was called later).
     * @param beneficiary Token purchaser
     * @param tokenAmount Amount of tokens purchased
     */
    function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
        _balances[beneficiary] = _balances[beneficiary].add(tokenAmount);
        _deliverTokens(address(_vault), tokenAmount);
    }
}

/**
 * @title __unstable__TokenVault
 * @dev Similar to an Escrow for tokens, this contract allows its primary account to spend its tokens as it sees fit.
 * This contract is an internal helper for PostDeliveryCrowdsale, and should not be used outside of this context.
 */
// solhint-disable-next-line contract-name-camelcase
contract __unstable__TokenVault is Secondary {
    function transfer(IERC20 token, address to, uint256 amount) public onlyPrimary {
        token.transfer(to, amount);
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/ReferralsCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

/**
 * Referrals consist of two parts:
 *
 * 1) When user -- referral -- is using someone's -- referrer's -- code,
 *  he receives a bonus of _percent on top of the purchased amounts.
 *  For example, buying 5000 tokens, will result in 500 bonus tokens, if they were bought using a referral code.
 *
 * 2) Referrer has a separate percent which also equals _percent at the start.
 *  When referrer earns more than increaseThreshold (e.g. 20ETH) from referrals, the percent increases.
 *  So, for example, when someone buys 5000 tokens using the referrer code:
 *      - referrer receives 10% of ETH value bonus converted to tokens: 5000 * 10% = 500
 *      - when reached 20ETH earnings, referrer receives 20% of ETH value: 5000 * 20% = 1000
 *
 * When all referral funds (_cap) are spent, further purchases won't earn bonuses for anyone.
 */
contract ReferralsCrowdsale is Crowdsale, WhitelistAdminRole {
    using SafeMath for uint256;

    // How much the referrer and referred receive.
    uint private _percent = 10;
    uint private _increasedPercent = 20;
    // After 20ETH referrer bonus increases
    uint256 private _increaseThreshold = 20 ether;
    uint256 private _cap;
    uint256 private _totalEarned;

    bool public referralsEnabled = false;

    struct Referral {
        address addr; // Who used the referral
        address referrer;
        uint256 earned; // How much tokens earned
        bool isActive;
    }

    struct Referrer {
        address addr; // The referral address, used by others to associate with it
        // How much % a referrer gets from referrals purchases. Referrals always get _percent
        uint percent;
        uint256 earnedTokens; // Total tokens earned
        uint256 earnedEth; // accumulated 10% from all referral purchases
        uint num; // Total referrals
        address[] addresses; // Keys for the map
        mapping(address => ReferrerRef) earnings; // Referral -> earned
    }

    struct ReferrerRef {
        address addr;
        uint256 earned;
        uint256 earnedEth;
        uint timestamp;
    }

    // States of people whose codes were used
    mapping(address => Referrer) private _referrers;
    // States of people who used someone's referral
    mapping(address => Referral) private _referrals;

    event ReferralEarned(address indexed beneficiary, address indexed from, uint256 amount);
    event ReferralActive(address indexed beneficiary, bool isActive);
    event NotEnoughReferralFunds(uint256 tried, uint256 remaining);

    function refTokensRemaining() public view returns (uint256) {
        return _cap.sub(_totalEarned);
    }

    function setReferrerPercent(address referrer, uint percent) public onlyWhitelistAdmin {
        require(percent > 0, "ReferralsCrowdsale: percent is zero");
        _referrers[referrer].percent = percent;
    }

    function setReferralsCap(uint256 cap) public onlyWhitelistAdmin {
        require(cap > 0, "ReferralsCrowdsale: cap is zero");
        require(cap > _totalEarned, "ReferralsCrowdsale: cap is less than already earned");
        _cap = cap;
    }

    function enableReferrals() public onlyWhitelistAdmin {
        referralsEnabled = true;
    }

    function disableReferrals() public onlyWhitelistAdmin {
        referralsEnabled = false;
    }

    /**
     * Referral stats: how much this used earned from using someones referral code.
     */
    function getReferralStats(address addr) public view returns (address, bool, address, uint, uint256) {
        Referral storage ref = _referrals[addr];
        uint percent = ref.isActive ? _percent : 0;

        return (ref.addr, ref.isActive, ref.referrer, percent, ref.earned);
    }

    /**
     * When buying tokens with a specified referral address, associate the buying user with it.
     * Update/init the referrer (whose code was used) stats.
     */
    function buyTokensWithReferral(address beneficiary, address referral) public payable {
        require(referralsEnabled == true, "ReferralsCrowdsale: referrals are disabled");
        require(_cap > 0, "ReferralsCrowdsale: cap is not set");
        require(referral != address(0), "ReferralsCrowdsale: referral is the zero address");
        require(referral != msg.sender, "ReferralsCrowdsale: referral can't be the sender address");

        // Activate a referral or switch the referrer address.
        Referral storage userReferral = _referrals[msg.sender];
        if (!userReferral.isActive || referral != userReferral.referrer) {
            userReferral.addr = msg.sender;
            userReferral.referrer = referral;
            userReferral.isActive = true;

            emit ReferralActive(msg.sender, userReferral.isActive);
        }

        buyTokens(beneficiary);
    }

    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        super._updatePurchasingState(beneficiary, weiAmount);

        if (!referralsEnabled) {
            return;
        }

        // User must have an active associated referral.
        Referral storage currentReferral = _referrals[msg.sender];
        if (!currentReferral.isActive) {
            return;
        }

        Referrer storage referrer = _referrers[currentReferral.referrer];
        referrer.percent = referrer.percent > 0 ? referrer.percent : _percent;

        uint256 amount = _getTokenAmount(weiAmount);
        uint256 referralBonus = amount.mul(_percent).div(100);

        uint256 referrerBonusEth = weiAmount.mul(referrer.percent).div(100);
        uint256 referrerBonus = _getTokenAmount(referrerBonusEth);

        uint256 totalBonus = referralBonus.add(referrerBonus);

        // If there's not enough referral funds remaining, proceed without giving referrals
        if (totalBonus > _cap.sub(_totalEarned)) {
            emit NotEnoughReferralFunds(totalBonus, _cap.sub(_totalEarned));
            return;
        }
        _totalEarned = _totalEarned.add(totalBonus);

        // Update referral stats
        currentReferral.earned = currentReferral.earned.add(referralBonus);

        // If current user wasn't previously added in the list of referrer's earnings, count it in
        if (referrer.earnings[msg.sender].addr == address(0)) {
            referrer.addr = currentReferral.referrer;
            referrer.addresses.push(msg.sender);
            referrer.num += 1;
        }

        // Update referrer stats and increase the bonus if user earned more than threshold (e.g. > 20ETH) from referrals
        referrer.earnedTokens = referrer.earnedTokens.add(referrerBonus);
        referrer.earnedEth = referrer.earnedEth.add(referrerBonusEth);
        if (referrer.earnedEth > _increaseThreshold) {
            referrer.percent = _increasedPercent;
        }
        _referrers[currentReferral.referrer] = referrer;

        // Track the specific referral, so referrer knows how much earned per referral
        ReferrerRef storage referrerRef = referrer.earnings[msg.sender];
        referrerRef.addr = msg.sender;
        referrerRef.earned = referrerRef.earned.add(referrerBonus);
        referrerRef.earnedEth = referrerRef.earnedEth.add(referrerBonusEth);
        referrerRef.timestamp = block.timestamp;
        _referrers[currentReferral.referrer].earnings[msg.sender] = referrerRef;

        // Transfer bonus tokens
        _processPurchase(msg.sender, referralBonus);
        emit ReferralEarned(msg.sender, currentReferral.referrer, referralBonus);

        _processPurchase(referrer.addr, referrerBonus);
        emit ReferralEarned(referrer.addr, msg.sender, referrerBonus);
    }

    /**
     * Referrer stats: stats for people who used the code.
     */
    function getReferrerStats(address referrer) public view
    returns (
        uint,
        uint,
        uint256,
        uint256,
        uint[] memory,
        address[] memory,
        uint256[] memory,
        uint256[] memory
    ) {
        Referrer storage state = _referrers[referrer];
        uint percent = state.percent > 0 ? state.percent : _percent;

        address[] memory addrs = new address[](state.num);
        uint256[] memory earnedTokens = new uint256[](state.num);
        uint256[] memory earnedEth = new uint256[](state.num);
        uint[] memory timestamps = new uint[](state.num);

        for (uint i = 0; i < state.num; i++) {
            address refAddr = state.addresses[i];
            ReferrerRef storage ref = state.earnings[refAddr];
            addrs[i] = ref.addr;
            earnedTokens[i] = ref.earned;
            earnedEth[i] = ref.earnedEth;
            timestamps[i] = ref.timestamp;
        }

        return (state.num, percent, state.earnedTokens, state.earnedEth, timestamps, addrs, earnedTokens, earnedEth);
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/RoundsCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "./TimedCrowdsale.sol";
import "@openzeppelin/contracts/ownership/Ownable.sol";
import "./WhitelistCrowdsale.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

contract RoundsCrowdsale is Crowdsale, WhitelistAdminRole, TimedCrowdsale, WhitelistCrowdsale {
    using SafeMath for uint256;

    struct Round {
        bool isOpen;
        uint n;
        uint256 rate;
        uint bonusPercent;
        uint contributors;
        uint256 raised;
        uint256 tokensLeft;
    }

    uint256 public contributionCap = 20 ether;
    uint public currentRound;

    // Min amount to buy with 550 rate per min 0.1ETH = 55 tokens, + bonus of 10%.
    uint256 constant _roundTokensLeftThreshold = 80 ether;

    uint private _roundsCount;
    mapping(uint => Round) private _rounds;
    // User -> [round -> value]
    mapping(address => mapping(uint => uint256)) private _contributions;

    event RoundOpened(uint n);
    event RoundBonusEarned(address beneficiary, uint256 amount);

    constructor(uint roundsCount, uint256 cap, uint256 initRate, uint256 rateDecrement) public {
        require(roundsCount > 0, "RoundsCrowdsale: roundsCount is 0");
        require(cap > 0, "RoundsCrowdsale: cap is 0");

        _roundsCount = roundsCount;

        uint[2] memory bonuses = [uint(10), 5];

        for (uint i = 0; i < _roundsCount; i++) {
            _rounds[i].tokensLeft = cap;
            _rounds[i].rate = initRate.sub(rateDecrement.mul(i));
            _rounds[i].bonusPercent = bonuses[i];
        }

        currentRound = 0;
        _rounds[currentRound].isOpen = super.isOpen();
    }

    function isOpen() public view returns (bool) {
        bool crowdsaleIsOpen = super.isOpen();
        // Open a previously closed first round when the crowdsale starts.
        bool roundIsOpen = _rounds[currentRound].isOpen || (crowdsaleIsOpen && currentRound == 0);
        return crowdsaleIsOpen && roundIsOpen;
    }

    function closeRound() public onlyWhitelistAdmin {
        _rounds[currentRound].isOpen = false;
        if (currentRound < _roundsCount - 1) {
            currentRound += 1;
            _rounds[currentRound].isOpen = true;
            emit RoundOpened(currentRound);
        }
    }

    function openRound(uint n) public onlyWhitelistAdmin {
        _rounds[currentRound].isOpen = false;
        _rounds[n].isOpen = true;
    }

    function rate() public view returns (uint256) {
        revert("IncreasingPriceCrowdsale: rate() called");
    }

    function getRoundsContributions(address beneficiary) public view returns (uint256[] memory) {
        uint256[] memory contributions = new uint256[](_roundsCount);
        for (uint i = 0; i < _roundsCount; i++) {
            contributions[i] = _contributions[beneficiary][i];
        }

        return contributions;
    }

    /**
     * @dev Returns the rate of tokens per wei at the present time.
     * Note that, as price _increases_ with time, the rate _decreases_.
     * @return The number of tokens a buyer gets per wei at a given time
     */
    function getCurrentRate() public view returns (uint256) {
        if (!isOpen()) {
            return 0;
        }

        return _rounds[currentRound].rate;
    }

    /**
     * @dev Overrides parent method taking into account variable rate.
     * @param weiAmount The value in wei to be converted into tokens
     * @return The number of tokens _weiAmount wei will buy at present time
     */
    function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
        uint256 currentRate = getCurrentRate();
        return currentRate.mul(weiAmount);
    }

    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal onlyWhileOpen view {
        super._preValidatePurchase(beneficiary, weiAmount);

        // First round is available only for whitelisted addresses
        if (currentRound == 0) {
            require(validateWhitelisted(beneficiary) == true, 'RoundsCrowdsale: first round is only for whitelisted addresses');
        }

        Round storage round = _rounds[currentRound];
        uint256 tokens = _getTokenAmount(weiAmount);
        uint256 bonusTokens = round.bonusPercent > 0 ? tokens.mul(round.bonusPercent).div(100) : 0;

        require(tokens.add(bonusTokens) <= round.tokensLeft, "RoundsCrowdsale: round cap exceeded");
    }

    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        super._updatePurchasingState(beneficiary, weiAmount);

        // Open the first round if the timed crowdsale started
        if (currentRound == 0 && !_rounds[currentRound].isOpen && super.isOpen()) {
            _rounds[currentRound].isOpen = true;
        }

        // Update round stats
        Round storage round = _rounds[currentRound];
        round.raised = round.raised.add(weiAmount);

        uint256 tokens = _getTokenAmount(weiAmount);

        // If current contributor wasn't in the list, count it
        if (_contributions[msg.sender][currentRound] == 0) {
            round.contributors += 1;
        }

        // Update individual stats
        _contributions[msg.sender][currentRound] = _contributions[msg.sender][currentRound].add(weiAmount);
        require(_contributions[msg.sender][currentRound] < contributionCap, "RoundsCrowdsale: individual contributions cap exceeded");

        // Send round bonus to purchaser
        uint256 bonusTokens = 0;
        if (round.bonusPercent > 0) {
            bonusTokens = tokens.mul(round.bonusPercent).div(100);
            _processPurchase(msg.sender, bonusTokens);
            emit RoundBonusEarned(msg.sender, bonusTokens);
        }

        // Close depleted round
        round.tokensLeft = round.tokensLeft.sub(tokens).sub(bonusTokens);
        if (round.tokensLeft <= _roundTokensLeftThreshold) {
            round.isOpen = false;
        }

        // If the current round is closed, open the next round (if there are any remaining)
        if (!round.isOpen && currentRound < _roundsCount - 1) {
            currentRound += 1;
            _rounds[currentRound].isOpen = true;

            emit RoundOpened(currentRound);
        }
    }

    /**
     * Rounds getter.
     */
    function getRounds() public view
    returns (
        uint[] memory,
        bool[] memory,
        uint256[] memory,
        uint[] memory,
        uint[] memory,
        uint256[] memory,
        uint256[] memory
    ) {
        uint[] memory n = new uint[](_roundsCount);
        bool[] memory openings = new bool[](_roundsCount);
        uint256[] memory rates = new uint256[](_roundsCount);
        uint[] memory bonuses = new uint[](_roundsCount);
        uint[] memory contributors = new uint[](_roundsCount);
        uint256[] memory raised = new uint256[](_roundsCount);
        uint256[] memory tokensLeft = new uint256[](_roundsCount);

        for (uint i = 0; i < _roundsCount; i++) {
            Round storage round = _rounds[i];
            n[i] = i;
            openings[i] = super.isOpen() && round.isOpen;
            rates[i] = round.rate;
            bonuses[i] = round.bonusPercent;
            contributors[i] = round.contributors;
            raised[i] = round.raised;
            tokensLeft[i] = round.tokensLeft;
        }

        return (n, openings, rates, bonuses, contributors, raised, tokensLeft);
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/TimedCrowdsale.sol



pragma solidity ^0.5.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

/**
 * @title TimedCrowdsale
 * @dev Crowdsale accepting contributions only within a time frame.
 */
contract TimedCrowdsale is Crowdsale, WhitelistAdminRole {
    using SafeMath for uint256;

    uint256 private _openingTime;
    uint256 private _closingTime;

    /**
     * Event for crowdsale extending
     * @param newClosingTime new closing time
     * @param prevClosingTime old closing time
     */
    event TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime);
    event TimedCrowdsaleClosed();

    /**
     * @dev Reverts if not in crowdsale time range.
     */
    modifier onlyWhileOpen {
        require(isOpen(), "TimedCrowdsale: not open");
        _;
    }

    /**
     * @dev Constructor, takes crowdsale opening and closing times.
     * @param openingTime Crowdsale opening time
     * @param closingTime Crowdsale closing time
     */
    constructor (uint256 openingTime, uint256 closingTime) public {
        // solhint-disable-next-line not-rely-on-time
        require(openingTime >= block.timestamp, "TimedCrowdsale: opening time is before current time");
        // solhint-disable-next-line max-line-length
        require(closingTime > openingTime, "TimedCrowdsale: opening time is not before closing time");

        _openingTime = openingTime;
        _closingTime = closingTime;
    }

    /**
     * @return the crowdsale opening time.
     */
    function openingTime() public view returns (uint256) {
        return _openingTime;
    }

    /**
     * @return the crowdsale closing time.
     */
    function closingTime() public view returns (uint256) {
        return _closingTime;
    }

    /**
     * @return true if the crowdsale is open, false otherwise.
     */
    function isOpen() public view returns (bool) {
        // solhint-disable-next-line not-rely-on-time
        return block.timestamp >= _openingTime && block.timestamp <= _closingTime;
    }

    function closeCrowdsale() public onlyWhitelistAdmin {
        require(isOpen(), "TimedCrowdsale: is not open yet");
        _closingTime = block.timestamp;
        emit TimedCrowdsaleClosed();
    }

    /**
     * @dev Checks whether the period in which the crowdsale is open has already elapsed.
     * @return Whether crowdsale period has elapsed
     */
    function hasClosed() public view returns (bool) {
        // solhint-disable-next-line not-rely-on-time
        return block.timestamp > _closingTime;
    }

    /**
     * @dev Extend parent behavior requiring to be within contributing period.
     * @param beneficiary Token purchaser
     * @param weiAmount Amount of wei contributed
     */
    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal onlyWhileOpen view {
        super._preValidatePurchase(beneficiary, weiAmount);
    }

    /**
     * @dev Extend crowdsale.
     * @param newClosingTime Crowdsale closing time
     */
    function _extendTime(uint256 newClosingTime) internal {
        require(!hasClosed(), "TimedCrowdsale: already closed");
        // solhint-disable-next-line max-line-length
        require(newClosingTime > _closingTime, "TimedCrowdsale: new closing time is before current closing time");

        emit TimedCrowdsaleExtended(_closingTime, newClosingTime);
        _closingTime = newClosingTime;
    }
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/WhitelistCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";

contract WhitelistCrowdsale is Crowdsale, WhitelistAdminRole {
    address public whitelister;
    bool public whitelistEnabled = true;

    constructor(address _whitelister) public {
        whitelister = _whitelister;
    }

    function isWhitelisted(address _address) external view returns (bool) {
    	return IWhitelister(whitelister).whitelisted(_address);
    }

    function validateWhitelisted(address beneficiary) internal view returns (bool) {
        return !whitelistEnabled || this.isWhitelisted(beneficiary);
    }

    function toggleWhitelistEnabled() external onlyWhitelistAdmin {
        whitelistEnabled = !whitelistEnabled;
    }
}

interface IWhitelister {
    function whitelisted(address _address) external view returns (bool);
}

// File: /Users/djagya/projects/erc20-contract/contracts/crowdsale/WithrawableCrowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";
import "./TimedCrowdsale.sol";

contract WithrawableCrowdsale is Crowdsale, WhitelistAdminRole, TimedCrowdsale {
    address public burnAddr = 0x000000000000000000000000000000000000dEaD;

    function _forwardFunds() internal {
        // Do nothing and keep funds in the Crowdsale.
    }

    function withdrawETH(uint256 amount) public onlyWhitelistAdmin {
        msg.sender.transfer(amount);
    }

    function burnUnsold(uint256 amount) public onlyWhitelistAdmin {
        require(hasClosed(), "WithrawableCrowdsale: crowdsale is not closed yet");
        require(amount <= token().balanceOf(address(this)), "WithrawableCrowdsale: amount is bigger than tokens left");
        token().transfer(burnAddr, amount);
    }
}

// File: @openzeppelin/contracts/GSN/Context.sol



pragma solidity ^0.5.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 GSN 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.
 */
contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/access/Roles.sol



pragma solidity ^0.5.0;

/**
 * @title Roles
 * @dev Library for managing addresses assigned to a Role.
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev Give an account access to this role.
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev Remove an account's access to this role.
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev Check if an account has this role.
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

// File: @openzeppelin/contracts/access/roles/WhitelistAdminRole.sol



pragma solidity ^0.5.0;

import "../../GSN/Context.sol";
import "../Roles.sol";

/**
 * @title WhitelistAdminRole
 * @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts.
 */
contract WhitelistAdminRole is Context {
    using Roles for Roles.Role;

    event WhitelistAdminAdded(address indexed account);
    event WhitelistAdminRemoved(address indexed account);

    Roles.Role private _whitelistAdmins;

    constructor () internal {
        _addWhitelistAdmin(_msgSender());
    }

    modifier onlyWhitelistAdmin() {
        require(isWhitelistAdmin(_msgSender()), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
        _;
    }

    function isWhitelistAdmin(address account) public view returns (bool) {
        return _whitelistAdmins.has(account);
    }

    function addWhitelistAdmin(address account) public onlyWhitelistAdmin {
        _addWhitelistAdmin(account);
    }

    function renounceWhitelistAdmin() public {
        _removeWhitelistAdmin(_msgSender());
    }

    function _addWhitelistAdmin(address account) internal {
        _whitelistAdmins.add(account);
        emit WhitelistAdminAdded(account);
    }

    function _removeWhitelistAdmin(address account) internal {
        _whitelistAdmins.remove(account);
        emit WhitelistAdminRemoved(account);
    }
}

// File: @openzeppelin/contracts/crowdsale/Crowdsale.sol



pragma solidity ^0.5.0;

import "../GSN/Context.sol";
import "../token/ERC20/IERC20.sol";
import "../math/SafeMath.sol";
import "../token/ERC20/SafeERC20.sol";
import "../utils/ReentrancyGuard.sol";

/**
 * @title Crowdsale
 * @dev Crowdsale is a base contract for managing a token crowdsale,
 * allowing investors to purchase tokens with ether. This contract implements
 * such functionality in its most fundamental form and can be extended to provide additional
 * functionality and/or custom behavior.
 * The external interface represents the basic interface for purchasing tokens, and conforms
 * the base architecture for crowdsales. It is *not* intended to be modified / overridden.
 * The internal interface conforms the extensible and modifiable surface of crowdsales. Override
 * the methods to add functionality. Consider using 'super' where appropriate to concatenate
 * behavior.
 */
contract Crowdsale is Context, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // The token being sold
    IERC20 private _token;

    // Address where funds are collected
    address payable private _wallet;

    // How many token units a buyer gets per wei.
    // The rate is the conversion between wei and the smallest and indivisible token unit.
    // So, if you are using a rate of 1 with a ERC20Detailed token with 3 decimals called TOK
    // 1 wei will give you 1 unit, or 0.001 TOK.
    uint256 private _rate;

    // Amount of wei raised
    uint256 private _weiRaised;

    /**
     * Event for token purchase logging
     * @param purchaser who paid for the tokens
     * @param beneficiary who got the tokens
     * @param value weis paid for purchase
     * @param amount amount of tokens purchased
     */
    event TokensPurchased(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);

    /**
     * @param rate Number of token units a buyer gets per wei
     * @dev The rate is the conversion between wei and the smallest and indivisible
     * token unit. So, if you are using a rate of 1 with a ERC20Detailed token
     * with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
     * @param wallet Address where collected funds will be forwarded to
     * @param token Address of the token being sold
     */
    constructor (uint256 rate, address payable wallet, IERC20 token) public {
        require(rate > 0, "Crowdsale: rate is 0");
        require(wallet != address(0), "Crowdsale: wallet is the zero address");
        require(address(token) != address(0), "Crowdsale: token is the zero address");

        _rate = rate;
        _wallet = wallet;
        _token = token;
    }

    /**
     * @dev fallback function ***DO NOT OVERRIDE***
     * Note that other contracts will transfer funds with a base gas stipend
     * of 2300, which is not enough to call buyTokens. Consider calling
     * buyTokens directly when purchasing tokens from a contract.
     */
    function () external payable {
        buyTokens(_msgSender());
    }

    /**
     * @return the token being sold.
     */
    function token() public view returns (IERC20) {
        return _token;
    }

    /**
     * @return the address where funds are collected.
     */
    function wallet() public view returns (address payable) {
        return _wallet;
    }

    /**
     * @return the number of token units a buyer gets per wei.
     */
    function rate() public view returns (uint256) {
        return _rate;
    }

    /**
     * @return the amount of wei raised.
     */
    function weiRaised() public view returns (uint256) {
        return _weiRaised;
    }

    /**
     * @dev low level token purchase ***DO NOT OVERRIDE***
     * This function has a non-reentrancy guard, so it shouldn't be called by
     * another `nonReentrant` function.
     * @param beneficiary Recipient of the token purchase
     */
    function buyTokens(address beneficiary) public nonReentrant payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // update state
        _weiRaised = _weiRaised.add(weiAmount);

        _processPurchase(beneficiary, tokens);
        emit TokensPurchased(_msgSender(), beneficiary, weiAmount, tokens);

        _updatePurchasingState(beneficiary, weiAmount);

        _forwardFunds();
        _postValidatePurchase(beneficiary, weiAmount);
    }

    /**
     * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
     * Use `super` in contracts that inherit from Crowdsale to extend their validations.
     * Example from CappedCrowdsale.sol's _preValidatePurchase method:
     *     super._preValidatePurchase(beneficiary, weiAmount);
     *     require(weiRaised().add(weiAmount) <= cap);
     * @param beneficiary Address performing the token purchase
     * @param weiAmount Value in wei involved in the purchase
     */
    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
        require(weiAmount != 0, "Crowdsale: weiAmount is 0");
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
    }

    /**
     * @dev Validation of an executed purchase. Observe state and use revert statements to undo rollback when valid
     * conditions are not met.
     * @param beneficiary Address performing the token purchase
     * @param weiAmount Value in wei involved in the purchase
     */
    function _postValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        // solhint-disable-previous-line no-empty-blocks
    }

    /**
     * @dev Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends
     * its tokens.
     * @param beneficiary Address performing the token purchase
     * @param tokenAmount Number of tokens to be emitted
     */
    function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
        _token.safeTransfer(beneficiary, tokenAmount);
    }

    /**
     * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
     * tokens.
     * @param beneficiary Address receiving the tokens
     * @param tokenAmount Number of tokens to be purchased
     */
    function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
        _deliverTokens(beneficiary, tokenAmount);
    }

    /**
     * @dev Override for extensions that require an internal state to check for validity (current user contributions,
     * etc.)
     * @param beneficiary Address receiving the tokens
     * @param weiAmount Value in wei involved in the purchase
     */
    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        // solhint-disable-previous-line no-empty-blocks
    }

    /**
     * @dev Override to extend the way in which ether is converted to tokens.
     * @param weiAmount Value in wei to be converted into tokens
     * @return Number of tokens that can be purchased with the specified _weiAmount
     */
    function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
        return weiAmount.mul(_rate);
    }

    /**
     * @dev Determines how ETH is stored/forwarded on purchases.
     */
    function _forwardFunds() internal {
        _wallet.transfer(msg.value);
    }
}

// File: @openzeppelin/contracts/math/SafeMath.sol



pragma solidity ^0.5.0;

/**
 * @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.
     *
     * _Available since v2.4.0._
     */
    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.
     *
     * _Available since v2.4.0._
     */
    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.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: @openzeppelin/contracts/ownership/Ownable.sol



pragma solidity ^0.5.0;

import "../GSN/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.
 *
 * 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.
 */
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 () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

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

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

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return _msgSender() == _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 onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = 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 onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: @openzeppelin/contracts/ownership/Secondary.sol



pragma solidity ^0.5.0;

import "../GSN/Context.sol";
/**
 * @dev A Secondary contract can only be used by its primary account (the one that created it).
 */
contract Secondary is Context {
    address private _primary;

    /**
     * @dev Emitted when the primary contract changes.
     */
    event PrimaryTransferred(
        address recipient
    );

    /**
     * @dev Sets the primary account to the one that is creating the Secondary contract.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _primary = msgSender;
        emit PrimaryTransferred(msgSender);
    }

    /**
     * @dev Reverts if called from any account other than the primary.
     */
    modifier onlyPrimary() {
        require(_msgSender() == _primary, "Secondary: caller is not the primary account");
        _;
    }

    /**
     * @return the address of the primary.
     */
    function primary() public view returns (address) {
        return _primary;
    }

    /**
     * @dev Transfers contract to a new primary.
     * @param recipient The address of new primary.
     */
    function transferPrimary(address recipient) public onlyPrimary {
        require(recipient != address(0), "Secondary: new primary is the zero address");
        _primary = recipient;
        emit PrimaryTransferred(recipient);
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
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);
}

// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol



pragma solidity ^0.5.0;

import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.
        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC20: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// File: @openzeppelin/contracts/utils/Address.sol



pragma solidity ^0.5.5;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following 
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }

    /**
     * @dev Converts an `address` into `address payable`. Note that this is
     * simply a type cast: the actual underlying value is not changed.
     *
     * _Available since v2.4.0._
     */
    function toPayable(address account) internal pure returns (address payable) {
        return address(uint160(account));
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     *
     * _Available since v2.4.0._
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-call-value
        (bool success, ) = recipient.call.value(amount)("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
}

// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol



pragma solidity ^0.5.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * _Since v2.5.0:_ this module is now much more gas efficient, given net gas
 * metering changes introduced in the Istanbul hardfork.
 */
contract ReentrancyGuard {
    bool private _notEntered;

    constructor () internal {
        // Storing an initial non-zero value makes deployment a bit more
        // expensive, but in exchange the refund on every call to nonReentrant
        // will be lower in amount. Since refunds are capped to a percetange of
        // the total transaction's gas, it is best to keep them low in cases
        // like this one, to increase the likelihood of the full refund coming
        // into effect.
        _notEntered = true;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_notEntered, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _notEntered = false;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _notEntered = true;
    }
}