//  purchase(): pay the :price, acquire ownership of the _planet
//
//    discovery of available planets can be done off-chain
//

function purchase(uint32 _planet)
      external
      payable
    {
      require( //  caller must pay exactly the price of a planet
               //
               (msg.value == price) &&
               //
               //  the planet must be available for purchase
               //
               available(_planet) );

      //  spawn the planet to us, then immediately transfer to the caller
      //
      //    spawning to the caller would give the point's prefix's owner
      //    a window of opportunity to cancel the transfer
      //
      Ecliptic ecliptic = Ecliptic(azimuth.owner());
      ecliptic.spawn(_planet, this);
      ecliptic.transferPoint(_planet, msg.sender, false);
      emit PlanetSold(azimuth.getPrefix(_planet), _planet);
    }


/// @dev Transfer tokens to addresses registered for airdrop
/// @param dests Array of addresses that have registered for airdrop
/// @param values Array of token amount for each address that have registered for airdrop
/// @return Number of transfers

function airdrop(address[] dests, uint256[] values) public onlyOwner returns (uint256) {
        require(dests.length == values.length);
        uint256 i = 0;
        while (i < dests.length) {
            token.transfer(dests[i], values[i]);
            i += 1;
        }
        return (i);
    }


/**
 * @dev Returns the multiplication of two unsigned integers, reverting on overflow.
 */

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            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 with custom message on
 * division by zero. The result is rounded towards zero.
 */

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);
            return a / b;
        }


/**
 * @dev Finds currently applicable rate modifier.
 * @return Current rate modifier percentage.
 */

function currentModifier() public view returns (uint256 rateModifier) {
        // solium-disable-next-line security/no-block-members
        uint256 comparisonVariable = now;
        for (uint i = 0; i < modifiers.length; i++) {
            if (comparisonVariable >= modifiers[i].start) {
                rateModifier = modifiers[i].ratePermilles;
            }
        }
    }


/**
 * @dev Delegates the current call to `implementation`.
 *
 * This function does not return to its internall call site, it will return directly to the external caller.
 */

function _delegate(address implementation) internal {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }


/// @notice Called by Aave when sending back the FL amount
/// @param _reserve The address of the borrowed token
/// @param _amount Amount of FL tokens received
/// @param _fee FL Aave fee
/// @param _params The params that are sent from the original FL caller contract

function executeOperation(
        address _reserve,
        uint256 _amount,
        uint256 _fee,
        bytes calldata _params)
    external override {
        // Format the call data for DSProxy
        (CompCreateData memory compCreate, ExchangeData memory exchangeData)
                                 = packFunctionCall(_amount, _fee, _params);

        address leveragedAsset = _reserve;

        // If the assets are different
        if (compCreate.cCollAddr != compCreate.cDebtAddr) {
            (, uint sellAmount) = _sell(exchangeData);
            getFee(sellAmount, exchangeData.destAddr, compCreate.proxyAddr);

            leveragedAsset = exchangeData.destAddr;
        }

        // Send amount to DSProxy
        sendToProxy(compCreate.proxyAddr, leveragedAsset);

        address compOpenProxy = shifterRegistry.getAddr("COMP_SHIFTER");

        // Execute the DSProxy call
        DSProxyInterface(compCreate.proxyAddr).execute(compOpenProxy, compCreate.proxyData);

        // Repay the loan with the money DSProxy sent back
        transferFundsBackToPoolInternal(_reserve, _amount.add(_fee));

        // if there is some eth left (0x fee), return it to user
        if (address(this).balance > 0) {
            // solhint-disable-next-line avoid-tx-origin
            tx.origin.transfer(address(this).balance);
        }
    }


/// @notice Formats function data call so we can call it through DSProxy
/// @param _amount Amount of FL
/// @param _fee Fee of the FL
/// @param _params Saver proxy params

function packFunctionCall(uint _amount, uint _fee, bytes memory _params) internal pure  returns (CompCreateData memory compCreate, ExchangeData memory exchangeData) {
        (
            uint[4] memory numData, // srcAmount, destAmount, minPrice, price0x
            address[6] memory cAddresses, // cCollAddr, cDebtAddr, srcAddr, destAddr, exchangeAddr, wrapper
            bytes memory callData,
            address proxy
        )
        = abi.decode(_params, (uint256[4],address[6],bytes,address));

        bytes memory proxyData = abi.encodeWithSignature(
            "open(address,address,uint256)",
                                cAddresses[0], cAddresses[1], (_amount + _fee));

         exchangeData = SaverExchangeCore.ExchangeData({
            srcAddr: cAddresses[2],
            destAddr: cAddresses[3],
            srcAmount: numData[0],
            destAmount: numData[1],
            minPrice: numData[2],
            wrapper: cAddresses[5],
            exchangeAddr: cAddresses[4],
            callData: callData,
            price0x: numData[3]
        });

        compCreate = CompCreateData({
            proxyAddr: payable(proxy),
            proxyData: proxyData,
            cCollAddr: cAddresses[0],
            cDebtAddr: cAddresses[1]
        });

        return (compCreate, exchangeData);
    }


//////////////////////// INTERNAL FUNCTIONS //////////////////////////

function _callCloseAndOpen(
        SaverExchangeCore.ExchangeData memory _exchangeData,
        LoanShiftData memory _loanShift
    ) internal {
        address protoAddr = shifterRegistry.getAddr(getNameByProtocol(uint8(_loanShift.fromProtocol)));

        if (_loanShift.wholeDebt) {
            _loanShift.debtAmount = ILoanShifter(protoAddr).getLoanAmount(_loanShift.id1, _loanShift.debtAddr1);
        }

        (
            uint[8] memory numData,
            address[8] memory addrData,
            uint8[3] memory enumData,
            bytes memory callData
        )
        = _packData(_loanShift, _exchangeData);

        // encode data
        bytes memory paramsData = abi.encode(numData, addrData, enumData, callData, address(this));

        address payable loanShifterReceiverAddr = payable(shifterRegistry.getAddr("LOAN_SHIFTER_RECEIVER"));

        loanShifterReceiverAddr.transfer(address(this).balance);

        // call FL
        givePermission(loanShifterReceiverAddr);

        lendingPool.flashLoan(loanShifterReceiverAddr,
           getLoanAddr(_loanShift.debtAddr1, _loanShift.fromProtocol), _loanShift.debtAmount, paramsData);

        removePermission(loanShifterReceiverAddr);

        unsubFromAutomation(
            _loanShift.unsub,
            _loanShift.id1,
            _loanShift.id2,
            _loanShift.fromProtocol,
            _loanShift.toProtocol
        );

        logEvent(_exchangeData, _loanShift);
    }


/**
 * @dev Accept ownership of the contract.
 *
 * Can only be called by the new owner.
 */

function acceptOwnership() public {
            require(msg.sender == _newOwner, "Ownable: caller is not the new owner address");
            require(msg.sender != address(0), "Ownable: caller is the zero address");

            emit OwnershipAccepted(_owner, msg.sender);
            _owner = msg.sender;
            _newOwner = address(0);
        }


/**
 * @dev Rescue compatible ERC20 Token
 *
 * Can only be called by the current owner.
 */

function rescueTokens(address tokenAddr, address recipient, uint256 amount) external onlyOwner {
            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);
        }


/// @notice Handles that the amount is not bigger than cdp debt and not dust

function limitLoanAmount(uint _cdpId, bytes32 _ilk, uint _paybackAmount, address _owner) internal returns (uint256) {
        uint debt = getAllDebt(address(vat), manager.urns(_cdpId), manager.urns(_cdpId), _ilk);

        if (_paybackAmount > debt) {
            ERC20(DAI_ADDRESS).transfer(_owner, (_paybackAmount - debt));
            return debt;
        }

        uint debtLeft = debt - _paybackAmount;

        (,,,, uint dust) = vat.ilks(_ilk);
        dust = dust / 10**27;

        // Less than dust value
        if (debtLeft < dust) {
            uint amountOverDust = (dust - debtLeft);

            ERC20(DAI_ADDRESS).transfer(_owner, amountOverDust);

            return (_paybackAmount - amountOverDust);
        }

        return _paybackAmount;
    }


/// @notice Bots call this method to repay for user when conditions are met
/// @dev If the contract ownes gas token it will try and use it for gas price reduction

function repayFor(
        SaverExchangeCore.ExchangeData memory _exchangeData,
        uint _cdpId,
        uint _nextPrice,
        address _joinAddr
    ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) {

        (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _cdpId, _nextPrice);
        require(isAllowed);

        uint gasCost = calcGasCost(REPAY_GAS_COST);

        address owner = subscriptionsContract.getOwner(_cdpId);

        monitorProxyContract.callExecute{value: msg.value}(
            owner,
            mcdSaverTakerAddress,
            abi.encodeWithSignature(
            "repayWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256,uint256,address)",
            _exchangeData, _cdpId, gasCost, _joinAddr));

        (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _cdpId, _nextPrice);
        require(isGoodRatio);

        returnEth();

        logger.Log(address(this), owner, "AutomaticMCDRepay", abi.encode(ratioBefore, ratioAfter));
    }


/**
 * @dev Withdraw Ether
 *
 * Can only be called by the current owner.
 */

function withdrawEther(address payable recipient, uint256 amount) external onlyOwner {
            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);
        }


/// @notice Gets CDP info (collateral, debt)
/// @param _cdpId Id of the CDP
/// @param _ilk Ilk of the CDP

function getCdpInfo(uint _cdpId, bytes32 _ilk) public view returns (uint, uint) {
        address urn = manager.urns(_cdpId);

        (uint collateral, uint debt) = vat.urns(_ilk, urn);
        (,uint rate,,,) = vat.ilks(_ilk);

        return (collateral, rmul(debt, rate));
    }


/// @notice Gets CDP ratio
/// @param _cdpId Id of the CDP
/// @param _nextPrice Next price for user

function getRatio(uint _cdpId, uint _nextPrice) public view returns (uint) {
        bytes32 ilk = manager.ilks(_cdpId);
        uint price = (_nextPrice == 0) ? getPrice(ilk) : _nextPrice;

        (uint collateral, uint debt) = getCdpInfo(_cdpId, ilk);

        if (debt == 0) return 0;

        return rdiv(wmul(collateral, price), debt) / (10 ** 18);
    }


/// @notice Checks if Boost/Repay could be triggered for the CDP
/// @dev Called by MCDMonitor to enforce the min/max check

function canCall(Method _method, uint _cdpId, uint _nextPrice) public view returns(bool, uint) {
        bool subscribed;
        CdpHolder memory holder;
        (subscribed, holder) = subscriptionsContract.getCdpHolder(_cdpId);

        // check if cdp is subscribed
        if (!subscribed) return (false, 0);

        // check if using next price is allowed
        if (_nextPrice > 0 && !holder.nextPriceEnabled) return (false, 0);

        // check if boost and boost allowed
        if (_method == Method.Boost && !holder.boostEnabled) return (false, 0);

        // check if owner is still owner
        if (getOwner(_cdpId) != holder.owner) return (false, 0);

        uint currRatio = getRatio(_cdpId, _nextPrice);

        if (_method == Method.Repay) {
            return (currRatio < holder.minRatio, currRatio);
        } else if (_method == Method.Boost) {
            return (currRatio > holder.maxRatio, currRatio);
        }
    }


/// @dev After the Boost/Repay check if the ratio doesn't trigger another call

function ratioGoodAfter(Method _method, uint _cdpId, uint _nextPrice) public view returns(bool, uint) {
        CdpHolder memory holder;

        (, holder) = subscriptionsContract.getCdpHolder(_cdpId);

        uint currRatio = getRatio(_cdpId, _nextPrice);

        if (_method == Method.Repay) {
            return (currRatio < holder.maxRatio, currRatio);
        } else if (_method == Method.Boost) {
            return (currRatio > holder.minRatio, currRatio);
        }
    }


/// @notice Helper method to payback the cream debt
/// @dev If amount is bigger it will repay the whole debt and send the extra to the _user
/// @param _amount Amount of tokens we want to repay
/// @param _cBorrowToken Ctoken address we are repaying
/// @param _borrowToken Token address we are repaying
/// @param _user Owner of the cream position we are paying back

function paybackDebt(uint _amount, address _cBorrowToken, address _borrowToken, address payable _user) internal {
        uint wholeDebt = CTokenInterface(_cBorrowToken).borrowBalanceCurrent(address(this));

        if (_amount > wholeDebt) {
            if (_borrowToken == ETH_ADDRESS) {
                _user.transfer((_amount - wholeDebt));
            } else {
                ERC20(_borrowToken).safeTransfer(_user, (_amount - wholeDebt));
            }

            _amount = wholeDebt;
        }

        approveCToken(_borrowToken, _cBorrowToken);

        if (_borrowToken == ETH_ADDRESS) {
            CEtherInterface(_cBorrowToken).repayBorrow{value: _amount}();
        } else {
            require(CTokenInterface(_cBorrowToken).repayBorrow(_amount) == 0);
        }
    }


/// @notice Calculates the fee amount
/// @param _amount Amount that is converted
/// @param _user Actuall user addr not DSProxy
/// @param _gasCost Ether amount of gas we are spending for tx
/// @param _cTokenAddr CToken addr. of token we are getting for the fee
/// @return feeAmount The amount we took for the fee

function getFee(uint _amount, address _user, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) {
        uint fee = MANUAL_SERVICE_FEE;

        if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) {
            fee = AUTOMATIC_SERVICE_FEE;
        }

        address tokenAddr = getUnderlyingAddr(_cTokenAddr);

        if (Discount(DISCOUNT_ADDR).isCustomFeeSet(_user)) {
            fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(_user);
        }

        feeAmount = (fee == 0) ? 0 : (_amount / fee);

        if (_gasCost != 0) {
            address oracle = ComptrollerInterface(COMPTROLLER).oracle();

            uint ethTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr);

            _gasCost = wdiv(_gasCost, ethTokenPrice);

            feeAmount = add(feeAmount, _gasCost);
        }

        // fee can't go over 20% of the whole amount
        if (feeAmount > (_amount / 5)) {
            feeAmount = _amount / 5;
        }

        if (tokenAddr == ETH_ADDRESS) {
            WALLET_ADDR.transfer(feeAmount);
        } else {
            ERC20(tokenAddr).safeTransfer(WALLET_ADDR, feeAmount);
        }
    }


/// @notice Calculates the gas cost of transaction and send it to wallet
/// @param _amount Amount that is converted
/// @param _gasCost Ether amount of gas we are spending for tx
/// @param _cTokenAddr CToken addr. of token we are getting for the fee
/// @return feeAmount The amount we took for the fee

function getGasCost(uint _amount, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) {
        address tokenAddr = getUnderlyingAddr(_cTokenAddr);

        if (_gasCost != 0) {
            address oracle = ComptrollerInterface(COMPTROLLER).oracle();

            uint ethTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr);

            feeAmount = wdiv(_gasCost, ethTokenPrice);
        }

        // fee can't go over 20% of the whole amount
        if (feeAmount > (_amount / 5)) {
            feeAmount = _amount / 5;
        }

        if (tokenAddr == ETH_ADDRESS) {
            WALLET_ADDR.transfer(feeAmount);
        } else {
            ERC20(tokenAddr).safeTransfer(WALLET_ADDR, feeAmount);
        }
    }


/// @notice Returns the maximum amount of collateral available to withdraw
/// @dev Due to rounding errors the result is - 1% wei from the exact amount
/// @param _cCollAddress Collateral we are getting the max value of
/// @param _account Users account
/// @return Returns the max. collateral amount in that token

function getMaxCollateral(address _cCollAddress, address _account) public returns (uint) {
        (, uint liquidityInEth, ) = ComptrollerInterface(COMPTROLLER).getAccountLiquidity(_account);
        uint usersBalance = CTokenInterface(_cCollAddress).balanceOfUnderlying(_account);
        address oracle = ComptrollerInterface(COMPTROLLER).oracle();

        if (liquidityInEth == 0) return usersBalance;

        CTokenInterface(_cCollAddress).accrueInterest();

         if (_cCollAddress == CETH_ADDRESS) {
             if (liquidityInEth > usersBalance) return usersBalance;

             return sub(liquidityInEth, (liquidityInEth / 100));
         }

        uint ethPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cCollAddress);
        uint liquidityInToken = wdiv(liquidityInEth, ethPrice);

        if (liquidityInToken > usersBalance) return usersBalance;

        return sub(liquidityInToken, (liquidityInToken / 100)); // cut off 1% due to rounding issues
    }


/**
 * Close the current stage.
 */

function _closeStage() private {
            _stage = _stage.add(1);
            emit StageClosed(_stage);

            // Close current season
            uint16 __seasonNumber = _seasonNumber(_stage);
            if (_season < __seasonNumber) {
                _season = __seasonNumber;
                emit SeasonClosed(_season);
            }

            _vokenUsdPrice = _calcVokenUsdPrice(_stage);
            _shareholdersRatio = _calcShareholdersRatio(_stage);
        }


/**
 * @dev Returns the {limitIndex} and {weiMax}.
 */

function _limit(uint256 weiAmount) private view returns (uint256 __wei) {
            uint256 __purchased = _weiSeasonAccountPurchased[_season][msg.sender];
            for(uint16 i = 0; i < LIMIT_WEIS.length; i++) {
                if (__purchased >= LIMIT_WEIS[i]) {
                    return 0;
                }

                if (__purchased < LIMIT_WEIS[i]) {
                    __wei = LIMIT_WEIS[i].sub(__purchased);
                    if (weiAmount >= __wei && _seasonLimitAccounts[_season][i].length < LIMIT_COUNTER[i]) {
                        return __wei;
                    }
                }
            }

            if (__purchased < LIMIT_WEI_MIN) {
                return LIMIT_WEI_MIN.sub(__purchased);
            }
        }


/**
 * @dev Updates the season limit accounts, or wei min accounts.
 */

function _updateSeasonLimits() private {
            uint256 __purchased = _weiSeasonAccountPurchased[_season][msg.sender];
            if (__purchased > LIMIT_WEI_MIN) {
                for(uint16 i = 0; i < LIMIT_WEIS.length; i++) {
                    if (__purchased >= LIMIT_WEIS[i]) {
                        _seasonLimitAccounts[_season][i].push(msg.sender);
                        return;
                    }
                }
            }

            else if (__purchased == LIMIT_WEI_MIN) {
                _seasonLimitWeiMinAccounts[_season].push(msg.sender);
                return;
            }
        }


/**
 * @dev Returns the accounts of wei limit, by `seasonNumber` and `limitIndex`.
 */

function seasonLimitAccounts(uint16 seasonNumber, uint16 limitIndex) public view returns (uint256 weis, address[] memory accounts) {
            if (limitIndex < LIMIT_WEIS.length) {
                weis = LIMIT_WEIS[limitIndex];
                accounts = _seasonLimitAccounts[seasonNumber][limitIndex];
            }

            else {
                weis = LIMIT_WEI_MIN;
                accounts = _seasonLimitWeiMinAccounts[seasonNumber];
            }
        }


/**
 * @dev Cache.
 */

function _cache() private {
            if (!_seasonHasAccount[_season][msg.sender]) {
                _seasonAccounts[_season].push(msg.sender);
                _seasonHasAccount[_season][msg.sender] = true;
            }

            _cacheWhitelisted = _VOKEN.whitelisted(msg.sender);
            if (_cacheWhitelisted) {
                address __account = msg.sender;
                for(uint16 i = 0; i < REWARDS_PCT.length; i++) {
                    address __referee = _VOKEN.whitelistReferee(__account);

                    if (__referee != address(0) && __referee != __account && _VOKEN.whitelistReferralsCount(__referee) > i) {
                        if (!_seasonHasReferral[_season][__referee]) {
                            _seasonReferrals[_season].push(__referee);
                            _seasonHasReferral[_season][__referee] = true;
                        }

                        if (!_seasonAccountHasReferral[_season][__referee][__account]) {
                            _seasonAccountReferrals[_season][__referee].push(__account);
                            _seasonAccountHasReferral[_season][__referee][__account] = true;
                        }

                        _cacheReferees.push(address(uint160(__referee)));
                        _cacheRewards.push(REWARDS_PCT[i]);
                    }
                    else {
                        _cachePended = _cachePended.add(REWARDS_PCT[i]);
                    }

                    __account = __referee;
                }
            }
        }


/**
 * @dev USD => Voken
 */

function _tx(uint256 __usd) private returns (uint256 __voken, uint256 __usdRemain) {
            uint256 __stageUsdCap = _stageUsdCap(_stage);
            uint256 __usdUsed;

            // in stage
            if (_stageUsdSold[_stage].add(__usd) <= __stageUsdCap) {
                __usdUsed = __usd;

                (__voken, ) = _calcExchange(__usdUsed);
                _mintVokenIssued(__voken);

                // close stage, if stage dollor cap reached
                if (__stageUsdCap == _stageUsdSold[_stage]) {
                    _closeStage();
                }
            }

            // close stage
            else {
                __usdUsed = __stageUsdCap.sub(_stageUsdSold[_stage]);

                (__voken, ) = _calcExchange(__usdUsed);
                _mintVokenIssued(__voken);

                _closeStage();

                __usdRemain = __usd.sub(__usdUsed);
            }
        }


/**
 * @dev USD => voken & wei, and make records.
 */

function _calcExchange(uint256 __usd) private returns (uint256 __voken, uint256 __wei) {
            __wei = _usd2wei(__usd);
            __voken = _usd2voken(__usd);

            uint256 __weiShareholders = _usd2wei(_2shareholders(__usd));

            // Stage: usd
            _stageUsdSold[_stage] = _stageUsdSold[_stage].add(__usd);

            // Season: wei
            _seasonWeiSold[_season] = _seasonWeiSold[_season].add(__wei);

            // Season: wei pended
            if (_cachePended > 0) {
                _seasonWeiPended[_season] = _seasonWeiPended[_season].add(__wei.mul(_cachePended).div(100));
            }

            // Season shareholders: wei
            _seasonWeiShareholders[_season] = _seasonWeiShareholders[_season].add(__weiShareholders);

            // Cache
            _cacheWeiShareholders = _cacheWeiShareholders.add(__weiShareholders);

            // Season => account: wei
            _weiSeasonAccountPurchased[_season][msg.sender] = _weiSeasonAccountPurchased[_season][msg.sender].add(__wei);

            // season referral account
            if (_cacheWhitelisted) {
                for (uint16 i = 0; i < _cacheRewards.length; i++) {
                    address __referee = _cacheReferees[i];
                    uint256 __weiReward = __wei.mul(_cacheRewards[i]).div(100);

                    //
                    if (i == 0) {
                        _accountVokenReferral[__referee] = _accountVokenReferral[__referee].add(__voken);
                        _vokenSeasonAccountReferral[_season][__referee] = _vokenSeasonAccountReferral[_season][__referee].add(__voken);
                    }

                    // season
                    _seasonWeiRewarded[_season] = _seasonWeiRewarded[_season].add(__weiReward);

                    // season => account
                    _accountVokenReferrals[__referee] = _accountVokenReferrals[__referee].add(__voken);
                    _vokenSeasonAccountReferrals[_season][__referee] = _vokenSeasonAccountReferrals[_season][__referee].add(__voken);
                    _weiSeasonAccountReferrals[_season][__referee] = _weiSeasonAccountReferrals[_season][__referee].add(__wei);
                    _weiSeasonAccountRewarded[_season][__referee] = _weiSeasonAccountRewarded[_season][__referee].add(__weiReward);
                }
            }
        }


/**
 * @dev Mint Voken issued.
 */

function _mintVokenIssued(uint256 amount) private {
            // Global
            _vokenIssued = _vokenIssued.add(amount);
            _vokenIssuedTxs = _vokenIssuedTxs.add(1);

            // Account
            _accountVokenIssued[msg.sender] = _accountVokenIssued[msg.sender].add(amount);

            // Stage
            _stageVokenIssued[_stage] = _stageVokenIssued[_stage].add(amount);

            // Season
            _seasonVokenIssued[_season] = _seasonVokenIssued[_season].add(amount);
            _vokenSeasonAccountIssued[_season][msg.sender] = _vokenSeasonAccountIssued[_season][msg.sender].add(amount);

            // Mint
            assert(_VOKEN.mint(msg.sender, amount));
        }


/**
 * @dev Mint Voken bonus.
 */

function _mintVokenBonus(uint256 amount) private {
            // Global
            _vokenBonus = _vokenBonus.add(amount);
            _vokenBonusTxs = _vokenBonusTxs.add(1);

            // Account
            _accountVokenBonus[msg.sender] = _accountVokenBonus[msg.sender].add(amount);

            // Stage
            _stageVokenBonus[_stage] = _stageVokenBonus[_stage].add(amount);

            // Season
            _seasonVokenBonus[_season] = _seasonVokenBonus[_season].add(amount);
            _vokenSeasonAccountBonus[_season][msg.sender] = _vokenSeasonAccountBonus[_season][msg.sender].add(amount);

            // Mint with allocation
            Allocations.Allocation memory __allocation;
            __allocation.amount = amount;
            __allocation.timestamp = now;
            _allocations[msg.sender].push(__allocation);
            assert(_VOKEN.mintWithAllocation(msg.sender, amount, address(this)));
        }


/**
 * @dev Complete an order combination and issue the loan to the borrower.
 * @param _taker address of investor.
 * @param _toTime order repayment due date.
 * @param _repaymentSum total amount of money that the borrower ultimately needs to return.
 */

function takerOrder(address _taker, uint32 _toTime, uint256 _repaymentSum) public onlyOwnerOrPartner {
    require(_taker != address(0) && _toTime > 0 && now <= _toTime && _repaymentSum > 0 && status == StatusChoices.NO_LOAN);
    taker = _taker;
    toTime = _toTime;
    repaymentSum = _repaymentSum;

    // Transfer the token provided by the investor to the borrower's address
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(ethAmount[_taker] > 0 && address(this).balance > 0);
      outLoanSum = address(this).balance;
      maker.transfer(outLoanSum);
    } else {
      require(token20 != address(0) && ERC20(token20).balanceOf(address(this)) > 0);
      outLoanSum = ERC20(token20).balanceOf(address(this));
      require(safeErc20Transfer(maker, outLoanSum));
    }

    // Update contract business execution status.
    status = StatusChoices.REPAYMENT_WAITING;

    emit TakerOrder(taker, outLoanSum);
  }


/**
 * @dev Only the full repayment will execute the contract agreement.
 */

function executeOrder() public onlyOwnerOrPartner {
    require(now <= toTime && status == StatusChoices.REPAYMENT_WAITING);
    // The borrower pays off the loan and performs two-way operation.
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(ethAmount[maker] >= repaymentSum && address(this).balance >= repaymentSum);
      lastRepaymentSum = address(this).balance;
      taker.transfer(repaymentSum);
    } else {
      require(ERC20(token20).balanceOf(address(this)) >= repaymentSum);
      lastRepaymentSum = ERC20(token20).balanceOf(address(this));
      require(safeErc20Transfer(taker, repaymentSum));
    }

    PledgeContract(owner)._conclude();
    status = StatusChoices.REPAYMENT_ALL;
    emit ExecuteOrder(maker, lastRepaymentSum);
  }


/**
 * @dev Close position or due repayment operation.
 */

function forceCloseOrder() public onlyOwnerOrPartner {
    require(status == StatusChoices.REPAYMENT_WAITING);
    uint256 transferSum = 0;

    if (now <= toTime) {
      status = StatusChoices.CLOSE_POSITION;
    } else {
      status = StatusChoices.OVERDUE_STOP;
    }

    if(loanTokenName.stringCompare(TOKEN_ETH)){
        if(ethAmount[maker] > 0 && address(this).balance > 0){
            transferSum = address(this).balance;
            maker.transfer(transferSum);
        }
    }else{
        if(ERC20(token20).balanceOf(address(this)) > 0){
            transferSum = ERC20(token20).balanceOf(address(this));
            require(safeErc20Transfer(maker, transferSum));
        }
    }

    // Return pledge token.
    PledgeContract(owner)._forceConclude(taker);
    emit ForceCloseOrder(toTime, transferSum);
  }


/**
 * @dev Withdrawal of the token invested by the taker.
 * @param _taker address of investor.
 * @param _refundSum refundSum number of tokens withdrawn.
 */

function withdrawToken(address _taker, uint256 _refundSum) public onlyOwnerOrPartner {
    require(status == StatusChoices.NO_LOAN);
    require(_taker != address(0) && _refundSum > 0);
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(address(this).balance >= _refundSum && ethAmount[_taker] >= _refundSum);
      _taker.transfer(_refundSum);
      ethAmount[_taker] = ethAmount[_taker].sub(_refundSum);
    } else {
      require(ERC20(token20).balanceOf(address(this)) >= _refundSum);
      require(safeErc20Transfer(_taker, _refundSum));
    }
    emit WithdrawToken(_taker, _refundSum);
  }


/**
 * @dev Get current contract order status.
 */

function getPledgeStatus() public view returns(string pledgeStatus) {
    if (status == StatusChoices.NO_LOAN) {
      pledgeStatus = "NO_LOAN";
    } else if (status == StatusChoices.REPAYMENT_WAITING) {
      pledgeStatus = "REPAYMENT_WAITING";
    } else if (status == StatusChoices.REPAYMENT_ALL) {
      pledgeStatus = "REPAYMENT_ALL";
    } else if (status == StatusChoices.CLOSE_POSITION) {
      pledgeStatus = "CLOSE_POSITION";
    } else {
      pledgeStatus = "OVERDUE_STOP";
    }
  }


/**
 * @dev Create a pledge subcontract
 * @param _pledgeId index number of the pledge contract.
 */

function createPledgeContract(uint256 _pledgeId, address _escrowPartner) public onlyPartner returns(bool) {
    require(_pledgeId > 0 && !isPledgeId[_pledgeId] && _escrowPartner!=address(0));

    // Give the pledge contract the right to update statistics.
    PledgeContract pledgeAddress = new PledgeContract(_pledgeId, address(this),partner);
    pledgeAddress.transferOwnership(_escrowPartner);
    addOperater(address(pledgeAddress));

    // update pledge contract info
    isPledgeId[_pledgeId] = true;
    pledgeEscrowById[_pledgeId] = EscrowPledge(pledgeAddress, INIT_TOKEN_NAME);

    emit CreatePledgeContract(_pledgeId, address(pledgeAddress));
    return true;
  }


// -----------------------------------------
// external interface
// -----------------------------------------

function() external payable {
    require(status != StatusChoices.PLEDGE_REFUND);
    // Identify the borrower.
    if (maker != address(0)) {
      require(address(msg.sender) == maker);
    }
    // Record basic information about the borrower's pledge ETH
    verifyEthAccount[msg.sender] = verifyEthAccount[msg.sender].add(msg.value);
  }


/**
 * @dev Add the pledge information and transfer the pledged token into the corresponding currency pool.
 * @param _pledgeTokenName maker pledge token name.
 * @param _maker borrower address.
 * @param _pledgeSum pledge amount.
 * @param _loanTokenName pledge token type.
 */

function addRecord(string _pledgeTokenName, address _maker, uint256 _pledgeSum, string _loanTokenName) public onlyOwner {
    require(_maker != address(0) && _pledgeSum > 0 && status != StatusChoices.PLEDGE_REFUND);
    // Add the pledge information for the first time.
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      // public data init.
      maker = _maker;
      pledgeTokenName = _pledgeTokenName;
      tokenPoolAddress = checkedTokenPool(pledgeTokenName);
      PledgeFactory(factory).updatePledgeType(pledgeId, pledgeTokenName);
      // Assign rights to the operation of the contract pool
      PledgeFactory(factory).tokenPoolOperater(tokenPoolAddress, address(this));
      // Create order management contracts.
      createOrderContract(_loanTokenName);
    }
    // Record information of each pledge.
    pledgeAccountSum = pledgeAccountSum.add(_pledgeSum);
    PledgePoolBase(tokenPoolAddress).addRecord(maker, pledgeAccountSum, pledgeId, pledgeTokenName);
    // Transfer the pledge token to the appropriate token pool.
    if (pledgeTokenName.stringCompare(TOKEN_ETH)) {
      require(verifyEthAccount[maker] >= _pledgeSum);
      tokenPoolAddress.transfer(_pledgeSum);
    } else {
      token20 = checkedToken(pledgeTokenName);
      require(ERC20(token20).balanceOf(address(this)) >= _pledgeSum);
      require(safeErc20Transfer(token20,tokenPoolAddress, _pledgeSum));
    }
  }


/**
 * @dev Withdraw pledge behavior.
 * @param _maker borrower address.
 */

function withdrawToken(address _maker) public onlyOwner {
    require(status != StatusChoices.PLEDGE_REFUND);
    uint256 pledgeSum = 0;
    // there are two types of retractions.
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      pledgeSum = classifySquareUp(_maker);
    } else {
      status = StatusChoices.PLEDGE_REFUND;
      require(PledgePoolBase(tokenPoolAddress).withdrawToken(pledgeId, maker, pledgeAccountSum));
      pledgeSum = pledgeAccountSum;
    }
    emit WithdrawToken(_maker, pledgeTokenName, pledgeSum);
  }


/**
 * @dev Executed in some extreme unforsee cases, to avoid eth locked.
 * @param _tokenName recycle token type.
 * @param _amount Number of eth to recycle.
 */

function recycle(string _tokenName, uint256 _amount) public onlyOwner {
    require(status != StatusChoices.NO_PLEDGE_INFO && _amount>0);
    if (_tokenName.stringCompare(TOKEN_ETH)) {
      require(address(this).balance >= _amount);
      owner.transfer(_amount);
    } else {
      address token = checkedToken(_tokenName);
      require(ERC20(token).balanceOf(address(this)) >= _amount);
      require(safeErc20Transfer(token,owner, _amount));
    }
  }


/**
 * @dev Create an order process management contract for the match and repayment business.
 * @param _loanTokenName expect loan token type.
 */

function createOrderContract(string _loanTokenName) internal {
    require(bytes(_loanTokenName).length > 0);
    status = StatusChoices.PLEDGE_CREATE_MATCHING;
    address loanToken20 = checkedToken(_loanTokenName);
    OrderManageContract newOrder = new OrderManageContract(_loanTokenName, loanToken20, maker);
    setPartner(address(newOrder));
    newOrder.setPartner(owner);
    // update contract public data.
    orderContract = newOrder;
    loanTokenName = _loanTokenName;
    emit CreateOrderContract(address(newOrder));
  }


/**
 * @dev classification withdraw.
 * @dev Execute without changing the current contract data state.
 * @param _maker borrower address.
 */

function classifySquareUp(address _maker) internal returns(uint256 sum) {
    if (pledgeTokenName.stringCompare(TOKEN_ETH)) {
      uint256 pledgeSum = verifyEthAccount[_maker];
      require(pledgeSum > 0 && address(this).balance >= pledgeSum);
      _maker.transfer(pledgeSum);
      verifyEthAccount[_maker] = 0;
      sum = pledgeSum;
    } else {
      uint256 balance = ERC20(token20).balanceOf(address(this));
      require(balance > 0);
      require(safeErc20Transfer(token20,_maker, balance));
      sum = balance;
    }
  }


/**
 * @dev Get current contract order status.
 * @return pledgeStatus state indicate.
 */

function getPledgeStatus() public view returns(string pledgeStatus) {
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      pledgeStatus = "NO_PLEDGE_INFO";
    } else if (status == StatusChoices.PLEDGE_CREATE_MATCHING) {
      pledgeStatus = "PLEDGE_CREATE_MATCHING";
    } else {
      pledgeStatus = "PLEDGE_REFUND";
    }
  }


/**
 * @dev _preValidateAddRecord, Validation of an incoming AddRecord. Use require statemens to revert state when conditions are not met.
 * @param _payerAddress Address performing the pleadge.
 * @param _pledgeSum the value to pleadge.
 * @param _pledgeId pledge contract index number.
 * @param _tokenName pledge token name.
 */

function _preValidateAddRecord(address _payerAddress, uint256 _pledgeSum, uint256 _pledgeId, string _tokenName) view internal {
    require(_pledgeSum > 0 && _pledgeId > 0
      && _payerAddress != address(0)
      && bytes(_tokenName).length > 0
      && address(msg.sender).isContract()
      && PledgeContract(msg.sender).getPledgeId()==_pledgeId
    );
  }


/**
 * @dev _preValidateRefund, Validation of an incoming refund. Use require statemens to revert state when conditions are not met.
 * @param _pledgeId pledge contract index number.
 * @param _targetAddress transfer target address.
 * @param _returnSum return token sum.
 */

function _preValidateRefund(uint256 _returnSum, address _targetAddress, uint256 _pledgeId) view internal {
    require(_returnSum > 0 && _pledgeId > 0
      && _targetAddress != address(0)
      && address(msg.sender).isContract()
      && _returnSum <= escrows[_pledgeId].pledgeSum
      && PledgeContract(msg.sender).getPledgeId()==_pledgeId
    );
  }


/**
 * @dev _preValidateWithdraw, Withdraw initiated parameter validation.
 * @param _pledgeId pledge contract index number.
 * @param _maker borrower address.
 * @param _num withdraw token sum.
 */

function _preValidateWithdraw(address _maker, uint256 _num, uint256 _pledgeId) view internal {
    require(_num > 0 && _pledgeId > 0
       && _maker != address(0)
       && address(msg.sender).isContract()
       && _num <= escrows[_pledgeId].pledgeSum
       && PledgeContract(msg.sender).getPledgeId()==_pledgeId
    );
  }


/**
 * @dev Returns the sum of claimable AC from multiple gauges.
 */

function claimable(address _account, address[] memory _gauges) external view returns (uint256) {
        uint256 _total = 0;
        for (uint256 i = 0; i < _gauges.length; i++) {
            _total = _total.add(IGauge(_gauges[i]).claimable(_account));
        }

        return _total;
    }


/**
 * @dev Constructor for CurrentCrowdsale contract.
 * @dev Set the owner who can manage whitelist and token.
 * @param _maxcap The maxcap value.
 * @param _startPhase1 The phase1 ICO start time.
 * @param _startPhase2 The phase2 ICO start time.
 * @param _startPhase3 The phase3 ICO start time.
 * @param _endOfPhase3 The end time of ICO.
 * @param _withdrawalWallet The address to which raised funds will be withdrawn.
 * @param _rate exchange rate for ico.
 * @param _token address of token used for ico.
 * @param _whitelist address of whitelist contract used for ico.
 */

function CurrentCrowdsale(
        uint256 _maxcap,
        uint256 _startPhase1,
        uint256 _startPhase2,
        uint256 _startPhase3,
        uint256 _endOfPhase3,
        address _withdrawalWallet,
        uint256 _rate,
        CurrentToken _token,
        Whitelist _whitelist
    )  TokenRate(_rate) public
    {
        require(_withdrawalWallet != address(0));
        require(_token != address(0) && _whitelist != address(0));
        require(_startPhase1 >= now);
        require(_endOfPhase3 > _startPhase3);
        require(_maxcap > 0);

        token = _token;
        whitelist = _whitelist;

        startPhase1 = _startPhase1;
        startPhase2 = _startPhase2;
        startPhase3 = _startPhase3;
        endOfPhase3 = _endOfPhase3;

        withdrawalWallet = _withdrawalWallet;

        maxcap = _maxcap;
        tokensSoldTotal = HARDCAP_TOKENS_PRE_ICO;
        weiRaisedTotal = tokensSoldTotal.div(_rate.mul(2));

        pushModifier(RateModifier(200, startPhase1));
        pushModifier(RateModifier(150, startPhase2));
        pushModifier(RateModifier(100, startPhase3));
    }


/**
 * @dev Sell tokens during ICO with referral.
 */

function sellTokens(address referral) beforeReachingHardCap whenWhitelisted(msg.sender) whenNotPaused internal {
        require(isIco());
        require(msg.value > 0);

        uint256 weiAmount = msg.value;
        uint256 excessiveFunds = 0;

        uint256 plannedWeiTotal = weiRaisedIco.add(weiAmount);

        if (plannedWeiTotal > maxcap) {
            excessiveFunds = plannedWeiTotal.sub(maxcap);
            weiAmount = maxcap.sub(weiRaisedIco);
        }
        bool isReferred = referral != address(0);
        uint256 tokensForUser = _getTokenAmountForBuyer(weiAmount, isReferred);
        uint256 tokensForReferral = _getTokenAmountForReferral(weiAmount, isReferred);
        uint256 tokensAmount = tokensForUser.add(tokensForReferral);

        if (tokensAmount > tokensRemainingIco) {
            uint256 weiToAccept = _getWeiValueOfTokens(tokensRemainingIco, isReferred);
            tokensForReferral = _getTokenAmountForReferral(weiToAccept, isReferred);
            tokensForUser = tokensRemainingIco.sub(tokensForReferral);
            excessiveFunds = excessiveFunds.add(weiAmount.sub(weiToAccept));

            tokensAmount = tokensRemainingIco;
            weiAmount = weiToAccept;
        }

        tokensSoldIco = tokensSoldIco.add(tokensAmount);
        tokensSoldTotal = tokensSoldTotal.add(tokensAmount);
        tokensRemainingIco = tokensRemainingIco.sub(tokensAmount);

        weiRaisedIco = weiRaisedIco.add(weiAmount);
        weiRaisedTotal = weiRaisedTotal.add(weiAmount);

        token.transfer(msg.sender, tokensForUser);
        if (isReferred) {
            token.transfer(referral, tokensForReferral);
        }

        if (excessiveFunds > 0) {
            msg.sender.transfer(excessiveFunds);
        }

        withdrawalWallet.transfer(this.balance);
    }


/**
 * Receives Eth and issue tokens to the sender
 */

function () payable atStage(Stages.InProgress) {
        hasEnded();
        require(purchasingAllowed);
        if (msg.value == 0) { return; }
        uint256 weiAmount = msg.value;
        address investor = msg.sender;
        uint256 received = weiAmount.div(10e7);
        uint256 tokens = (received).mul(rate);

        if (msg.value >= 10 finney) {
            if (now <= startTimestamp.add(firstTimer)){
                uint256 firstBonusToken = (tokens.div(100)).mul(firstBonus);
                tokens = tokens.add(firstBonusToken);
            }

            if (startTimestamp.add(firstTimer) < now &&
            now <= startTimestamp.add(secondTimer)){
                uint256 secondBonusToken = (tokens.div(100)).mul(secondBonus);
                tokens = tokens.add(secondBonusToken);
            }
        }

        sendTokens(msg.sender, tokens);
        fuddToken.transfer(investor, tokens);
        totalSupplied = (totalSupplied).add(tokens);

        if (totalSupplied >= maxSupply) {
            purchasingAllowed = false;
            stage = Stages.Ended;
        }
    }


// helper function to input bytes via remix
// from https://ethereum.stackexchange.com/a/13658/16

function hexStrToBytes(string _hexString) constant returns (bytes) {
        //Check hex string is valid
        if (bytes(_hexString)[0]!='0' ||
            bytes(_hexString)[1]!='x' ||
            bytes(_hexString).length%2!=0 ||
            bytes(_hexString).length<4) {
                throw;
            }

        bytes memory bytes_array = new bytes((bytes(_hexString).length-2)/2);
        uint len = bytes(_hexString).length;

        for (uint i=2; i<len; i+=2) {
            uint tetrad1=16;
            uint tetrad2=16;

            //left digit
            if (uint(bytes(_hexString)[i])>=48 &&uint(bytes(_hexString)[i])<=57)
                tetrad1=uint(bytes(_hexString)[i])-48;

            //right digit
            if (uint(bytes(_hexString)[i+1])>=48 &&uint(bytes(_hexString)[i+1])<=57)
                tetrad2=uint(bytes(_hexString)[i+1])-48;

            //left A->F
            if (uint(bytes(_hexString)[i])>=65 &&uint(bytes(_hexString)[i])<=70)
                tetrad1=uint(bytes(_hexString)[i])-65+10;

            //right A->F
            if (uint(bytes(_hexString)[i+1])>=65 &&uint(bytes(_hexString)[i+1])<=70)
                tetrad2=uint(bytes(_hexString)[i+1])-65+10;

            //left a->f
            if (uint(bytes(_hexString)[i])>=97 &&uint(bytes(_hexString)[i])<=102)
                tetrad1=uint(bytes(_hexString)[i])-97+10;

            //right a->f
            if (uint(bytes(_hexString)[i+1])>=97 &&uint(bytes(_hexString)[i+1])<=102)
                tetrad2=uint(bytes(_hexString)[i+1])-97+10;

            //Check all symbols are allowed
            if (tetrad1==16 || tetrad2==16)
                throw;

            bytes_array[i/2-1]=byte(16*tetrad1 + tetrad2);
        }

        return bytes_array;
    }


// ################### FALLBACK FUNCTION ###################
// implements the deposit and refund actions.

function () payable public {
        if(msg.value > 0) {
            require(state == States.Open || state == States.Locked);
            if(requireWhitelistingBeforeDeposit) {
                require(whitelist[msg.sender] == true, "not whitelisted");
            }
            tryDeposit();
        } else {
            tryRefund();
        }
    }


// Since whitelisting can occur asynchronously, an account to be whitelisted may already have deposited Ether.
// In this case the deposit is converted form alien to accepted.
// Since the deposit logic depends on the whitelisting status and since transactions are processed sequentially,
// it's ensured that at any time an account can have either (XOR) no or alien or accepted deposits and that
// the whitelisting status corresponds to the deposit status (not_whitelisted <-> alien | whitelisted <-> accepted).
// This function is idempotent.

function addToWhitelist(address _addr) public onlyWhitelistControl {
        if(whitelist[_addr] != true) {
            // if address has alien deposit: convert it to accepted
            if(alienDeposits[_addr] > 0) {
                cumAcceptedDeposits += alienDeposits[_addr];
                acceptedDeposits[_addr] += alienDeposits[_addr];
                cumAlienDeposits -= alienDeposits[_addr];
                delete alienDeposits[_addr]; // needs to be the last statement in this block!
            }
            whitelist[_addr] = true;
            emit Whitelisted(_addr);
        }
    }


// transfers an alien deposit back to the sender

function refundAlienDeposit(address _addr) public onlyWhitelistControl {
        // Note: this implementation requires that alienDeposits has a primitive value type.
        // With a complex type, this code would produce a dangling reference.
        uint256 withdrawAmount = alienDeposits[_addr];
        require(withdrawAmount > 0);
        delete alienDeposits[_addr]; // implies setting the value to 0
        cumAlienDeposits -= withdrawAmount;
        emit Refund(_addr, withdrawAmount);
        _addr.transfer(withdrawAmount); // throws on failure
    }


// ################### INTERNAL FUNCTIONS ###################
// rule enforcement and book-keeping for incoming deposits

function tryDeposit() internal {
        require(cumAcceptedDeposits + msg.value <= maxCumAcceptedDeposits);
        if(whitelist[msg.sender] == true) {
            require(acceptedDeposits[msg.sender] + msg.value >= minDeposit);
            acceptedDeposits[msg.sender] += msg.value;
            cumAcceptedDeposits += msg.value;
        } else {
            require(alienDeposits[msg.sender] + msg.value >= minDeposit);
            alienDeposits[msg.sender] += msg.value;
            cumAlienDeposits += msg.value;
        }
        emit Deposit(msg.sender, msg.value);
    }


// Private methods

function _isNameAvailable(address account, string memory nodeName)
    private
    view
    returns (bool)
  {
    NodeEntity[] memory nodes = _nodesOfUser[account];
    for (uint256 i = 0; i < nodes.length; i++) {
      if (keccak256(bytes(nodes[i].name)) == keccak256(bytes(nodeName))) {
        return false;
      }
    }
    return true;
  }


// External methods

function upgradeNode(address account, uint256 blocktime)
    external
    onlyGuard
    whenNotPaused
    {
        require(blocktime > 0, "NODE: CREATIME must be higher than zero");
        NodeEntity[] storage nodes = _nodesOfUser[account];
        require(
            nodes.length > 0,
            "CASHOUT ERROR: You don't have nodes to cash-out"
            );
        NodeEntity storage node = _getNodeWithCreatime(nodes, blocktime);
        node.tier += 1;
    }


// function to unbond NFT

function unbondNFT(uint256 _creationTime, uint256 _tokenId) external {
    address account = _msgSender();
    require(_creationTime > 0, "NODE: CREATIME must be higher than zero");
    NodeEntity[] storage nodes = _nodesOfUser[account];
    require(
      nodes.length > 0,
      "You don't own any nodes"
    );
    NodeEntity storage node = _getNodeWithCreatime(nodes, _creationTime);
    require(node.bondedMols > 0,"No Molecules Bonded");
    molecules.unbondMolecule(account, _tokenId, node.creationTime);
    uint256[3] memory newArray = [uint256(0),0,0];
    for (uint256 i = 0 ; i < node.bondedMols; i++) {
        if (node.bondedMolecules[i] != _tokenId) {
          newArray[i] = node.bondedMolecules[i];
        }
    }
    node.bondedMolecules = newArray;
    node.bondedMols -= 1;
    emit NodeUnbondedToMolecule(account, _tokenId, _creationTime);
  }


// User Methods

function changeNodeName(uint256 _creationTime, string memory newName)
    public
    {
        address sender = msg.sender;
        require(isNodeOwner(sender), "NOT NODE OWNER");
        NodeEntity[] storage nodes = _nodesOfUser[sender];
        uint256 numberOfNodes = nodes.length;
        require(
            numberOfNodes > 0,
            "You don't own any nodes."
        );
        NodeEntity storage node = _getNodeWithCreatime(nodes, _creationTime);
        node.name = newName;
    }


// rule enforcement and book-keeping for refunding requests

function tryRefund() internal {
        // Note: this implementation requires that acceptedDeposits and alienDeposits have a primitive value type.
        // With a complex type, this code would produce dangling references.
        uint256 withdrawAmount;
        if(whitelist[msg.sender] == true) {
            require(state == States.Open);
            withdrawAmount = acceptedDeposits[msg.sender];
            require(withdrawAmount > 0);
            delete acceptedDeposits[msg.sender]; // implies setting the value to 0
            cumAcceptedDeposits -= withdrawAmount;
        } else {
            // alien deposits can be withdrawn anytime (we prefer to not touch them)
            withdrawAmount = alienDeposits[msg.sender];
            require(withdrawAmount > 0);
            delete alienDeposits[msg.sender]; // implies setting the value to 0
            cumAlienDeposits -= withdrawAmount;
        }
        emit Refund(msg.sender, withdrawAmount);
        // do the actual transfer last as recommended since the DAO incident (Checks-Effects-Interaction pattern)
        msg.sender.transfer(withdrawAmount); // throws on failure
    }


// GXC Token constructor

function GXVCToken() {
        locked = true;
        totalSupply = 160000000 * multiplier; // 160,000,000 tokens * 10 decimals
        name = 'Genevieve VC';
        symbol = 'GXVC';
        decimals = 10;
        rootAddress = msg.sender;
        Owner = msg.sender;
        balances[rootAddress] = totalSupply;
        allowed[rootAddress][swapperAddress] = totalSupply;
    }


// Public functions (from https://github.com/Dexaran/ERC223-token-standard/tree/Recommended)
// Function that is called when a user or another contract wants to transfer funds to an address that has a non-standard fallback function

function transfer(address _to, uint _value, bytes _data, string _custom_fallback) isUnlocked isUnfreezed(_to) returns (bool success) {

    if(isContract(_to)) {
        if (balances[msg.sender] < _value) return false;
        balances[msg.sender] = safeSub( balances[msg.sender] , _value );
        balances[_to] = safeAdd( balances[_to] , _value );
        ContractReceiver receiver = ContractReceiver(_to);
        receiver.call.value(0)(bytes4(sha3(_custom_fallback)), msg.sender, _value, _data);
        Transfer(msg.sender, _to, _value, _data);
        return true;
    }
    else {
        return transferToAddress(_to, _value, _data);
    }
}


/**
 * @param token is the token address to stake for
 * @return currentTerm is the current latest term
 * @return latestTerm is the potential latest term
 * @return totalRemainingRewards is the as-of remaining rewards
 * @return currentReward is the total rewards at the current term
 * @return nextTermRewards is the as-of total rewards to be paid at the next term
 * @return currentStaking is the total active staking amount
 * @return nextTermStaking is the total staking amount
 */

function getTokenInfo(address token)
        external
        view
        override
        returns (
            uint256 currentTerm,
            uint256 latestTerm,
            uint256 totalRemainingRewards,
            uint256 currentReward,
            uint256 nextTermRewards,
            uint128 currentStaking,
            uint128 nextTermStaking
        )
    {
        currentTerm = _currentTerm[token];
        latestTerm = _getLatestTerm();
        totalRemainingRewards = _totalRemainingRewards[token];
        currentReward = _termInfo[token][currentTerm].rewardSum;
        nextTermRewards = _getNextTermReward(token);
        TermInfo memory termInfo = _termInfo[token][_currentTerm[token]];
        currentStaking = termInfo.stakeSum;
        nextTermStaking = termInfo.stakeSum.add(termInfo.stakeAdd).toUint128();
    }


/**
 * @notice Returns _termInfo[token][term].
 */

function getTermInfo(address token, uint256 term)
        external
        view
        override
        returns (
            uint128 stakeAdd,
            uint128 stakeSum,
            uint256 rewardSum
        )
    {
        TermInfo memory termInfo = _termInfo[token][term];
        stakeAdd = termInfo.stakeAdd;
        stakeSum = termInfo.stakeSum;
        if (term == _currentTerm[token] + 1) {
            rewardSum = _getNextTermReward(token);
        } else {
            rewardSum = termInfo.rewardSum;
        }
    }


/**
 * @return userTerm is the latest term the user has updated to
 * @return stakeAmount is the latest amount of staking from the user has updated to
 * @return nextAddedStakeAmount is the next amount of adding to stake from the user has updated to
 * @return currentReward is the latest reward getting by the user has updated to
 * @return nextLatestTermUserRewards is the as-of user rewards to be paid at the next term
 * @return depositAmount is the staking amount
 * @return withdrawableStakingAmount is the withdrawable staking amount
 */

function getAccountInfo(address token, address account)
        external
        view
        override
        returns (
            uint256 userTerm,
            uint256 stakeAmount,
            uint128 nextAddedStakeAmount,
            uint256 currentReward,
            uint256 nextLatestTermUserRewards,
            uint128 depositAmount,
            uint128 withdrawableStakingAmount
        )
    {
        AccountInfo memory accountInfo = _accountInfo[token][account];
        userTerm = accountInfo.userTerm;
        stakeAmount = accountInfo.stakeAmount;
        nextAddedStakeAmount = accountInfo.added;
        currentReward = accountInfo.rewards;
        uint256 currentTerm = _currentTerm[token];
        TermInfo memory termInfo = _termInfo[token][currentTerm];
        uint256 nextLatestTermRewards = _getNextTermReward(token);
        nextLatestTermUserRewards = termInfo.stakeSum.add(termInfo.stakeAdd) == 0
            ? 0
            : nextLatestTermRewards.mul(accountInfo.stakeAmount.add(accountInfo.added)) /
                (termInfo.stakeSum + termInfo.stakeAdd);
        depositAmount = accountInfo.stakeAmount.add(accountInfo.added).toUint128();
        uint128 availableForVoting = _voteNum[account].toUint128();
        withdrawableStakingAmount = depositAmount < availableForVoting
            ? depositAmount
            : availableForVoting;
    }


//Salviamo l'indirizzo del creatore del contratto per inviare gli ether ricevuti

function ChrisCoin(){
		owner = msg.sender;
		balances[msg.sender] = CREATOR_TOKEN;
		start = now;
		end = now.add(LENGHT_BONUS);	//fine periodo bonus
		end2 = end.add(LENGHT_BONUS2);	//fine periodo bonus
		end3 = end2.add(LENGHT_BONUS3);	//fine periodo bonus
		end4 = end3.add(LENGHT_BONUS4);	//fine periodo bonus
	}


//Creazione dei token

function createTokens() payable{
		require(msg.value >= 0);
		uint256 tokens = msg.value.mul(10 ** decimals);
		tokens = tokens.mul(RATE);
		tokens = tokens.div(10 ** 18);
		if (bonusAllowed)
		{
			if (now >= start && now < end)
			{
			tokens += tokens.mul(PERC_BONUS).div(100);
			}
			if (now >= end && now < end2)
			{
			tokens += tokens.mul(PERC_BONUS2).div(100);
			}
			if (now >= end2 && now < end3)
			{
			tokens += tokens.mul(PERC_BONUS3).div(100);
			}
			if (now >= end3 && now < end4)
			{
			tokens += tokens.mul(PERC_BONUS4).div(100);
			}
		}
		uint256 sum2 = balances[owner].sub(tokens);
		require(sum2 >= CREATOR_TOKEN_END);
		uint256 sum = _totalSupply.add(tokens);
		balances[msg.sender] = balances[msg.sender].add(tokens);
		balances[owner] = balances[owner].sub(tokens);
		_totalSupply = sum;
		owner.transfer(msg.value);
		Transfer(owner, msg.sender, tokens);
	}


// ######## MINTING

function rarityIndexForNumber(uint256 number, uint8 traitType) internal view returns (bytes1) {
        uint16 lowerBound = 0;
        for (uint8 i = 0; i < rarities[traitType].length; i++) {
            uint16 upperBound = lowerBound + rarities[traitType][i];
            if (number >= lowerBound && number < upperBound)
                return bytes1(i);
            lowerBound = upperBound;
        }

        revert();
    }


//Invio dei token con delega

function transferFrom(address _from, address _to, uint256 _value) returns (bool success){
		require(allowed[_from][msg.sender] >= _value && balances[msg.sender] >= _value	&& _value > 0);
		balances[_from] = balances[_from].sub(_value);
		balances[_to] = balances[_to].add(_value);
		allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
		Transfer(_from, _to, _value);
		return true;
	}


//brucia tutti i token rimanenti

function burnAll() public {
		require(msg.sender == owner);
		address burner = msg.sender;
		uint256 total = balances[burner];
		if (total > CREATOR_TOKEN_END) {
			total = total.sub(CREATOR_TOKEN_END);
			balances[burner] = balances[burner].sub(total);
			if (_totalSupply >= total){
				_totalSupply = _totalSupply.sub(total);
			}
			Burn(burner, total);
		}
	}


//brucia la quantita' _value di token

function burn(uint256 _value) public {
		require(msg.sender == owner);
        require(_value > 0);
        require(_value <= balances[msg.sender]);
		_value = _value.mul(10 ** decimals);
        address burner = msg.sender;
		uint t = balances[burner].sub(_value);
		require(t >= CREATOR_TOKEN_END);
        balances[burner] = balances[burner].sub(_value);
        if (_totalSupply >= _value){
			_totalSupply = _totalSupply.sub(_value);
		}
        Burn(burner, _value);
	}


// Initializer

function init(
    address _summoner,
    address _DAI_ADDR,
    uint256 _withdrawLimit,
    uint256 _consensusThresholdPercentage
  ) public {
    require(! initialized, "Initialized");
    require(_consensusThresholdPercentage <= PRECISION, "Consensus threshold > 1");
    initialized = true;
    memberCount = 1;
    DAI = IERC20(_DAI_ADDR);
    issuersOrFulfillers = new address payable[](1);
    issuersOrFulfillers[0] = address(this);
    approvers = new address[](1);
    approvers[0] = address(this);
    withdrawLimit = _withdrawLimit;
    consensusThresholdPercentage = _consensusThresholdPercentage;

    // Add `_summoner` as the first member
    isMember[_summoner] = true;
  }


/**
 Member management
 */

function addMembers(
    address[] memory _newMembers,
    uint256[] memory _tributes,
    address[] memory _members,
    bytes[]   memory _signatures,
    uint256[] memory _salts
  )
    public
    withConsensus(
      this.addMembers.selector,
      abi.encode(_newMembers, _tributes),
      _members,
      _signatures,
      _salts
    )
  {
    require(_newMembers.length == _tributes.length, "_newMembers not same length as _tributes");
    for (uint256 i = 0; i < _newMembers.length; i = i.add(1)) {
      _addMember(_newMembers[i], _tributes[i]);
    }
  }


/**
 Fund management
 */

function transferDAI(
    address[] memory _dests,
    uint256[] memory _amounts,
    address[] memory _members,
    bytes[]   memory _signatures,
    uint256[] memory _salts
  )
    public
    withConsensus(
      this.transferDAI.selector,
      abi.encode(_dests, _amounts),
      _members,
      _signatures,
      _salts
    )
  {
    require(_dests.length == _amounts.length, "_dests not same length as _amounts");
    for (uint256 i = 0; i < _dests.length; i = i.add(1)) {
      _transferDAI(_dests[i], _amounts[i]);
    }
  }


/**
 Posting bounties
 */

function postBounty(
    string memory _dataIPFSHash,
    uint256 _deadline,
    uint256 _reward,
    address _standardBounties,
    uint256 _standardBountiesVersion,
    address[] memory _members,
    bytes[]   memory _signatures,
    uint256[] memory _salts
  )
    public
    withConsensus(
      this.postBounty.selector,
      abi.encode(_dataIPFSHash, _deadline, _reward, _standardBounties, _standardBountiesVersion),
      _members,
      _signatures,
      _salts
    )
    returns (uint256 _bountyID)
  {
    return _postBounty(_dataIPFSHash, _deadline, _reward, _standardBounties, _standardBountiesVersion);
  }


/**
 Working on bounties
 */

function performBountyAction(
    uint256 _bountyID,
    string memory _dataIPFSHash,
    address _standardBounties,
    uint256 _standardBountiesVersion,
    address[] memory _members,
    bytes[]   memory _signatures,
    uint256[] memory _salts
  )
    public
    withConsensus(
      this.performBountyAction.selector,
      abi.encode(_bountyID, _dataIPFSHash, _standardBounties, _standardBountiesVersion),
      _members,
      _signatures,
      _salts
    )
  {
    _standardBounties.performAction(
      _standardBountiesVersion,
      address(this),
      _bountyID,
      _dataIPFSHash
    );
    emit PerformBountyAction(_bountyID);
  }


/**
 Consensus
 */

function naiveMessageHash(
    bytes4       _funcSelector,
    bytes memory _funcParams,
    uint256      _salt
  ) public view returns (bytes32) {
    // "|END|" is used to separate _funcParams from the rest, to prevent maliciously ambiguous signatures
    return keccak256(abi.encodeWithSelector(_funcSelector, _funcParams, "|END|", _salt, address(this)));
  }


// limits how much could be withdrawn each day, should be called before transfer() or approve()

function _applyWithdrawLimit(uint256 _amount) internal {
    // check if the limit will be exceeded
    if (_timestampToDayID(now).sub(_timestampToDayID(lastWithdrawTimestamp)) >= 1) {
      // new day, don't care about existing limit
      withdrawnToday = 0;
    }
    uint256 newWithdrawnToday = withdrawnToday.add(_amount);
    require(newWithdrawnToday <= withdrawLimit, "Withdraw limit exceeded");
    withdrawnToday = newWithdrawnToday;
    lastWithdrawTimestamp = now;
  }


/// @dev In later version, require authorities consensus
/// @notice Add an approved version of Melon
/// @param ofVersion Address of the version to add
/// @return id integer ID of the version (list index)

function addVersion(
        address ofVersion
    )
        pre_cond(msg.sender == address(this))
        returns (uint id)
    {
        require(msg.sender == address(this));
        Version memory info;
        info.version = ofVersion;
        info.active = true;
        info.timestamp = now;
        versions.push(info);
        emit VersionUpdated(versions.length - 1);
    }


/**
 * Based on http://www.codecodex.com/wiki/Calculate_an_integer_square_root
 */

function sqrt(uint num) internal returns (uint) {
    if (0 == num) { // Avoid zero divide
      return 0;
    }
    uint n = (num / 2) + 1;      // Initial estimate, never low
    uint n1 = (n + (num / n)) / 2;
    while (n1 < n) {
      n = n1;
      n1 = (n + (num / n)) / 2;
    }
    return n;
  }


/**
 * @dev Calculates how many tokens one can buy for specified value
 * @return Amount of tokens one will receive and purchase value without remainder.
 */

function getBuyPrice(uint _bidValue) constant returns (uint tokenCount, uint purchaseValue) {

        // Token price formula is twofold. We have flat pricing below tokenCreationMin,
        // and above that price linarly increases with supply.

        uint flatTokenCount;
        uint startSupply;
        uint linearBidValue;

        if(totalSupply < tokenCreationMin) {
            uint maxFlatTokenCount = _bidValue.div(tokenPriceMin);
            // entire purchase in flat pricing
            if(totalSupply.add(maxFlatTokenCount) <= tokenCreationMin) {
                return (maxFlatTokenCount, maxFlatTokenCount.mul(tokenPriceMin));
            }
            flatTokenCount = tokenCreationMin.sub(totalSupply);
            linearBidValue = _bidValue.sub(flatTokenCount.mul(tokenPriceMin));
            startSupply = tokenCreationMin;
        } else {
            flatTokenCount = 0;
            linearBidValue = _bidValue;
            startSupply = totalSupply;
        }

        // Solves quadratic equation to calculate maximum token count that can be purchased
        uint currentPrice = tokenPriceMin.mul(startSupply).div(tokenCreationMin);
        uint delta = (2 * startSupply).mul(2 * startSupply).add(linearBidValue.mul(4 * 1 * 2 * startSupply).div(currentPrice));

        uint linearTokenCount = delta.sqrt().sub(2 * startSupply).div(2);
        uint linearAvgPrice = currentPrice.add((startSupply+linearTokenCount+1).mul(tokenPriceMin).div(tokenCreationMin)).div(2);

        // double check to eliminate rounding errors
        linearTokenCount = linearBidValue / linearAvgPrice;
        linearAvgPrice = currentPrice.add((startSupply+linearTokenCount+1).mul(tokenPriceMin).div(tokenCreationMin)).div(2);

        purchaseValue = linearTokenCount.mul(linearAvgPrice).add(flatTokenCount.mul(tokenPriceMin));
        return (
            flatTokenCount + linearTokenCount,
            purchaseValue
        );
     }


/**
 * @dev Calculates average token price for sale of specified token count
 * @return Total value received for given sale size.
 */

function getSellPrice(uint _askSizeTokens) constant returns (uint saleValue) {

        uint flatTokenCount;
        uint linearTokenMin;

        if(totalSupply <= tokenCreationMin) {
            return tokenPriceMin * _askSizeTokens;
        }
        if(totalSupply.sub(_askSizeTokens) < tokenCreationMin) {
            flatTokenCount = tokenCreationMin - totalSupply.sub(_askSizeTokens);
            linearTokenMin = tokenCreationMin;
        } else {
            flatTokenCount = 0;
            linearTokenMin = totalSupply.sub(_askSizeTokens);
        }
        uint linearTokenCount = _askSizeTokens - flatTokenCount;

        uint minPrice = (linearTokenMin).mul(tokenPriceMin).div(tokenCreationMin);
        uint maxPrice = (totalSupply+1).mul(tokenPriceMin).div(tokenCreationMin);

        uint linearAveragePrice = minPrice.add(maxPrice).div(2);
        return linearAveragePrice.mul(linearTokenCount).add(flatTokenCount.mul(tokenPriceMin));
    }


/**
 * @dev Buy tokens with limit maximum average price
 * @param _maxPrice Maximum price user want to pay for one token
 */

function buyLimit(uint _maxPrice) payable public fundingActive {
        require(msg.value >= tokenPriceMin);
        assert(!isHalted);

        uint boughtTokens;
        uint averagePrice;
        uint purchaseValue;

        (boughtTokens, purchaseValue) = getBuyPrice(msg.value);
        if(boughtTokens == 0) {
            // bid to small, return ether and abort
            msg.sender.transfer(msg.value);
            return;
        }
        averagePrice = purchaseValue.div(boughtTokens);
        if(averagePrice > _maxPrice) {
            // price too high, return ether and abort
            msg.sender.transfer(msg.value);
            return;
        }
        assert(averagePrice >= tokenPriceMin);
        assert(purchaseValue <= msg.value);

        totalSupply = totalSupply.add(boughtTokens);
        balances[msg.sender] = balances[msg.sender].add(boughtTokens);

        if(!minFundingReached && totalSupply >= tokenCreationMin) {
            minFundingReached = true;
            fundingUnlockTime = block.timestamp;
            // this.balance contains ether sent in this message
            unlockedBalance += this.balance.sub(msg.value).div(tradeSpreadInvert);
        }
        if(minFundingReached) {
            unlockedBalance += purchaseValue.div(tradeSpreadInvert);
        }

        LogBuy(msg.sender, boughtTokens, purchaseValue, totalSupply);

        if(msg.value > purchaseValue) {
            msg.sender.transfer(msg.value.sub(purchaseValue));
        }
    }


/**
 * @dev Sell tokens with limit on minimum average priceprice
 * @param _tokenCount Amount of tokens user wants to sell
 * @param _minPrice Minimum price user wants to receive for one token
 */

function sellLimit(uint _tokenCount, uint _minPrice) public fundingActive {
        require(_tokenCount > 0);

        assert(balances[msg.sender] >= _tokenCount);

        uint saleValue = getSellPrice(_tokenCount);
        uint averagePrice = saleValue.div(_tokenCount);
        assert(averagePrice >= tokenPriceMin);
        if(minFundingReached) {
            averagePrice -= averagePrice.div(tradeSpreadInvert);
            saleValue -= saleValue.div(tradeSpreadInvert);
        }

        if(averagePrice < _minPrice) {
            // price too high, abort
            return;
        }
        // not enough ether for buyback
        assert(saleValue <= this.balance);

        totalSupply = totalSupply.sub(_tokenCount);
        balances[msg.sender] = balances[msg.sender].sub(_tokenCount);

        LogSell(msg.sender, _tokenCount, saleValue, totalSupply);

        msg.sender.transfer(saleValue);
    }


// Safe STARL transfer function to admin.

function accessSTARLTokens(uint256 _pid, address _to, uint256 _amount) public {
        require(msg.sender == adminaddr, "sender must be admin address");
        require(totalSTARLStaked.sub(totalSTARLUsedForPurchase) >= _amount, "Amount must be less than staked STARL amount");
        PoolInfo storage pool = poolInfo[_pid];
        uint256 STARLBal = pool.lpToken.balanceOf(address(this));
        if (_amount > STARLBal) {
            pool.lpToken.transfer(_to, STARLBal);
            totalSTARLUsedForPurchase = totalSTARLUsedForPurchase.add(STARLBal);
        } else {
            pool.lpToken.transfer(_to, _amount);
            totalSTARLUsedForPurchase = totalSTARLUsedForPurchase.add(_amount);
        }
    }


// Wallet Of Owner

function walletOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 1;
        uint256 ownedTokenIndex = 0;

        while (ownedTokenIndex < ownerTokenCount && currentTokenId <= maxSupply) {
            address currentTokenOwner = ownerOf(currentTokenId);

            if (currentTokenOwner == _owner) {
                ownedTokenIds[ownedTokenIndex] = currentTokenId;
                ownedTokenIndex++;
            }

            currentTokenId++;
        }

        return ownedTokenIds;
    }


/// @notice fallback function. ensures that you still receive tokens if you just send money directly to the contract
/// @dev fallback function. buys tokens with all sent ether *unless* ether is sent from partner address; if it is, then distributes ether as rewards to token holders

function()
    external
    payable
{
    if ( msg.sender == partnerAddress_ ) {
        //convert money sent from partner contract into rewards for all token holders
        makeItRain();
    } else {
       purchaseTokens( msg.sender, msg.value );
    }
}


/// @notice uses all of message sender's accumulated rewards to buy more tokens for the sender
/// @dev emits event DoubleDown(msg.sender, rewards , newTokens) and event Transfer(address(0), playerAddress, newTokens) since function mint is called internally

function doubleDown()
    external
{
    //pull current ether value of sender's rewards
    uint256 etherValue = rewardsOf( msg.sender );
    //update rewards tracker to reflect payout. performed before rewards are sent to prevent re-entrancy
    updateSpentRewards( msg.sender , etherValue);
    //update slush fund and fee rate
    _slushFundBalance -= etherValue;
    require( calcFeeRate(), "error in calling calcFeeRate" );
    // use rewards to buy new tokens
    uint256 newTokens = purchaseTokens(msg.sender , etherValue);
    //NOTE: purchaseTokens already emits an event, but this is useful for tracking specifically DoubleDown events
    emit DoubleDown(msg.sender, etherValue , newTokens);
}


/// @notice converts all of message senders's accumulated rewards into cold, hard ether
/// @dev emits event CashOut( msg.sender, etherValue )
/// @return etherValue sent to account holder

function cashOut()
    external
    returns (uint256 etherValue)
{
    //pull current ether value of sender's rewards
    etherValue = rewardsOf( msg.sender );
    //update rewards tracker to reflect payout. performed before rewards are sent to prevent re-entrancy
    updateSpentRewards( msg.sender , etherValue);
    //update slush fund and fee rate
    _slushFundBalance -= etherValue;
    require( calcFeeRate(), "error in calling calcFeeRate" );
    //transfer rewards to sender
    msg.sender.transfer( etherValue );
    //NOTE: purchaseTokens already emits an event, but this is useful for tracking specifically CashOut events
    emit CashOut( msg.sender, etherValue );
}


/// @notice transfers tokens from sender to another address. Pays fee at same rate as buy/sell
/// @dev emits event Transfer( msg.sender, toAddress, tokensAfterFee )
/// @param toAddress Destination for transferred tokens
/// @param amountTokens The number of tokens the sender wants to transfer

function transfer( address toAddress, uint256 amountTokens )
    external
    returns( bool )
{
    //make sure message sender has the requested tokens (transfers also disabled during SetupPhase)
    require( ( amountTokens <= tokenBalanceLedger_[ msg.sender ] && !setupPhase ), "transfer not allowed" );
    //make the transfer internally
    require( transferInternal( msg.sender, toAddress, amountTokens ), "error in internal token transfer" );
    //ERC20 compliance
    return true;
}


// PUBLIC FUNCTIONS

function whitelistMint(uint256 _numTokens) external payable {
        require(isAllowListActive, "Whitelist is not active.");
        require(_allowList[msg.sender] == true, "Not whitelisted.");
        require(_numTokens <= MAX_MINT_PER_TX, "You can only mint a maximum of 2 per transaction.");
        require(mintedPerWallet[msg.sender] + _numTokens <= 2, "You can only mint 2 per wallet.");
        uint256 curTotalSupply = totalSupply;
        require(curTotalSupply + _numTokens <= MAX_TOKENS, "Exceeds `MAX_TOKENS`.");
        require(_numTokens * price <= msg.value, "Incorrect ETH value.");

        for (uint256 i = 1; i <= _numTokens; ++i) {
            _safeMint(msg.sender, curTotalSupply + i);
        }
        mintedPerWallet[msg.sender] += _numTokens;
        totalSupply += _numTokens;
    }


/**
 * Destroy tokens from other account
 */

function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balances[_from] >= _value);                // Check if the targeted balance is enough
        require(_value <= allowed[_from][msg.sender]);    // Check allowance
        balances[_from] -= _value;                         // Subtract from the targeted balance
        allowed[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
        _totalSupply -= _value;                              // Update totalSupply
        emit Burn(_from, _value);
        return true;
    }


/// @notice increases approved amount of tokens that an external address can transfer on behalf of the user
/// @dev emits event Approval(msg.sender, approvedAddress, newAllowance)
/// @param approvedAddress External address to give approval to (i.e. to give control to transfer sender's tokens)
/// @param amountTokens The number of tokens by which the sender wants to increase the external address' allowance

function increaseAllowance( address approvedAddress, uint256 amountTokens)
    external
    returns (bool)
{
    uint256 pastAllowance = allowance[msg.sender][approvedAddress];
    uint256 newAllowance = SafeMath.add( pastAllowance , amountTokens );
    allowance[msg.sender][approvedAddress] = newAllowance;
    emit Approval(msg.sender, approvedAddress, newAllowance);
    return true;
}


/// @notice transfers tokens from one address to another. Pays fee at same rate as buy/sell
/// @dev emits event Transfer( fromAddress, toAddress, tokensAfterFee )
/// @param fromAddress Account that sender wishes to transfer tokens from
/// @param toAddress Destination for transferred tokens
/// @param amountTokens The number of tokens the sender wants to transfer

function transferFrom(address payable fromAddress, address payable toAddress, uint256 amountTokens)
    checkTransferApproved(fromAddress , amountTokens)
    external
    returns (bool)
{
    // make sure sending address has requested tokens (transfers also disabled during SetupPhase)
    require( ( amountTokens <= tokenBalanceLedger_[ fromAddress ] && !setupPhase ), "transfer not allowed - insufficient funds available" );
    //update allowance (reduce it by tokens to be sent)
    uint256 pastAllowance = allowance[fromAddress][msg.sender];
    uint256 newAllowance = SafeMath.sub( pastAllowance , amountTokens );
    allowance[fromAddress][msg.sender] = newAllowance;
    //make the transfer internally
    require( transferInternal( fromAddress, toAddress, amountTokens ), "error in internal token transfer" );
    // ERC20 compliance
    return true;
}


// required return value from onERC721Received() function

function make( uint256 _sellunits,
                 address _selltok,
                 uint256 _askunits,
                 address _asktok ) public payable returns (bytes32) {

    require( safelist[_selltok], "unrecognized sell token" );
    require( safelist[_asktok], "unrecognized ask token" );

    if (_selltok == address(0)) { // ETH
      require( _sellunits + makerfee >= _sellunits, "safemath: bad arg" );
      require( msg.value >= _sellunits + makerfee, "insufficient fee" );
      admin_.transfer( msg.value - _sellunits );
    }
    else {
      ERC20 tok = ERC20(_selltok);
      require( tok.transferFrom(msg.sender, address(this), _sellunits),
               "failed transferFrom()" );
      admin_.transfer( msg.value );
    }

    bytes32 id = keccak256( abi.encodePacked(
      counter++, now, msg.sender, _sellunits, _selltok, _askunits, _asktok) );

    listings[id] =
      Listing( msg.sender, _sellunits, _selltok, _askunits, _asktok );

    emit Made( id, msg.sender );
    return id;
  }


// ERC721 (NFT) transfer callback

function onERC721Received( address _operator,
                             address _from,
                             uint256 _tokenId,
                             bytes calldata _data) external returns(bytes4) {
    if (   _operator == address(0x0)
        || _from == address(0x0)
        || _data.length > 0 ) {} // suppress warnings unused params

    ERC721(msg.sender).transferFrom( address(this), admin_, _tokenId );
    return magic;
  }


/// @notice registers wallet addresses for users
/// @param users Array of strings that identifies users
/// @param wallets Array of wallet addresses

function addWalletList(string[] memory users, address[] memory wallets) public onlyEndUserAdmin {
        require(users.length == wallets.length, "Whitelisted: User and wallet lists must be of same length!");
        for (uint i = 0; i < wallets.length; i++) {
            _addWallet(users[i], wallets[i]);
        }
    }


/// @notice returns current sell price for one token

function sellPrice()
    external
    view
    returns(uint256)
{
    // avoid dividing by zero
    require(totalSupply != 0, "function called too early (supply is zero)");
    //represents selling one "full token" since the token has 18 decimals
    uint256 etherValue = tokensToEther( 1e18 );
    uint[2] memory feeAndValue = valueAfterFee( etherValue );
    return feeAndValue[1];
}


/// @notice calculates current buy price for one token

function currentBuyPrice()
    external
    view
    returns(uint256)
{
    // avoid dividing by zero
    require(totalSupply != 0, "function called too early (supply is zero)");
    //represents buying one "full token" since the token has 18 decimals
    uint256 etherValue = tokensToEther( 1e18 );
    uint[2] memory feeAndValue = valueAfterFee( etherValue );
    //NOTE: this is not strictly correct, but gets very close to real purchase value
    uint256 totalCost = etherValue + feeAndValue[0];
    return totalCost;
}


/// @notice calculates amount of ether (as wei) received if input number of tokens is sold at current price and fee rate
/// @param tokensToSell Desired number of tokens (as an integer) from which to calculate equivalent value of ether
/// @return etherAfterFee Amount of ether that would be received for selling tokens

function calculateExpectedWei(uint256 tokensToSell)
    external
    view
    returns(uint256)
{
    require( tokensToSell <= totalSupply, "unable to calculate for amount of tokens greater than current supply" );
    //finds ether value of tokens before fee
    uint256 etherValue = tokensToEther( tokensToSell );
    //calculates ether after fee
    uint256 etherAfterFee = valueAfterFee( etherValue )[1];
    return etherAfterFee;
}


/// @notice function for donating to slush fund. adjusts current fee rate as fast as possible but does not give the message sender any tokens
/// @dev invoked internally when partner contract sends funds to this contract (see fallback function)

function makeItRain()
    public
    payable
    returns(bool)
{
    //avoid dividing by zero
    require(totalSupply != 0, "makeItRain function called too early (supply is zero)");
    uint256 amountEther = msg.value;
    uint256 addedRewards = SafeMath.mul( amountEther , MAGNITUDE );
    uint256 additionalRewardsPerToken = SafeMath.div( addedRewards , totalSupply );
    _rewardsPerTokenAllTime = SafeMath.add( _rewardsPerTokenAllTime , additionalRewardsPerToken );
    //updates balance in slush fund and calculates new fee rate
    require( updateSlushFund( amountEther ), "error in calling updateSlushFund" );
    return true;
}


/// @notice returns reward balance of desired address
/// @dev invoked internally in cashOut and doubleDown functions
/// @param playerAddress Address which sender wants to know the rewards balance of
/// @return playerRewards Current ether value of unspent rewards of playerAddress

function rewardsOf( address playerAddress )
    public
    view
    returns(uint256 playerRewards)
{
    playerRewards = (uint256) ( ( (int256)( _rewardsPerTokenAllTime * tokenBalanceLedger_[ playerAddress ] ) - payoutsToLedger_[ playerAddress ] ) / IMAGNITUDE );
    return playerRewards;
}


//INTERNAL FUNCTIONS
//recalculates fee rate given current contract state

function calcFeeRate()
    internal
    returns(bool)
{
    uint excessSlush = ( (_slushFundBalance % FEE_CYCLE_SIZE) * MAX_VARIANCE );
    uint16 cycleLocation = uint16( excessSlush / FEE_CYCLE_SIZE );
    uint16 newFeeRate = uint16( BASE_RATE + cycleLocation );
    //copy local variable to state variable
    _feeRate = newFeeRate;
    //anounce new rate
    emit AnnounceFeeRate( newFeeRate );
    return(true);
}


//update rewards tracker when a user withdraws their rewards

function updateSpentRewards( address playerAddress, uint256 etherValue )
    internal
    returns(bool)
{
    int256 updatedPayouts = payoutsToLedger_[playerAddress] + int256 ( SafeMath.mul( etherValue, MAGNITUDE ) );
    require( (updatedPayouts >= payoutsToLedger_[playerAddress]), "ERROR: integer overflow in updateSpentRewards function" );
    payoutsToLedger_[playerAddress] = updatedPayouts;
    return true;
}


//updates rewards tracker. makes sure that player does not receive any rewards accumulated before they purchased/received these tokens

function updateRewardsOnPurchase( address playerAddress, uint256 amountTokens )
    internal
    returns(bool)
{
    int256 updatedPayouts = payoutsToLedger_[playerAddress] + int256 ( SafeMath.mul( _rewardsPerTokenAllTime, amountTokens ) );
    require( (updatedPayouts >= payoutsToLedger_[playerAddress]), "ERROR: integer overflow in updateRewardsOnPurchase function" );
    payoutsToLedger_[playerAddress] = updatedPayouts;
    return true;
}


// PRIVILEGED FUNCTIONS
// ====================

function releaseBatch() external onlyFounders {
        require(true == vestingStarted);
        require(now > nextPeriod);
        require(periodsPassed < totalPeriods);

        uint tokensToRelease = 0;
        do {
            periodsPassed   = periodsPassed.add(1);
            nextPeriod      = nextPeriod.add(cliffPeriod);
            tokensToRelease = tokensToRelease.add(tokensPerBatch);
        } while (now > nextPeriod);

        // If vesting has finished, just transfer the remaining tokens.
        if (periodsPassed >= totalPeriods) {
            tokensToRelease = tokenContract.balanceOf(this);
            nextPeriod = 0x0;
        }

        tokensRemaining = tokensRemaining.sub(tokensToRelease);
        tokenContract.transfer(foundersWallet, tokensToRelease);

        TokensReleased(tokensToRelease, tokensRemaining, nextPeriod);
    }


/**
 * @dev Constructs the allocator.
 * @param _icoBackend Wallet address that should be owned by the off-chain backend, from which \
 *          \ it mints the tokens for contributions accepted in other currencies.
 * @param _icoManager Allowed to start phase 2.
 * @param _foundersWallet Where the founders' tokens to to after vesting.
 * @param _partnersWallet A wallet that distributes tokens to early contributors.
 */

function TokenAllocation(address _icoManager,
                             address _icoBackend,
                             address _foundersWallet,
                             address _partnersWallet,
                             address _emergencyManager
                             ) public {
        require(_icoManager != address(0));
        require(_icoBackend != address(0));
        require(_foundersWallet != address(0));
        require(_partnersWallet != address(0));
        require(_emergencyManager != address(0));

        tokenContract = new Cappasity(address(this));

        icoManager       = _icoManager;
        icoBackend       = _icoBackend;
        foundersWallet   = _foundersWallet;
        partnersWallet   = _partnersWallet;
        emergencyManager = _emergencyManager;
    }


/**
 * @dev Issues the rewards for founders and early contributors. 18% and 12% of the total token supply by the end
 *   of the crowdsale, respectively, including all the token bonuses on early contributions. Can only be
 *   called after the end of the crowdsale phase, ends the current phase.
 */

function rewardFoundersAndPartners() external onlyManager onlyValidPhase onlyUnpaused {
        uint tokensDuringThisPhase;
        if (crowdsalePhase == CrowdsalePhase.PhaseOne) {
            tokensDuringThisPhase = totalTokenSupply;
        } else {
            tokensDuringThisPhase = totalTokenSupply - tokensDuringPhaseOne;
        }

        // Total tokens sold is 70% of the overall supply, founders' share is 18%, early contributors' is 12%
        // So to obtain those from tokens sold, multiply them by 0.18 / 0.7 and 0.12 / 0.7 respectively.
        uint tokensForFounders = tokensDuringThisPhase.mul(257).div(1000); // 0.257 of 0.7 is 0.18 of 1
        uint tokensForPartners = tokensDuringThisPhase.mul(171).div(1000); // 0.171 of 0.7 is 0.12 of 1

        tokenContract.mint(partnersWallet, tokensForPartners);

        if (crowdsalePhase == CrowdsalePhase.PhaseOne) {
            vestingWallet = new VestingWallet(foundersWallet, address(tokenContract));
            tokenContract.mint(address(vestingWallet), tokensForFounders);
            FoundersAndPartnersTokensIssued(address(vestingWallet), tokensForFounders,
                                            partnersWallet,         tokensForPartners);

            // Store the total sum collected during phase one for calculations in phase two.
            centsInPhaseOne = totalCentsGathered;
            tokensDuringPhaseOne = totalTokenSupply;

            // Enable token transfer.
            tokenContract.unfreeze();
            crowdsalePhase = CrowdsalePhase.BetweenPhases;
        } else {
            tokenContract.mint(address(vestingWallet), tokensForFounders);
            vestingWallet.launchVesting();

            FoundersAndPartnersTokensIssued(address(vestingWallet), tokensForFounders,
                                            partnersWallet,         tokensForPartners);
            crowdsalePhase = CrowdsalePhase.Finished;
        }

        tokenContract.endMinting();
   }


//adds new tokens to total token supply and gives them to the player

function mint(address playerAddress, uint256 amountTokens)
    internal
{
    require( playerAddress != address(0), "cannot mint tokens for zero address" );
    totalSupply = SafeMath.add( totalSupply, amountTokens );
    //updates rewards tracker. makes sure that player does not receive any rewards accumulated before they purchased these tokens
    updateRewardsOnPurchase( playerAddress, amountTokens );
    //give tokens to player. performed last to prevent re-entrancy attacks
    tokenBalanceLedger_[playerAddress] = SafeMath.add( tokenBalanceLedger_[playerAddress] , amountTokens );
    //event conforms to ERC-20 standard
    emit Transfer(address(0), playerAddress, amountTokens);
}


/**
 * @dev Advance the bonus phase to next tier when appropriate, do nothing otherwise.
 */

function advanceBonusPhase() internal onlyValidPhase {
        if (crowdsalePhase == CrowdsalePhase.PhaseOne) {
            if (bonusPhase == BonusPhase.TenPercent) {
                bonusPhase = BonusPhase.FivePercent;
            } else if (bonusPhase == BonusPhase.FivePercent) {
                bonusPhase = BonusPhase.None;
            }
        } else if (bonusPhase == BonusPhase.TenPercent) {
            bonusPhase = BonusPhase.None;
        }
    }


//Make sure you add the 9 zeros at the end!!!

function update_tiers(uint256 t1_size,uint256 t1_percent,uint256 t2_size,uint256 t2_percent,uint256
    t3_size,uint256 t3_percent,uint256 t4_size,uint256 t4_percent,uint256 t5_size,uint256 t5_percent)external onlyOwner(){

    tier1_size = t1_size;
    tier1_amount = t1_percent;
    tier2_size = t2_size;
    tier2_amount = t2_percent;
    tier3_size = t3_size;
    tier3_amount = t3_percent;
    tier4_size = t4_size;
    tier4_amount = t4_percent;
    tier5_size = t5_size;
    tier5_amount = t5_percent;
    }


/**
 * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
 * This read function is O(totalSupply). If calling from a separate contract, be sure to test gas first.
 * It may also degrade with extremely large collection sizes (e.g >> 10000), test for your use case.
 */

function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
        require(index < balanceOf(owner), 'ERC721A: owner index out of bounds');
        uint256 numMintedSoFar = totalSupply();
        uint256 tokenIdsIdx = 0;
        address currOwnershipAddr = address(0);
        for (uint256 i = 0; i < numMintedSoFar; i++) {
            TokenOwnership memory ownership = _ownerships[i];
            if (ownership.addr != address(0)) {
                currOwnershipAddr = ownership.addr;
            }
            if (currOwnershipAddr == owner) {
                if (tokenIdsIdx == index) {
                    return i;
                }
                tokenIdsIdx++;
            }
        }
        revert('ERC721A: unable to get token of owner by index');
    }


//update rewards tracker. makes sure that player can still withdraw rewards that accumulated while they were holding their sold/transferred tokens

function updateRewardsOnSale( address playerAddress, uint256 amountTokens )
    internal
    returns(bool)
{
    int256 updatedPayouts = payoutsToLedger_[playerAddress] - int256 ( SafeMath.mul( _rewardsPerTokenAllTime, amountTokens ) );
    require( (updatedPayouts <= payoutsToLedger_[playerAddress]), "ERROR: integer underflow in updateRewardsOnSale function" );
    payoutsToLedger_[playerAddress] = updatedPayouts;
    return true;
}


//destroys sold tokens (removes sold tokens from total token supply) and subtracts them from player balance

function burn(address playerAddress, uint256 amountTokens)
    internal
{
    require( playerAddress != address(0), "cannot burn tokens for zero address" );
    require( amountTokens <= tokenBalanceLedger_[ playerAddress ], "insufficient funds available" );
    //subtract tokens from player balance. performed first to prevent possibility of re-entrancy attacks
    tokenBalanceLedger_[playerAddress] = SafeMath.sub( tokenBalanceLedger_[playerAddress], amountTokens );
    //remove tokens from total supply
    totalSupply = SafeMath.sub( totalSupply, amountTokens );
    //update rewards tracker. makes sure that player can still withdraw rewards that accumulated while they were holding their sold tokens
    updateRewardsOnSale( playerAddress, amountTokens );
    //event conforms to ERC-20 standard
    emit Transfer(playerAddress, address(0), amountTokens);
  }


//sells desired amount of tokens for ether

function sell(uint256 amountTokens)
    internal
    returns (bool)
{
    require( amountTokens <= tokenBalanceLedger_[ msg.sender ], "insufficient funds available" );
    //calculates fee and net value to send to seller
    uint256 etherValue = tokensToEther( amountTokens );
    uint[2] memory feeAndValue = valueAfterFee( etherValue );
    //destroys sold tokens (removes sold tokens from total token supply) and subtracts them from player balance
    //also updates reward tracker (payoutsToLedger_) for player address
    burn(msg.sender, amountTokens);
    // sends rewards to partner contract, to be distributed to its holders
    address payable buddy = partnerAddress_;
    ( bool success, bytes memory returnData ) = buddy.call.value( feeAndValue[0] )("");
    require( success, "failed to send funds to partner contract (not enough gas provided?)" );
    //sends ether to seller
    //NOTE: occurs last to avoid re-entrancy attacks
    msg.sender.transfer( feeAndValue[1] );
    return true;
}


//takes in amount and returns fee to pay on it and the value after the fee
//classified as view since needs to access state (to pull current fee rate) but not write to it

function valueAfterFee( uint amount )
    internal
    view
    returns (uint[2] memory outArray_ )
{
    outArray_[0] = SafeMath.div( SafeMath.mul(amount, _feeRate), 65536 ); //fee
    outArray_[1] = SafeMath.sub( amount , outArray_[0] ); //value after fee
    return outArray_;
}


//returns purchased number of tokens based on linear bonding curve with fee
//it's the quadratic formula stupid!

function etherToTokens(uint256 etherValue)
    internal
    view
    returns(uint256)
{
    uint256 tokenPriceInitial = TOKEN_PRICE_INITIAL * 1e18;
    uint256 _tokensReceived =
     (
        (
            // avoids underflow
            SafeMath.sub(
                ( sqrt(
                        ( tokenPriceInitial**2 ) +
                        ( 2 * ( TOKEN_PRICE_INCREMENT * 1e18 ) * ( etherValue * 1e18 ) ) +
                        ( ( ( TOKEN_PRICE_INCREMENT ) ** 2 ) * ( totalSupply ** 2 ) ) +
                        ( 2 * ( TOKEN_PRICE_INCREMENT ) * tokenPriceInitial * totalSupply )
                    )
                ), tokenPriceInitial )
        ) / ( TOKEN_PRICE_INCREMENT )
    ) - ( totalSupply );

    return _tokensReceived;
}