// File: EarlyBirdV2.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;

// VokenTB (TeraByte) EarlyBird-Sale
//
// More info:
//   https://voken.io
//
// Contact us:
//   support@voken.io


import "LibSafeMath.sol";
import "LibIERC20.sol";
import "LibIVesting.sol";
import "LibIVokenTB.sol";
import "LibAuthPause.sol";
import "EbWithVestingPermille.sol";


interface IUniswapV2Router02 {
    function WETH() external pure returns (address);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
}


/**
 * @dev EarlyBird-Sale v2
 */
contract EarlyBirdSaleV2 is AuthPause, IVesting, WithVestingPermille {
    using SafeMath for uint256;

    uint256 private immutable VOKEN_ISSUE_MAX = 21_000_000e9;  // 21 million for early-birds
    uint256 private immutable VOKEN_ISSUE_MID = 10_500_000e9;
    uint256 private immutable WEI_PAYMENT_MAX = 5.0 ether;
    uint256 private immutable WEI_PAYMENT_MID = 3.0 ether;
    uint256 private immutable WEI_PAYMENT_MIN = 0.1 ether;
    uint256 private immutable USD_PRICE_START = 0.5e6;  // $ 0.5 USD
    uint256 private immutable USD_PRICE_DISTA = 0.4e6;  // $ 0.4 USD = 0.9 - 0.5
    uint256 private _vokenIssued;
    uint256 private _vokenRandom;

    IUniswapV2Router02 private immutable UniswapV2Router02 = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    IVokenTB private immutable VOKEN_TB = IVokenTB(0x1234567a022acaa848E7D6bC351d075dBfa76Dd4);
    IERC20 private immutable DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);

    struct Account {
        uint256 issued;
        uint256 bonuses;
        uint256 referred;
        uint256 rewards;
    }

    mapping (address => Account) private _accounts;

    event Payment(address indexed account, uint256 daiAmount, uint256 issued, uint256 random);
    event Reward(address indexed account, address indexed referrer, uint256 amount);
    

    constructor () {
        _vokenIssued = 344506165000000;
        _vokenRandom = 19157979970000;
    }

    receive()
        external
        payable
    {
        _swap();
    }

    function swap()
        external
        payable
    {
        _swap();
    }

    function status()
        public
        view
        returns (
            uint256 vokenCap,
            uint256 vokenTotalSupply,

            uint256 vokenIssued,
            uint256 vokenRandom,
            uint256 etherUSD,
            uint256 vokenUSD,
            uint256 weiMin,
            uint256 weiMax
        )
    {
        vokenCap = VOKEN_TB.cap();
        vokenTotalSupply = VOKEN_TB.totalSupply();

        vokenIssued = _vokenIssued;
        vokenRandom = _vokenRandom;
        etherUSD = etherUSDPrice();
        vokenUSD = vokenUSDPrice();

        weiMin = WEI_PAYMENT_MIN;
        weiMax = _vokenIssued < VOKEN_ISSUE_MID ? WEI_PAYMENT_MAX : WEI_PAYMENT_MID;
    }

    function getAccountStatus(address account)
        public
        view
        returns (
            uint256 issued,
            uint256 bonuses,
            uint256 referred,
            uint256 rewards,

            uint256 etherBalance,
            uint256 vokenBalance,

            uint160 voken,
            address referrer,
            uint160 referrerVoken
        )
    {
        issued = _accounts[account].issued;
        bonuses = _accounts[account].bonuses;
        referred = _accounts[account].referred;
        rewards = _accounts[account].referred;

        etherBalance = account.balance;
        vokenBalance = VOKEN_TB.balanceOf(account);
        
        voken = VOKEN_TB.address2voken(account);
        referrer = VOKEN_TB.referrer(account);

        if (referrer != address(0)) {
            referrerVoken = VOKEN_TB.address2voken(referrer);
        }
    }

    function vestingOf(address account)
        public
        override
        view
        returns (uint256 vesting)
    {
        vesting = vesting.add(_getVestingAmountForIssued(_accounts[account].issued));
        vesting = vesting.add(_getVestingAmountForBonuses(_accounts[account].bonuses));
        vesting = vesting.add(_getVestingAmountForRewards(_accounts[account].rewards));
    }

    function vokenUSDPrice()
        public
        view
        returns (uint256)
    {
        return USD_PRICE_START.add(USD_PRICE_DISTA.mul(_vokenIssued).div(VOKEN_ISSUE_MAX));
    }
    
    function daiTransfer(address to, uint256 amount)
        external
        onlyAgent
    {
        DAI.transfer(to, amount);
    }

    function _swap()
        private
        onlyNotPaused
    {
        require(msg.value >= WEI_PAYMENT_MIN, "Insufficient ether");
        require(_vokenIssued < VOKEN_ISSUE_MAX, "Early-Bird sale completed");
        require(_accounts[msg.sender].issued == 0, "Caller is already an early-bird");

        uint256 weiPayment = msg.value;
        uint256 weiPaymentMax = _vokenIssued < VOKEN_ISSUE_MID ? WEI_PAYMENT_MAX : WEI_PAYMENT_MID;

        // Limit the Payment and Refund (if needed)
        if (weiPayment > weiPaymentMax)
        {
            msg.sender.transfer(weiPayment.sub(weiPaymentMax));
            weiPayment = weiPaymentMax;
        }

        uint256 daiAmount = _swapExactETH2DAI(weiPayment);
        uint256 vokenIssued = daiAmount.div(1e3).div(vokenUSDPrice());
        uint256 vokenRandom;

        // Voken Bonus & Ether Rewards
        address payable referrer = VOKEN_TB.referrer(msg.sender);
        if (referrer != address(0))
        {
            // Reffer
            _accounts[referrer].referred = _accounts[referrer].referred.add(daiAmount);

            // Voken Random: 1% - 10%
            vokenRandom = vokenIssued.mul(uint256(blockhash(block.number - 1)).mod(10).add(1)).div(100);
            emit Reward(msg.sender, referrer, vokenRandom);

            _vokenRandom = _vokenRandom.add(vokenRandom.mul(2));
            _accounts[msg.sender].bonuses = _accounts[msg.sender].bonuses.add(vokenRandom);
            _accounts[referrer].rewards = _accounts[referrer].rewards.add(vokenRandom);

            // VOKEN_TB.mintWithVesting(msg.sender, vokenRandom, address(this));
            VOKEN_TB.mintWithVesting(referrer, vokenRandom, address(this));
        }

        _vokenIssued = _vokenIssued.add(vokenIssued);
        _accounts[msg.sender].issued = _accounts[msg.sender].issued.add(vokenIssued);
        
        // Issued + Random
        VOKEN_TB.mintWithVesting(msg.sender, vokenIssued.add(vokenRandom), address(this));

        // Payment Event
        emit Payment(msg.sender, daiAmount, vokenIssued, vokenRandom);
    }

    function etherUSDPrice()
        public
        view
        returns (uint256)
    {
        return UniswapV2Router02.getAmountsOut(1_000_000, _pathETH2DAI())[1];
    }

    function _swapExactETH2DAI(uint256 etherAmount)
        private
        returns (uint256)
    {
        uint256[] memory _result = UniswapV2Router02.swapExactETHForTokens{value: etherAmount}(0, _pathETH2DAI(), address(this), block.timestamp + 1 days);
        return _result[1];
    }

    function _pathETH2DAI()
        private
        view
        returns (address[] memory)
    {
        address[] memory path = new address[](2);
        path[0] = UniswapV2Router02.WETH();
        path[1] = address(DAI);
        return path;
    }
}

// File: EbWithVestingPermille.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


import "LibSafeMath.sol";
import "LibBaseAuth.sol";
import "LibIPermille.sol";


contract WithVestingPermille is BaseAuth {
    using SafeMath for uint256;
    
    IPermille private _issuedVestingPermilleContract;
    IPermille private _bonusesVestingPermilleContract;
    IPermille private _rewardsVestingPermilleContract;

    /**
     * @dev Set Vesting Permille Contract(s).
     */
    function setIVPC(address issuedVestingPermilleContract)
        external
        onlyAgent
    {
        _issuedVestingPermilleContract = IPermille(issuedVestingPermilleContract);
    }

    function setBVPC(address bonusesVestingPermilleContract)
        external
        onlyAgent
    {
        _bonusesVestingPermilleContract = IPermille(bonusesVestingPermilleContract);
    }

    function setRVPC(address rewardsVestingPermilleContract)
        external
        onlyAgent
    {
        _rewardsVestingPermilleContract = IPermille(rewardsVestingPermilleContract);
    }

    /**
     * @dev Returns the Vesting Permille Contract(s).
     */
    function VestingPermilleContracts()
        public
        view
        returns (
            IPermille issuedVestingPermilleContract,
            IPermille bonusesVestingPermilleContract,
            IPermille rewardsVestingPermilleContract
        )
    {
        issuedVestingPermilleContract = _issuedVestingPermilleContract;
        bonusesVestingPermilleContract = _bonusesVestingPermilleContract;
        rewardsVestingPermilleContract = _rewardsVestingPermilleContract;
    }

    /**
     * @dev Returns vesting amount for issued of `amount`.
     */
    function _getVestingAmountForIssued(uint256 amount)
        internal
        view
        returns (uint256 vesting)
    {
        if (amount > 0) {
            vesting = _getVestingAmount(amount, _issuedVestingPermilleContract, 900);
        }
    }

    /**
     * @dev Returns vesting amount for bonuses of `amount`.
     */
    function _getVestingAmountForBonuses(uint256 amount)
        internal
        view
        returns (uint256 vesting)
    {
        if (amount > 0) {
            vesting = _getVestingAmount(amount, _bonusesVestingPermilleContract, 1_000);
        }
    }
    
    
    /**
     * @dev Returns vesting amount for rewards of `amount`.
     */
    function _getVestingAmountForRewards(uint256 amount)
        internal
        view
        returns (uint256 vesting)
    {
        if (amount > 0) {
            vesting = _getVestingAmount(amount, _rewardsVestingPermilleContract, 1_000);
        }
    }

    /**
     * @dev Returns vesting amount via the `permilleContract`.
     */
    function _getVestingAmount(uint256 amount, IPermille permilleContract, uint16 defaultPermille)
        private
        view
        returns (uint256 vesting)
    {
        vesting = amount;

        uint16 permille = defaultPermille;

        if (permilleContract != IPermille(0)) {
            try permilleContract.permille() returns (uint16 permille_) {
                permille = permille_;
            }

            catch {
                //
            }
        }
        
        if (permille == 0) {
            vesting = 0;
        }

        else if (permille < 1_000) {
            vesting = vesting.mul(permille).div(1_000);
        }
    }
}

// File: LibAuthPause.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;

import "LibBaseAuth.sol";


/**
 * @dev Auth pause.
 */
contract AuthPause is BaseAuth {
    using Roles for Roles.Role;

    bool private _paused = false;

    event PausedON();
    event PausedOFF();


    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     */
    modifier onlyNotPaused() {
        require(!_paused, "Paused");
        _;
    }

    /**
     * @return Returns true if the contract is paused, false otherwise.
     */
    function isPaused()
        public
        view
        returns (bool)
    {
        return _paused;
    }

    /**
     * @dev Sets paused state.
     *
     * Can only be called by the current owner.
     */
    function setPaused(bool value)
        external
        onlyAgent
    {
        _paused = value;

        if (_paused) {
            emit PausedON();
        } else {
            emit PausedOFF();
        }
    }
}

// File: LibBaseAuth.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;

import "LibRoles.sol";
import "LibIERC20.sol";


/**
 * @dev Base auth.
 */
contract BaseAuth {
    using Roles for Roles.Role;

    Roles.Role private _agents;

    event AgentAdded(address indexed account);
    event AgentRemoved(address indexed account);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor ()
    {
        _agents.add(msg.sender);
        emit AgentAdded(msg.sender);
    }

    /**
     * @dev Throws if called by account which is not an agent.
     */
    modifier onlyAgent() {
        require(isAgent(msg.sender), "AgentRole: caller does not have the Agent role");
        _;
    }

    /**
     * @dev Rescue compatible ERC20 Token
     *
     * Can only be called by an agent.
     */
    function rescueToken(
        address tokenAddr,
        address recipient,
        uint256 amount
    )
        external
        onlyAgent
    {
        IERC20 _token = IERC20(tokenAddr);
        require(recipient != address(0), "Rescue: recipient is the zero address");
        uint256 balance = _token.balanceOf(address(this));

        require(balance >= amount, "Rescue: amount exceeds balance");
        _token.transfer(recipient, amount);
    }

    /**
     * @dev Withdraw Ether
     *
     * Can only be called by an agent.
     */
    function withdrawEther(
        address payable recipient,
        uint256 amount
    )
        external
        onlyAgent
    {
        require(recipient != address(0), "Withdraw: recipient is the zero address");
        uint256 balance = address(this).balance;
        require(balance >= amount, "Withdraw: amount exceeds balance");
        recipient.transfer(amount);
    }

    /**
     * @dev Returns true if the `account` has the Agent role.
     */
    function isAgent(address account)
        public
        view
        returns (bool)
    {
        return _agents.has(account);
    }

    /**
     * @dev Give an `account` access to the Agent role.
     *
     * Can only be called by an agent.
     */
    function addAgent(address account)
        public
        onlyAgent
    {
        _agents.add(account);
        emit AgentAdded(account);
    }

    /**
     * @dev Remove an `account` access from the Agent role.
     *
     * Can only be called by an agent.
     */
    function removeAgent(address account)
        public
        onlyAgent
    {
        _agents.remove(account);
        emit AgentRemoved(account);
    }
}


// File: LibIERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);
}

// File: LibIPermille.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @dev Interface of a permille contract.
 */
interface IPermille {
    function permille() external view returns (uint16);
}

// File: LibIVesting.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @dev Interface of an vesting contract.
 */
interface IVesting {
    function vestingOf(address account) external view returns (uint256);
}

// File: LibIVokenTB.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @title Interface of VokenTB.
 */
interface IVokenTB {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    
    function cap() external view returns (uint256);
    function totalSupply() external view returns (uint256);
    
    function mint(address account, uint256 amount) external returns (bool);
    function mintWithVesting(address account, uint256 amount, address vestingContract) external returns (bool);

    function referrer(address account) external view returns (address payable);
    function address2voken(address account) external view returns (uint160);
    function voken2address(uint160 voken) external view returns (address payable);
}

// File: LibRoles.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @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: LibSafeMath.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.7.5;


/**
 * @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;
    }
}