/// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
///  its behalf. This is a modified version of the ERC20 approve function
///  where allowance per spender must be 0 to allow change of such allowance
/// @param spender The address of the account able to transfer the tokens
/// @param amount The amount of tokens to be approved for transfer
/// @return True or reverts, False is never returned

function approve(address spender, uint256 amount)
        public
        returns (bool success)
    {
        // Alerts the token controller of the approve function call
        require(mOnApprove(msg.sender, spender, amount));

        // To change the approve amount you first have to reduce the addresses`
        //  allowance to zero by calling `approve(_spender,0)` if it is not
        //  already 0 to mitigate the race condition described here:
        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        require((amount == 0 || _allowed[msg.sender][spender] == 0) && mAllowanceOverride(msg.sender, spender) == 0);

        _allowed[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }


/// @notice convenience function to withdraw and transfer to external account
/// @param sendTo address to which send total amount
/// @param amount total amount to withdraw and send
/// @dev function is payable and is meant to withdraw funds on accounts balance and token in single transaction
/// @dev BEWARE that msg.sender of the funds is Ether Token contract and not simple account calling it.
/// @dev  when sent to smart conctract funds may be lost, so this is prevented below

function withdrawAndSend(address sendTo, uint256 amount)
        public
        payable
    {
        // must send at least what is in msg.value to being another deposit function
        require(amount >= msg.value, "NF_ET_NO_DEPOSIT");
        if (amount > msg.value) {
            uint256 withdrawRemainder = amount - msg.value;
            withdrawPrivate(withdrawRemainder);
        }
        emit LogWithdrawAndSend(msg.sender, sendTo, amount);
        sendTo.transfer(amount);
    }


//
// Implements IERC223Token
//

function transfer(address to, uint256 amount, bytes data)
        public
        returns (bool)
    {
        BasicToken.mTransfer(msg.sender, to, amount);

        // Notify the receiving contract.
        if (isContract(to)) {
            // in case of re-entry (1) transfer is done (2) msg.sender is different
            IERC223Callback(to).tokenFallback(msg.sender, amount, data);
        }
        return true;
    }


////////////////////////
/// @notice deposit 'amount' of EUR-T to address 'to', attaching correlating `reference` to LogDeposit event
/// @dev deposit may happen only in case 'to' can receive transfer in token controller
///     by default KYC is required to receive deposits

function deposit(address to, uint256 amount, bytes32 reference)
        public
        only(ROLE_EURT_DEPOSIT_MANAGER)
        onlyIfDepositAllowed(to, amount)
        acceptAgreement(to)
    {
        require(to != address(0));
        _balances[to] = add(_balances[to], amount);
        _totalSupply = add(_totalSupply, amount);
        emit LogDeposit(to, msg.sender, amount, reference);
        emit Transfer(address(0), to, amount);
    }


//
// Implements MTokenController
//

function mOnTransfer(
        address from,
        address to,
        uint256 amount
    )
        internal
        acceptAgreement(from)
        returns (bool allow)
    {
        address broker = msg.sender;
        if (broker != from) {
            // if called by the depositor (deposit and send), ignore the broker flag
            bool isDepositor = accessPolicy().allowed(msg.sender, ROLE_EURT_DEPOSIT_MANAGER, this, msg.sig);
            // this is not very clean but alternative (give brokerage rights to all depositors) is maintenance hell
            if (isDepositor) {
                broker = from;
            }
        }
        return _tokenController.onTransfer(broker, from, to, amount);
    }


/// @notice internal transfer function that checks permissions and calls the tokenFallback

function ierc223TransferInternal(address from, address to, uint256 amount, bytes data)
        private
        returns (bool success)
    {
        BasicToken.mTransfer(from, to, amount);

        // Notify the receiving contract.
        if (isContract(to)) {
            // in case of re-entry (1) transfer is done (2) msg.sender is different
            IERC223Callback(to).tokenFallback(from, amount, data);
        }
        return true;
    }


////////////////////////
/// @notice returns additional amount of neumarks issued for euroUlps at totalEuroUlps
/// @param totalEuroUlps actual curve position from which neumarks will be issued
/// @param euroUlps amount against which neumarks will be issued

function incremental(uint256 totalEuroUlps, uint256 euroUlps)
        public
        pure
        returns (uint256 neumarkUlps)
    {
        require(totalEuroUlps + euroUlps >= totalEuroUlps);
        uint256 from = cumulative(totalEuroUlps);
        uint256 to = cumulative(totalEuroUlps + euroUlps);
        // as expansion is not monotonic for large totalEuroUlps, assert below may fail
        // example: totalEuroUlps=1.999999999999999999999000000e+27 and euroUlps=50
        assert(to >= from);
        return to - from;
    }


/// @notice returns amount of euro corresponding to burned neumarks
/// @param totalEuroUlps actual curve position from which neumarks will be burned
/// @param burnNeumarkUlps amount of neumarks to burn

function incrementalInverse(uint256 totalEuroUlps, uint256 burnNeumarkUlps)
        public
        pure
        returns (uint256 euroUlps)
    {
        uint256 totalNeumarkUlps = cumulative(totalEuroUlps);
        require(totalNeumarkUlps >= burnNeumarkUlps);
        uint256 fromNmk = totalNeumarkUlps - burnNeumarkUlps;
        uint newTotalEuroUlps = cumulativeInverse(fromNmk, 0, totalEuroUlps);
        // yes, this may overflow due to non monotonic inverse function
        assert(totalEuroUlps >= newTotalEuroUlps);
        return totalEuroUlps - newTotalEuroUlps;
    }


/// @notice returns amount of euro corresponding to burned neumarks
/// @param totalEuroUlps actual curve position from which neumarks will be burned
/// @param burnNeumarkUlps amount of neumarks to burn
/// @param minEurUlps euro amount to start inverse search from, inclusive
/// @param maxEurUlps euro amount to end inverse search to, inclusive

function incrementalInverse(uint256 totalEuroUlps, uint256 burnNeumarkUlps, uint256 minEurUlps, uint256 maxEurUlps)
        public
        pure
        returns (uint256 euroUlps)
    {
        uint256 totalNeumarkUlps = cumulative(totalEuroUlps);
        require(totalNeumarkUlps >= burnNeumarkUlps);
        uint256 fromNmk = totalNeumarkUlps - burnNeumarkUlps;
        uint newTotalEuroUlps = cumulativeInverse(fromNmk, minEurUlps, maxEurUlps);
        // yes, this may overflow due to non monotonic inverse function
        assert(totalEuroUlps >= newTotalEuroUlps);
        return totalEuroUlps - newTotalEuroUlps;
    }


//
// Implements ISnapshotable
//

function createSnapshot()
        public
        returns (uint256)
    {
        uint256 base = dayBase(uint128(block.timestamp));

        if (base > _currentSnapshotId) {
            // New day has started, create snapshot for midnight
            _currentSnapshotId = base;
        } else {
            // within single day, increase counter (assume 2**128 will not be crossed)
            _currentSnapshotId += 1;
        }

        // Log and return
        emit LogSnapshotCreated(_currentSnapshotId);
        return _currentSnapshotId;
    }


/// gets last value in the series

function getValue(
        Values[] storage values,
        uint256 defaultValue
    )
        internal
        constant
        returns (uint256)
    {
        if (values.length == 0) {
            return defaultValue;
        } else {
            uint256 last = values.length - 1;
            return values[last].value;
        }
    }


/// @dev `getValueAt` retrieves value at a given snapshot id
/// @param values The series of values being queried
/// @param snapshotId Snapshot id to retrieve the value at
/// @return Value in series being queried

function getValueAt(
        Values[] storage values,
        uint256 snapshotId,
        uint256 defaultValue
    )
        internal
        constant
        returns (uint256)
    {
        require(snapshotId <= mCurrentSnapshotId());

        // Empty value
        if (values.length == 0) {
            return defaultValue;
        }

        // Shortcut for the out of bounds snapshots
        uint256 last = values.length - 1;
        uint256 lastSnapshot = values[last].snapshotId;
        if (snapshotId >= lastSnapshot) {
            return values[last].value;
        }
        uint256 firstSnapshot = values[0].snapshotId;
        if (snapshotId < firstSnapshot) {
            return defaultValue;
        }
        // Binary search of the value in the array
        uint256 min = 0;
        uint256 max = last;
        while (max > min) {
            uint256 mid = (max + min + 1) / 2;
            // must always return lower indice for approximate searches
            if (values[mid].snapshotId <= snapshotId) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }
        return values[min].value;
    }


/// @notice gets all token balances of 'owner'
/// @dev intended to be called via eth_call where gas limit is not an issue

function allBalancesOf(address owner)
        external
        constant
        returns (uint256[2][])
    {
        /* very nice and working implementation below,
        // copy to memory
        Values[] memory values = _balances[owner];
        do assembly {
            // in memory structs have simple layout where every item occupies uint256
            balances := values
        } while (false);*/

        Values[] storage values = _balances[owner];
        uint256[2][] memory balances = new uint256[2][](values.length);
        for(uint256 ii = 0; ii < values.length; ++ii) {
            balances[ii] = [values[ii].snapshotId, values[ii].value];
        }

        return balances;
    }


// get balance at snapshot if with continuation in parent token

function balanceOfAtInternal(address owner, uint256 snapshotId)
        internal
        constant
        returns (uint256)
    {
        Values[] storage values = _balances[owner];

        // If there is a value, return it, reverts if value is in the future
        if (hasValueAt(values, snapshotId)) {
            return getValueAt(values, snapshotId, 0);
        }

        // Try parent contract at or before the fork
        if (PARENT_TOKEN != address(0)) {
            uint256 earlierSnapshotId = PARENT_SNAPSHOT_ID > snapshotId ? snapshotId : PARENT_SNAPSHOT_ID;
            return PARENT_TOKEN.balanceOfAt(owner, earlierSnapshotId);
        }

        // Default to an empty balance
        return 0;
    }


/// @notice Generates `amount` tokens that are assigned to `owner`
/// @param owner The address that will be assigned the new tokens
/// @param amount The quantity of tokens generated

function mGenerateTokens(address owner, uint256 amount)
        internal
    {
        // never create for address 0
        require(owner != address(0));
        // block changes in clone that points to future/current snapshots of patent token
        require(parentToken() == address(0) || parentSnapshotId() < parentToken().currentSnapshotId());

        uint256 curTotalSupply = totalSupply();
        uint256 newTotalSupply = curTotalSupply + amount;
        require(newTotalSupply >= curTotalSupply); // Check for overflow

        uint256 previousBalanceTo = balanceOf(owner);
        uint256 newBalanceTo = previousBalanceTo + amount;
        assert(newBalanceTo >= previousBalanceTo); // Check for overflow

        setValue(_totalSupplyValues, newTotalSupply);
        setValue(_balances[owner], newBalanceTo);

        emit Transfer(0, owner, amount);
    }


////////////////////////
/// @notice issues new Neumarks to msg.sender with reward at current curve position
///     moves curve position by euroUlps
///     callable only by ROLE_NEUMARK_ISSUER

function issueForEuro(uint256 euroUlps)
        public
        only(ROLE_NEUMARK_ISSUER)
        acceptAgreement(msg.sender)
        returns (uint256)
    {
        require(_totalEurUlps + euroUlps >= _totalEurUlps);
        uint256 neumarkUlps = incremental(_totalEurUlps, euroUlps);
        _totalEurUlps += euroUlps;
        mGenerateTokens(msg.sender, neumarkUlps);
        emit LogNeumarksIssued(msg.sender, euroUlps, neumarkUlps);
        return neumarkUlps;
    }


//
// Implements IERC223Token with IERC223Callback (onTokenTransfer) callback
//
// old implementation of ERC223 that was actual when ICBM was deployed
// as Neumark is already deployed this function keeps old behavior for testing

function transfer(address to, uint256 amount, bytes data)
        public
        returns (bool)
    {
        // it is necessary to point out implementation to be called
        BasicSnapshotToken.mTransfer(msg.sender, to, amount);

        // Notify the receiving contract.
        if (isContract(to)) {
            IERC223LegacyCallback(to).onTokenTransfer(msg.sender, amount, data);
        }
        return true;
    }


/**
 * Creates the contract with up to 16 owners
 * shares must be > 0
 */

function MultiOwnable (address[16] _owners_dot_recipient, uint[16] _owners_dot_share)   {
Owner[16] memory _owners;

for(uint __recipient_iterator__ = 0; __recipient_iterator__ < _owners_dot_recipient.length;__recipient_iterator__++)
  _owners[__recipient_iterator__].recipient = address(_owners_dot_recipient[__recipient_iterator__]);
for(uint __share_iterator__ = 0; __share_iterator__ < _owners_dot_share.length;__share_iterator__++)
  _owners[__share_iterator__].share = uint(_owners_dot_share[__share_iterator__]);
        for(var idx = 0; idx < _owners_dot_recipient.length; idx++) {
            if(_owners[idx].recipient != 0) {
                owners.push(_owners[idx]);
                assert(owners[idx].share > 0);
                ownersIdx[_owners[idx].recipient] = true;
            }
        }
    }


////////////////////////
/// @notice locks funds of investors for a period of time
/// @param investor funds owner
/// @param amount amount of funds locked
/// @param neumarks amount of neumarks that needs to be returned by investor to unlock funds
/// @dev callable only from controller (Commitment) contract

function lock(address investor, uint256 amount, uint256 neumarks)
        public
        onlyState(LockState.AcceptingLocks)
        onlyController()
    {
        require(amount > 0);
        // transfer to itself from Commitment contract allowance
        assert(ASSET_TOKEN.transferFrom(msg.sender, address(this), amount));

        Account storage account = _accounts[investor];
        account.balance = addBalance(account.balance, amount);
        account.neumarksDue = add(account.neumarksDue, neumarks);

        if (account.unlockDate == 0) {
            // this is new account - unlockDate always > 0
            _totalInvestors += 1;
            account.unlockDate = currentTime() + LOCK_PERIOD;
        }
        emit LogFundsLocked(investor, amount, neumarks);
    }


/// @notice unlocks investors funds, see unlockInvestor for details
/// @dev this ERC667 callback by Neumark contract after successful approve
///     allows to unlock and allow neumarks to be burned in one transaction

function receiveApproval(
        address from,
        uint256, // _amount,
        address _token,
        bytes _data
    )
        public
        onlyState(LockState.AcceptingUnlocks)
        returns (bool)
    {
        require(msg.sender == _token);
        require(_data.length == 0);

        // only from neumarks
        require(_token == address(NEUMARK));

        // this will check if allowance was made and if _amount is enough to
        //  unlock, reverts on any error condition
        unlockInvestor(from);

        // we assume external call so return value will be lost to clients
        // that's why we throw above
        return true;
    }


/// migrates single investor

function migrate()
        public
        onlyMigrationEnabled()
    {
        // migrates
        Account memory account = _accounts[msg.sender];

        // return on non existing accounts silently
        if (account.balance == 0) {
            return;
        }

        // this will clear investor storage
        removeInvestor(msg.sender, account.balance);

        // let migration target to own asset balance that belongs to investor
        assert(ASSET_TOKEN.approve(address(_migration), account.balance));
        ICBMLockedAccountMigration(_migration).migrateInvestor(
            msg.sender,
            account.balance,
            account.neumarksDue,
            account.unlockDate
        );
        emit LogInvestorMigrated(msg.sender, account.balance, account.neumarksDue, account.unlockDate);
    }


////////////////////////
/// @notice commits funds in one of offerings on the platform
/// @param commitment commitment contract with token offering
/// @param amount amount of funds to invest
/// @dev data ignored, to keep compatibility with ERC223
/// @dev happens via ERC223 transfer and callback

function transfer(address commitment, uint256 amount, bytes /*data*/)
        public
        onlyIfCommitment(commitment)
    {
        require(amount > 0, "NF_LOCKED_NO_ZERO");
        Account storage account = _accounts[msg.sender];
        // no overflow with account.balance which is uint112
        require(account.balance >= amount, "NF_LOCKED_NO_FUNDS");
        // calculate unlocked NEU as proportion of invested amount to account balance
        uint112 unlockedNmkUlps = uint112(
            proportion(
                account.neumarksDue,
                amount,
                account.balance
            )
        );
        account.balance = subBalance(account.balance, uint112(amount));
        // will not overflow as amount < account.balance so unlockedNmkUlps must be >= account.neumarksDue
        account.neumarksDue -= unlockedNmkUlps;
        // track investment
        Account storage investment = _commitments[address(commitment)][msg.sender];
        investment.balance += uint112(amount);
        investment.neumarksDue += unlockedNmkUlps;
        // invest via ERC223 interface
        assert(PAYMENT_TOKEN.transfer(commitment, amount, abi.encodePacked(msg.sender)));
        emit LogFundsCommitted(msg.sender, commitment, amount, unlockedNmkUlps);
    }


/**
 * Transfers `amount` of tokens to `to` address
 * @param  to - address to transfer to
 * @param  amount - amount of tokens to transfer
 * @return success - `true` if the transfer was succesful, `false` otherwise
 */

function transfer (address to, uint256 amount)   returns (bool success) {
        if(balances[msg.sender] < amount)
            return false;

        if(amount <= 0)
            return false;

        if(balances[to] + amount <= balances[to])
            return false;

        balances[msg.sender] -= amount;
        balances[to] += amount;
        Transfer(msg.sender, to, amount);
        return true;
    }


/// @notice refunds investor in case of failed offering
/// @param investor funds owner
/// @dev callable only by ETO contract, bookkeeping in LockedAccount::_commitments
/// @dev expected that ETO makes allowance for transferFrom

function refunded(address investor)
        public
    {
        Account memory investment = _commitments[msg.sender][investor];
        // return silently when there is no refund (so commitment contracts can blank-call, less gas used)
        if (investment.balance == 0)
            return;
        // free gas here
        delete _commitments[msg.sender][investor];
        Account storage account = _accounts[investor];
        // account must exist
        require(account.unlockDate > 0, "NF_LOCKED_ACCOUNT_LIQUIDATED");
        // add refunded amount
        account.balance = addBalance(account.balance, investment.balance);
        account.neumarksDue = add112(account.neumarksDue, investment.neumarksDue);
        // transfer to itself from Commitment contract allowance
        assert(PAYMENT_TOKEN.transferFrom(msg.sender, address(this), investment.balance));
        emit LogFundsRefunded(investor, msg.sender, investment.balance, investment.neumarksDue);
    }


/// @notice changes migration destination for msg.sender
/// @param destinationWallet where migrate funds to, must have valid verification claims
/// @dev msg.sender has funds in old icbm wallet and calls this function on new icbm wallet before s/he migrates

function setInvestorMigrationWallet(address destinationWallet)
        public
    {
        Destination[] storage destinations = _destinations[msg.sender];
        // delete old destinations
        if(destinations.length > 0) {
            delete _destinations[msg.sender];
        }
        // new destination for the whole amount
        addDestination(destinations, destinationWallet, 0);
    }


/// @dev if one of amounts is > 2**112, solidity will pass modulo value, so for 2**112 + 1, we'll get 1
///      and that's fine

function setInvestorMigrationWallets(address[] wallets, uint112[] amounts)
        public
    {
        require(wallets.length == amounts.length);
        Destination[] storage destinations = _destinations[msg.sender];
        // delete old destinations
        if(destinations.length > 0) {
            delete _destinations[msg.sender];
        }
        uint256 idx;
        while(idx < wallets.length) {
            addDestination(destinations, wallets[idx], amounts[idx]);
            idx += 1;
        }
    }


/// @notice returns current set of destination wallets for investor migration

function getInvestorMigrationWallets(address investor)
        public
        constant
        returns (address[] wallets, uint112[] amounts)
    {
        Destination[] storage destinations = _destinations[investor];
        wallets = new address[](destinations.length);
        amounts = new uint112[](destinations.length);
        uint256 idx;
        while(idx < destinations.length) {
            wallets[idx] = destinations[idx].investor;
            amounts[idx] = destinations[idx].amount;
            idx += 1;
        }
    }


/// @notice unlocks 'investor' tokens by making them withdrawable from paymentToken
/// @dev expects number of neumarks that is due on investor's account to be approved for LockedAccount for transfer
/// @dev there are 3 unlock modes depending on contract and investor state
///     in 'AcceptingUnlocks' state Neumarks due will be burned and funds transferred to investors address in paymentToken,
///         before unlockDate, penalty is deduced and distributed

function unlockInvestor(address investor)
        private
    {
        // use memory storage to obtain copy and be able to erase storage
        Account memory accountInMem = _accounts[investor];

        // silently return on non-existing accounts
        if (accountInMem.balance == 0) {
            return;
        }
        // remove investor account before external calls
        removeInvestor(investor, accountInMem.balance);

        // transfer Neumarks to be burned to itself via allowance mechanism
        //  not enough allowance results in revert which is acceptable state so 'require' is used
        require(NEUMARK.transferFrom(investor, address(this), accountInMem.neumarksDue));

        // burn neumarks corresponding to unspent funds
        NEUMARK.burn(accountInMem.neumarksDue);

        // take the penalty if before unlockDate
        if (block.timestamp < accountInMem.unlockDate) {
            address penaltyDisbursalAddress = UNIVERSE.feeDisbursal();
            require(penaltyDisbursalAddress != address(0));
            uint112 penalty = uint112(decimalFraction(accountInMem.balance, PENALTY_FRACTION));
            // distribution via ERC223 to contract or simple address
            assert(PAYMENT_TOKEN.transfer(penaltyDisbursalAddress, penalty, abi.encodePacked(NEUMARK)));
            emit LogPenaltyDisbursed(penaltyDisbursalAddress, investor, penalty, PAYMENT_TOKEN);
            accountInMem.balance -= penalty;
        }
        // transfer amount back to investor - now it can withdraw
        assert(PAYMENT_TOKEN.transfer(investor, accountInMem.balance, ""));
        emit LogFundsUnlocked(investor, accountInMem.balance, accountInMem.neumarksDue);
    }


/// @notice locks funds of investors for a period of time, called by migration
/// @param investor funds owner
/// @param amount amount of funds locked
/// @param neumarks amount of neumarks that needs to be returned by investor to unlock funds
/// @param unlockDate unlockDate of migrating account
/// @dev used only by migration

function lock(address investor, uint112 amount, uint112 neumarks, uint32 unlockDate)
        private
        acceptAgreement(investor)
    {
        require(amount > 0);
        Account storage account = _accounts[investor];
        if (account.unlockDate == 0) {
            // this is new account - unlockDate always > 0
            _totalInvestors += 1;
        }

        // update holdings
        account.balance = addBalance(account.balance, amount);
        account.neumarksDue = add112(account.neumarksDue, neumarks);
        // overwrite unlockDate if it is earler. we do not supporting joining tickets from different investors
        // this will discourage sending 1 wei to move unlock date
        if (unlockDate > account.unlockDate) {
            account.unlockDate = unlockDate;
        }

        emit LogFundsLocked(investor, amount, neumarks);
    }


// calculates investor's and platform operator's neumarks from total reward

function calculateNeumarkDistribution(uint256 rewardNmk)
        public
        pure
        returns (uint256 platformNmk, uint256 investorNmk)
    {
        // round down - platform may get 1 wei less than investor
        platformNmk = rewardNmk / PLATFORM_NEUMARK_SHARE;
        // rewardNmk > platformNmk always
        return (platformNmk, rewardNmk - platformNmk);
    }


// calculates token amount for a given commitment at a position of the curve
// we require that equity token precision is 0

function calculateTokenAmount(uint256 /*totalEurUlps*/, uint256 committedEurUlps)
        public
        constant
        returns (uint256 tokenAmountInt)
    {
        // we may disregard totalEurUlps as curve is flat, round down when calculating tokens
        return committedEurUlps / calculatePriceFraction(10**18 - PUBLIC_DISCOUNT_FRAC);
    }


// calculate contribution of investor

function calculateContribution(
        address investor,
        uint256 totalContributedEurUlps,
        uint256 existingInvestorContributionEurUlps,
        uint256 newInvestorContributionEurUlps,
        bool applyWhitelistDiscounts
    )
        public
        constant
        returns (
            bool isWhitelisted,
            bool isEligible,
            uint256 minTicketEurUlps,
            uint256 maxTicketEurUlps,
            uint256 equityTokenInt,
            uint256 fixedSlotEquityTokenInt
            )
    {
        (
            isWhitelisted,
            minTicketEurUlps,
            maxTicketEurUlps,
            equityTokenInt,
            fixedSlotEquityTokenInt
        ) = calculateContributionPrivate(
            investor,
            totalContributedEurUlps,
            existingInvestorContributionEurUlps,
            newInvestorContributionEurUlps,
            applyWhitelistDiscounts);
        // check if is eligible for investment
        IdentityClaims memory claims = deserializeClaims(IDENTITY_REGISTRY.getClaims(investor));
        isEligible = claims.isVerified && !claims.accountFrozen;
    }


/// @notice checks terms against platform terms, reverts on invalid

function requireValidTerms(PlatformTerms platformTerms)
        public
        constant
        returns (bool)
    {
        // apply constraints on retail fundraising
        if (ALLOW_RETAIL_INVESTORS) {
            // make sure transfers are disabled after offering for retail investors
            require(!ENABLE_TRANSFERS_ON_SUCCESS, "NF_MUST_DISABLE_TRANSFERS");
        } else {
            // only qualified investors allowed defined as tickets > 100000 EUR
            require(MIN_TICKET_EUR_ULPS >= MIN_QUALIFIED_INVESTOR_TICKET_EUR_ULPS, "NF_MIN_QUALIFIED_INVESTOR_TICKET");
        }
        // min ticket must be > token price
        require(MIN_TICKET_EUR_ULPS >= TOKEN_TERMS.TOKEN_PRICE_EUR_ULPS(), "NF_MIN_TICKET_LT_TOKEN_PRICE");
        // it must be possible to collect more funds than max number of tokens
        require(ESTIMATED_MAX_CAP_EUR_ULPS() >= MIN_TICKET_EUR_ULPS, "NF_MAX_FUNDS_LT_MIN_TICKET");

        require(MIN_TICKET_EUR_ULPS >= platformTerms.MIN_TICKET_EUR_ULPS(), "NF_ETO_TERMS_MIN_TICKET_EUR_ULPS");
        // duration checks
        require(DURATION_TERMS.WHITELIST_DURATION() >= platformTerms.MIN_WHITELIST_DURATION(), "NF_ETO_TERMS_WL_D_MIN");
        require(DURATION_TERMS.WHITELIST_DURATION() <= platformTerms.MAX_WHITELIST_DURATION(), "NF_ETO_TERMS_WL_D_MAX");

        require(DURATION_TERMS.PUBLIC_DURATION() >= platformTerms.MIN_PUBLIC_DURATION(), "NF_ETO_TERMS_PUB_D_MIN");
        require(DURATION_TERMS.PUBLIC_DURATION() <= platformTerms.MAX_PUBLIC_DURATION(), "NF_ETO_TERMS_PUB_D_MAX");

        uint256 totalDuration = DURATION_TERMS.WHITELIST_DURATION() + DURATION_TERMS.PUBLIC_DURATION();
        require(totalDuration >= platformTerms.MIN_OFFER_DURATION(), "NF_ETO_TERMS_TOT_O_MIN");
        require(totalDuration <= platformTerms.MAX_OFFER_DURATION(), "NF_ETO_TERMS_TOT_O_MAX");

        require(DURATION_TERMS.SIGNING_DURATION() >= platformTerms.MIN_SIGNING_DURATION(), "NF_ETO_TERMS_SIG_MIN");
        require(DURATION_TERMS.SIGNING_DURATION() <= platformTerms.MAX_SIGNING_DURATION(), "NF_ETO_TERMS_SIG_MAX");

        require(DURATION_TERMS.CLAIM_DURATION() >= platformTerms.MIN_CLAIM_DURATION(), "NF_ETO_TERMS_CLAIM_MIN");
        require(DURATION_TERMS.CLAIM_DURATION() <= platformTerms.MAX_CLAIM_DURATION(), "NF_ETO_TERMS_CLAIM_MAX");

        return true;
    }


// @notice time induced state transitions, called before logic
// @dev don't use `else if` and keep sorted by time and call `state()`
//     or else multiple transitions won't cascade properly.

function advanceTimedState()
        private
    {
        // if timed state machine was not run, the next state will never come
        if (_pastStateTransitionTimes[uint32(ETOState.Setup)] == 0) {
            return;
        }

        uint256 t = block.timestamp;
        if (_state == ETOState.Setup && t >= startOfInternal(ETOState.Whitelist)) {
            transitionTo(ETOState.Whitelist);
        }
        if (_state == ETOState.Whitelist && t >= startOfInternal(ETOState.Public)) {
            transitionTo(ETOState.Public);
        }
        if (_state == ETOState.Public && t >= startOfInternal(ETOState.Signing)) {
            transitionTo(ETOState.Signing);
        }
        // signing to refund: first we check if it's claim time and if it we go
        // for refund. to go to claim agreement MUST be signed, no time transition
        if (_state == ETOState.Signing && t >= startOfInternal(ETOState.Claim)) {
            transitionTo(ETOState.Refund);
        }
        // claim to payout
        if (_state == ETOState.Claim && t >= startOfInternal(ETOState.Payout)) {
            transitionTo(ETOState.Payout);
        }
    }


/// @notice executes transition state function

function transitionTo(ETOState newState)
        private
    {
        ETOState oldState = _state;
        ETOState effectiveNewState = mBeforeStateTransition(oldState, newState);
        // require(validTransition(oldState, effectiveNewState));

        _state = effectiveNewState;
        // store deadline for previous state
        uint32 deadline = _pastStateTransitionTimes[uint256(oldState)];
        // if transition came before deadline, count time from timestamp, if after always count from deadline
        if (uint32(block.timestamp) < deadline) {
            deadline = uint32(block.timestamp);
        }
        // we have 60+ years for 2^32 overflow on epoch so disregard
        _pastStateTransitionTimes[uint256(oldState)] = deadline;
        // set deadline on next state
        _pastStateTransitionTimes[uint256(effectiveNewState)] = deadline + ETO_STATE_DURATIONS[uint256(effectiveNewState)];
        // should not change _state
        mAfterTransition(oldState, effectiveNewState);
        assert(_state == effectiveNewState);
        // should notify observer after internal state is settled
        COMMITMENT_OBSERVER.onStateTransition(oldState, effectiveNewState);
        emit LogStateTransition(uint32(oldState), uint32(effectiveNewState), deadline);
    }


////////////////////////
/// @dev sets timed state machine in motion

function setStartDate(
        ETOTerms etoTerms,
        IEquityToken equityToken,
        uint256 startDate
    )
        external
        onlyCompany
        onlyWithAgreement
        withStateTransition()
        onlyState(ETOState.Setup)
    {
        require(etoTerms == ETO_TERMS);
        require(equityToken == EQUITY_TOKEN);
        assert(startDate < 0xFFFFFFFF);
        // must be more than NNN days (platform terms!)
        require(
            startDate > block.timestamp && startDate - block.timestamp > PLATFORM_TERMS.DATE_TO_WHITELIST_MIN_DURATION(),
            "NF_ETO_DATE_TOO_EARLY");
        // prevent re-setting start date if ETO starts too soon
        uint256 startAt = startOfInternal(ETOState.Whitelist);
        // block.timestamp must be less than startAt, otherwise timed state transition is done
        require(
            startAt == 0 || (startAt - block.timestamp > PLATFORM_TERMS.DATE_TO_WHITELIST_MIN_DURATION()),
            "NF_ETO_START_TOO_SOON");
        runStateMachine(uint32(startDate));
        // todo: lock ETO_TERMS whitelist to be more trustless

        emit LogTermsSet(msg.sender, address(etoTerms), address(equityToken));
        emit LogETOStartDateSet(msg.sender, startAt, startDate);
    }


/// commit function happens via ERC223 callback that must happen from trusted payment token
/// @dev data in case of LockedAccount contains investor address and investor is LockedAccount address

function tokenFallback(address wallet, uint256 amount, bytes data)
        public
        withStateTransition()
        onlyStates(ETOState.Whitelist, ETOState.Public)
    {
        uint256 equivEurUlps = amount;
        bool isEuroInvestment = msg.sender == address(EURO_TOKEN);
        bool isEtherInvestment = msg.sender == address(ETHER_TOKEN);
        // we trust only tokens below
        require(isEtherInvestment || isEuroInvestment, "NF_ETO_UNK_TOKEN");
        // check if LockedAccount
        bool isLockedAccount = (wallet == address(ETHER_LOCK) || wallet == address(EURO_LOCK));
        address investor = wallet;
        if (isLockedAccount) {
            // data contains investor address
            investor = decodeAddress(data);
        }
        if (isEtherInvestment) {
            // compute EUR eurEquivalent via oracle if ether
            (uint256 rate, uint256 rateTimestamp) = CURRENCY_RATES.getExchangeRate(ETHER_TOKEN, EURO_TOKEN);
            // require if rate older than 4 hours
            require(block.timestamp - rateTimestamp < TOKEN_RATE_EXPIRES_AFTER, "NF_ETO_INVALID_ETH_RATE");
            equivEurUlps = decimalFraction(amount, rate);
        }
        // agreement accepted by act of reserving funds in this function
        acceptAgreementInternal(investor);
        // we modify state and emit events in function below
        processTicket(investor, wallet, amount, equivEurUlps, isEuroInvestment);
    }


//
// Overrides internal interface
//

function mAdavanceLogicState(ETOState oldState)
        internal
        constant
        returns (ETOState)
    {
        // add 1 to MIN_TICKET_TOKEN because it was produced by floor and check only MAX CAP
        // WHITELIST CAP will not induce state transition as fixed slots should be able to invest till the end of Whitelist
        bool capExceeded = isCapExceeded(false, MIN_TICKET_TOKENS + 1, 0);
        if (capExceeded) {
            if (oldState == ETOState.Whitelist) {
                return ETOState.Public;
            }
            if (oldState == ETOState.Public) {
                return ETOState.Signing;
            }
        }
        if (oldState == ETOState.Signing && _nomineeSignedInvestmentAgreementUrlHash != bytes32(0)) {
            return ETOState.Claim;
        }
        return oldState;
    }


// a copy of PlatformTerms working on local storage

function calculateNeumarkDistribution(uint256 rewardNmk)
        private
        constant
        returns (uint256 platformNmk, uint256 investorNmk)
    {
        // round down - platform may get 1 wei less than investor
        platformNmk = rewardNmk / PLATFORM_NEUMARK_SHARE;
        // rewardNmk > platformNmk always
        return (platformNmk, rewardNmk - platformNmk);
    }


/// called on transition to ETOState.Claim

function onClaimTransition()
        private
    {
        // platform operator gets share of NEU
        uint256 rewardNmk = NEUMARK.balanceOf(this);
        (uint256 platformNmk,) = calculateNeumarkDistribution(rewardNmk);
        assert(NEUMARK.transfer(PLATFORM_WALLET, platformNmk, ""));
        // company legal rep receives funds
        if (_additionalContributionEth > 0) {
            assert(ETHER_TOKEN.transfer(COMPANY_LEGAL_REPRESENTATIVE, _additionalContributionEth, ""));
        }

        if (_additionalContributionEurUlps > 0) {
            assert(EURO_TOKEN.transfer(COMPANY_LEGAL_REPRESENTATIVE, _additionalContributionEurUlps, ""));
        }
        // issue missing tokens
        EQUITY_TOKEN.issueTokens(_tokenParticipationFeeInt);
        emit LogPlatformNeuReward(PLATFORM_WALLET, rewardNmk, platformNmk);
        emit LogAdditionalContribution(COMPANY_LEGAL_REPRESENTATIVE, ETHER_TOKEN, _additionalContributionEth);
        emit LogAdditionalContribution(COMPANY_LEGAL_REPRESENTATIVE, EURO_TOKEN, _additionalContributionEurUlps);
    }


/// called on transtion to ETOState.Refund

function onRefundTransition()
        private
    {
        // burn all neumark generated in this ETO
        uint256 balanceNmk = NEUMARK.balanceOf(this);
        uint256 balanceTokenInt = EQUITY_TOKEN.balanceOf(this);
        if (balanceNmk > 0) {
            NEUMARK.burn(balanceNmk);
        }
        // destroy all tokens generated in ETO
        if (balanceTokenInt > 0) {
            EQUITY_TOKEN.destroyTokens(balanceTokenInt);
        }
        emit LogRefundStarted(EQUITY_TOKEN, balanceTokenInt, balanceNmk);
    }


/// called on transition to ETOState.Payout

function onPayoutTransition()
        private
    {
        // distribute what's left in balances: company took funds on claim
        address disbursal = UNIVERSE.feeDisbursal();
        assert(disbursal != address(0));
        address platformPortfolio = UNIVERSE.platformPortfolio();
        assert(platformPortfolio != address(0));
        bytes memory serializedAddress = abi.encodePacked(address(NEUMARK));
        // assert(decodeAddress(serializedAddress) == address(NEUMARK));
        if (_platformFeeEth > 0) {
            // disburse via ERC223, where we encode token used to provide pro-rata in `data` parameter
            assert(ETHER_TOKEN.transfer(disbursal, _platformFeeEth, serializedAddress));
        }
        if (_platformFeeEurUlps > 0) {
            // disburse via ERC223
            assert(EURO_TOKEN.transfer(disbursal, _platformFeeEurUlps, serializedAddress));
        }
        // add token participation fee to platfrom portfolio
        EQUITY_TOKEN.distributeTokens(platformPortfolio, _tokenParticipationFeeInt);

        emit LogPlatformFeePayout(ETHER_TOKEN, disbursal, _platformFeeEth);
        emit LogPlatformFeePayout(EURO_TOKEN, disbursal, _platformFeeEurUlps);
        emit LogPlatformPortfolioPayout(EQUITY_TOKEN, platformPortfolio, _tokenParticipationFeeInt);
    }


/**
 * @dev Returns the seller that put a given NFT into escrow,
 * or bubbles the call up to check the current owner if the NFT is not currently in escrow.
 */

function _getSellerFor(address nftContract, uint256 tokenId)
        internal
        view
        virtual
        override
        returns (address payable)
    {
        address payable seller = auctionIdToAuction[
            nftContractToTokenIdToAuctionId[nftContract][tokenId]
        ].seller;
        if (seller == address(0)) {
            return super._getSellerFor(nftContract, tokenId);
        }
        return seller;
    }


/**
 * @notice Creates an auction for the given NFT.
 * The NFT is held in escrow until the auction is finalized or canceled.
 */

function createReserveAuction(
        address nftContract,
        uint256 tokenId,
        uint256 reservePrice
    )
        public
        onlyValidAuctionConfig(reservePrice)
        nonReentrant
        notBanned(nftContract, tokenId)
    {
        // If an auction is already in progress then the NFT would be in escrow and the modifier would have failed
        uint256 auctionId = _getNextAndIncrementAuctionId();
        nftContractToTokenIdToAuctionId[nftContract][tokenId] = auctionId;
        auctionIdToAuction[auctionId] = ReserveAuction(
            nftContract,
            tokenId,
            payable(msg.sender),
            _duration,
            EXTENSION_DURATION,
            0, // endTime is only known once the reserve price is met
            payable(address(0)), // bidder is only known once a bid has been placed
            reservePrice
        );

        IERC721Upgradeable(nftContract).transferFrom(
            msg.sender,
            address(this),
            tokenId
        );

        emit ReserveAuctionCreated(
            msg.sender,
            nftContract,
            tokenId,
            _duration,
            EXTENSION_DURATION,
            reservePrice,
            auctionId
        );
    }


/**
 * @notice If an auction has been created but has not yet received bids, the configuration
 * such as the reservePrice may be changed by the seller.
 */

function updateReserveAuction(uint256 auctionId, uint256 reservePrice)
        public
        onlyValidAuctionConfig(reservePrice)
    {
        ReserveAuction storage auction = auctionIdToAuction[auctionId];
        require(
            auction.seller == msg.sender,
            "NFTMarketReserveAuction: Not your auction"
        );
        require(
            auction.endTime == 0,
            "NFTMarketReserveAuction: Auction in progress"
        );

        auction.amount = reservePrice;

        emit ReserveAuctionUpdated(auctionId, reservePrice);
    }


/**
 * @notice If an auction has been created but has not yet received bids, it may be canceled by the seller.
 * The NFT is returned to the seller from escrow.
 */

function cancelReserveAuction(uint256 auctionId) public nonReentrant {
        ReserveAuction memory auction = auctionIdToAuction[auctionId];
        require(
            auction.seller == msg.sender,
            "NFTMarketReserveAuction: Not your auction"
        );
        require(
            auction.endTime == 0,
            "NFTMarketReserveAuction: Auction in progress"
        );

        delete nftContractToTokenIdToAuctionId[auction.nftContract][
            auction.tokenId
        ];
        delete auctionIdToAuction[auctionId];

        IERC721Upgradeable(auction.nftContract).transferFrom(
            address(this),
            auction.seller,
            auction.tokenId
        );

        emit ReserveAuctionCanceled(auctionId);
    }


/**
 * @notice Once the countdown has expired for an auction, anyone can settle the auction.
 * This will send the NFT to the highest bidder and distribute funds.
 */

function finalizeReserveAuction(uint256 auctionId) public nonReentrant {
        ReserveAuction memory auction = auctionIdToAuction[auctionId];
        require(
            auction.endTime > 0,
            "NFTMarketReserveAuction: Auction was already settled"
        );
        require(
            auction.endTime < block.timestamp,
            "NFTMarketReserveAuction: Auction still in progress"
        );

        delete nftContractToTokenIdToAuctionId[auction.nftContract][
            auction.tokenId
        ];
        delete auctionIdToAuction[auctionId];

        IERC721Upgradeable(auction.nftContract).transferFrom(
            address(this),
            auction.bidder,
            auction.tokenId
        );

        (
            uint256 blsFee,
            uint256 creatorFee,
            uint256 ownerRev
        ) = _distributeFunds(
                auction.nftContract,
                auction.tokenId,
                auction.seller,
                auction.amount
            );

        emit ReserveAuctionFinalized(
            auctionId,
            auction.seller,
            auction.bidder,
            blsFee,
            creatorFee,
            ownerRev
        );
    }


/**
 * @dev Determines the minimum bid amount when outbidding another user.
 */

function _getMinBidAmountForReserveAuction(uint256 currentBidAmount)
        private
        view
        returns (uint256)
    {
        uint256 minIncrement = currentBidAmount.mul(
            _minPercentIncrementInBasisPoints
        ) / BASIS_POINTS;
        if (minIncrement == 0) {
            // The next bid must be at least 1 wei greater than the current.
            return currentBidAmount.add(1);
        }
        return minIncrement.add(currentBidAmount);
    }


/**
 * @notice Allows Blocksport to cancel an auction, refunding the bidder and returning the NFT to the seller.
 * This should only be used for extreme cases such as DMCA takedown requests. The reason should always be provided.
 */

function _adminCancelReserveAuction(uint256 auctionId, string memory reason)
        private
        onlyBlocksportAdmin
    {
        require(
            bytes(reason).length > 0,
            "NFTMarketReserveAuction: Include a reason for this cancellation"
        );
        ReserveAuction memory auction = auctionIdToAuction[auctionId];
        require(
            auction.amount > 0,
            "NFTMarketReserveAuction: Auction not found"
        );

        delete nftContractToTokenIdToAuctionId[auction.nftContract][
            auction.tokenId
        ];
        delete auctionIdToAuction[auctionId];

        IERC721Upgradeable(auction.nftContract).transferFrom(
            address(this),
            auction.seller,
            auction.tokenId
        );
        if (auction.bidder != address(0)) {
            _sendValueWithFallbackWithdrawWithMediumGasLimit(
                auction.bidder,
                auction.amount
            );
        }

        emit ReserveAuctionCanceledByAdmin(auctionId, reason);
    }


/**
 * @notice Allows Blocksport to ban token, refunding the bidder and returning the NFT to the seller.
 */

function adminBanToken(
        address nftContract,
        uint256 tokenId,
        string memory reason
    ) public onlyBlocksportAdmin {
        require(
            bytes(reason).length > 0,
            "NFTMarketReserveAuction: Include a reason for this ban"
        );
        require(!bannedTokens[nftContract][tokenId], "Token already banned");
        uint256 auctionId = getReserveAuctionIdFor(nftContract, tokenId);

        if (auctionIdToAuction[auctionId].tokenId == tokenId) {
            bannedAuction[nftContract][tokenId] = auctionIdToAuction[auctionId];
            _adminCancelReserveAuction(auctionId, reason);
            emit ReserveAuctionBannedByAdmin(auctionId, reason);
        }

        bannedTokens[nftContract][tokenId] = true;
        emit TokenBannedByAdmin(nftContract, tokenId, reason);
    }


/**
 * @notice Allows Blocksport to unban token, and list it again with no bids in the auction.
 */

function adminUnbanToken(address nftContract, uint256 tokenId)
        public
        onlyBlocksportAdmin
    {
        require(
            !bannedTokens[nftContract][tokenId],
            "NFTMarketReserveAuction: Token not banned"
        );

        if (bannedAuction[nftContract][tokenId].tokenId > 0) {
            ReserveAuction memory auction = bannedAuction[nftContract][tokenId];
            delete bannedAuction[nftContract][tokenId];
            createReserveAuction(nftContract, auction.tokenId, auction.amount);
            emit ReserveAuctionUnbannedByAdmin(auction.tokenId);
        }

        delete bannedTokens[nftContract][tokenId];
        emit TokenUnbannedByAdmin(nftContract, tokenId);
    }


/**
 * Begins the crowdsale (presale period)
 * @param tokenContractAddress - address of the `WIN` contract (token holder)
 * @dev must be called by one of owners
 */

function startPresale (address tokenContractAddress)   onlyOneOfOwners  {
        require(state == State.NotStarted);

        win = WIN(tokenContractAddress);

        assert(win.balanceOf(this) >= tokensForPeriod(0));

        periods[0] = Period(now, NEVER, PRESALE_TOKEN_PRICE, 0);
        PeriodStarted(0,
            PRESALE_TOKEN_PRICE,
            tokensForPeriod(currentPeriod),
            now,
            NEVER,
            now);
        state = State.Presale;
    }


/**
 * Withdraws the money to be spent to Blind Croupier Project needs
 * @param  amount - amount of Wei to withdraw (total)
 */

function withdraw (uint256 amount)   onlyOneOfOwners  {
        require(this.balance >= amount);

        uint totalShares = 0;
        for(var idx = 0; idx < owners.length; idx++) {
            totalShares += owners[idx].share;
        }

        for(idx = 0; idx < owners.length; idx++) {
            owners[idx].recipient.transfer(amount * owners[idx].share / totalShares);
        }
    }


// function sliceBytes32To10(bytes32 input) public pure returns (bytes10 output) {
//     assembly {
//         output := input
//     }
// }

function claim(address _to, uint256 _tokenId, string memory _tokenMidContent) public {
        require(_tokenId >= 0 && _tokenId < 2392, "Token ID invalid");
        if (_tokenId >= 0 && _tokenId < 104) {
            require(bytes(_tokenMidContent).length == 429, "Token Content Size invalid");
            require(_tokenMidContentHashes[_tokenId] == keccak256(abi.encodePacked(_tokenMidContent)), "Token Content invalid");
        } else {
            _tokenMidContent = "";
        }
        _safeMint(_to, _tokenId);
        _tokenMidContents[_tokenId] = _tokenMidContent;
    }


/// @dev not test for functions related to signature

function verifySignature(
        uint256[] memory _tokenIds,
        uint256 _price,
        address _paymentTokenAddress,
        bytes memory _signature
    ) internal view returns (bool) {
        bytes32 messageHash = getMessageHash(
            _tokenIds,
            _price,
            _paymentTokenAddress
        );
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
        return recoverSigner(ethSignedMessageHash, _signature) == owner();
    }


// ----- PUBLIC METHODS ----- //

function buyToken(
        uint256[] memory _tokenIds,
        uint256 _price,
        address _paymentToken,
        address _receiver,
        bytes memory _signature
    ) external payable {
        require(_tokenIds.length == 1, "More than one token");
        require(
            verifySignature(_tokenIds, _price, _paymentToken, _signature),
            "Signature mismatch"
        );
        if (_price != 0) {
            if (_paymentToken == address(0)) {
                require(msg.value >= _price, "Not enough ether");
                if (_price < msg.value) {
                    payable(msg.sender).transfer(msg.value - _price);
                }
            } else {
                require(
                    IERC20(_paymentToken).transferFrom(
                        msg.sender,
                        address(this),
                        _price
                    )
                );
            }
        }
        address[] memory receivers = new address[](1);
        receivers[0] = _receiver;
        IUniqCollections(_shopAddress).batchMintSelectedIds(
            _tokenIds,
            receivers
        );
    }


//called when transfers happened, to ensure new users will generate tokens too

function rewardSystemUpdate(address from, address to) external {
        require(msg.sender == address(PixelTigers));
        if(from != address(0)){
            storeRewards[from] += pendingReward(from);
            lastUpdate[from] = block.timestamp;
        }
        if(to != address(0)){
            storeRewards[to] += pendingReward(to);
            lastUpdate[to] = block.timestamp;
        }
    }


// Register player

function playerRegister(string memory name, uint64[] memory numbers) payable public {
    require(contractActive == true, "Contract was disabled");
    require(state == State.Accepting, "Game state is not valid");
    require(numbers.length > 0, "At least 1 number");
    require(msg.value >= minPrice * numbers.length, "Value is not valid");

    for (uint i = 0; i < playerList.length; i++) {
      require(playerList[i].playerAddress != msg.sender);
      for (uint j = 0; j < playerList[i].playerNumbers.length; j++) {
        require(playerList[i].playerNumbers[j] <= maxLuckyNumberRandom);
      }
    }

    totalFund += msg.value;
    Player memory player = Player(msg.sender, name, numbers);
    playerList.push(player);
    emit PlayerRegisterEvent(player.playerAddress);

    if (playerList.length >= playerInSession) {
      finishGame();

      if (contractActive) {
        // Init new game session
        gameInit();
      }
    }
  }


// Emergency drain in case of a bug
// Adds all funds to owner to refund people
// Designed to be as simple as possible

function emergencyDrain24hAfterLiquidityGenerationEventIsDone() public onlyOwner {
        require(contractStartTimestamp.add(8 days) < block.timestamp, "Liquidity generation grace period still ongoing"); // About 24h after liquidity generation happens
        (bool success, ) = msg.sender.call.value(address(this).balance)("");
        require(success, "Transfer failed.");
        _balances[msg.sender] = _balances[address(this)];
        _balances[address(this)] = 0;
    }


// ------------------------------------------------------------------------
// Transfer the balance from token owner's account to `to` account
// - Owner's account must have sufficient balance to transfer
// ------------------------------------------------------------------------

function transfer(address to, uint tokens) public returns (bool success) {
        require(to != address(0));
        require(tokens > 0);
        require(balances[msg.sender] >= tokens);

        balances[msg.sender] = balances[msg.sender].sub(tokens);
        balances[to] = balances[to].add(tokens);
        emit Transfer(msg.sender, to, tokens);
        return true;
    }


// Mint function for OG sale
// Caller MUST be OG-Whitelisted to use this function!

function freeWhitelistMint() external isWallet enoughSupply(maxMintsOg) {
        require(freeWhitelistEnabled, 'OG sale not enabled');
        if (isGenesis) {
            require(genesisOg[msg.sender] >= maxMintsOg, 'Not a wiggle world OG');
            genesisOg[msg.sender] = genesisOg[msg.sender] - maxMintsOg;
        } else {
            require(freeWhitelist[msg.sender] >= maxMintsOg, 'Not a wiggle world OG');
            freeWhitelist[msg.sender] = freeWhitelist[msg.sender] - maxMintsOg;
        }
        _safeMint(msg.sender, maxMintsOg);
    }


// Mint function for whitelist sale
// Requires minimum ETH value of unitPrice * quantity
// Caller MUST be whitelisted to use this function!

function paidWhitelistMint(uint256 quantity) external payable isWallet enoughSupply(quantity) {
        require(paidWhitelistEnabled, 'Whitelist sale not enabled');
        require(msg.value >= quantity * unitPrice, 'Not enough ETH');
        if (isGenesis) {
            require(genesisWhitelist[msg.sender] >= quantity, 'No whitelist mints left');
            genesisWhitelist[msg.sender] = genesisWhitelist[msg.sender] - quantity;
        } else {
            require(paidWhitelist[msg.sender] >= quantity, 'No whitelist mints left');
            paidWhitelist[msg.sender] = paidWhitelist[msg.sender] - quantity;
        }
        _safeMint(msg.sender, quantity);
        refundIfOver(quantity * unitPrice);
    }


// Mint function for public sale
// Requires minimum ETH value of unitPrice * quantity

function publicMint(uint256 quantity) external payable isWallet enoughSupply(quantity) {
        require(publicMintEnabled, 'Minting not enabled');
        require(quantity <= maxMints, 'Illegal quantity');
        require(numberMinted(msg.sender) + quantity <= maxMints, 'Cant mint that many');
        require(msg.value >= quantity * unitPrice, 'Not enough ETH');
        _safeMint(msg.sender, quantity);
        refundIfOver(quantity * unitPrice);
    }


// Mint function for developers (owner)
// Mints a maximum of 20 NFTs to the recipient
// Used for devs, marketing, friends, family
// Capped at 55 mints total

function devMint(uint256 quantity, address recipient)
        external
        onlyOwner
        enoughSupply(quantity)
    {
        if (isGenesis) {
            require(remainingDevSupply - quantity >= genesisDevCutoff, 'No dev supply (genesis)');
        } else {
            require(remainingDevSupply - quantity >= 0, 'Not enough dev supply');
        }
        require(quantity <= maxMints, 'Illegal quantity');
        require(numberMinted(recipient) + quantity <= maxMints, 'Cant mint that many (dev)');
        remainingDevSupply = remainingDevSupply - quantity;
        _safeMint(recipient, quantity);
    }


// Returns the correct URI for the given tokenId based on contract state

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), 'Nonexistent token');
        if (
            (tokenId < genesisCollectionSize && genesisRevealed == false) ||
            (tokenId >= genesisCollectionSize && revealed == false)
        ) {
            return
                bytes(notRevealedURI).length > 0
                    ? string(
                        abi.encodePacked(
                            notRevealedURI,
                            Strings.toString(tokenId % numBackgrounds),
                            baseExtension
                        )
                    )
                    : '';
        }
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, Strings.toString(tokenId), baseExtension))
                : '';
    }


// Update relevant mint format variables.
// Should only be called once,
// To change price between genesis and second mint.

function updateMintFormat(
        uint256 _ogSlots,
        uint256 _ogWlSlots,
        uint256 _wlSlots,
        uint256 _maxMints,
        uint256 _numBackgrounds
    ) external onlyOwner {
        maxMintsOg = _ogSlots;
        ogWlMints = _ogWlSlots;
        maxMintsWhitelist = _wlSlots;
        maxMints = _maxMints;
        numBackgrounds = _numBackgrounds;
    }


// Set the mint state

function setMintState(uint256 _state) external onlyOwner {
        if (_state == 1) {
            freeWhitelistEnabled = true;
        } else if (_state == 2) {
            paidWhitelistEnabled = true;
        } else if (_state == 3) {
            publicMintEnabled = true;
        } else {
            freeWhitelistEnabled = false;
            paidWhitelistEnabled = false;
            publicMintEnabled = false;
        }
    }


// Seed the appropriate whitelist

function setWhitelist(address[] calldata addrs, bool isOG) external onlyOwner {
        if (isOG) {
            for (uint256 i = 0; i < addrs.length; i++) {
                if (isGenesis) {
                    genesisOg[addrs[i]] = maxMintsOg;
                    genesisWhitelist[addrs[i]] = ogWlMints;
                } else {
                    freeWhitelist[addrs[i]] = maxMintsOg;
                    paidWhitelist[addrs[i]] = ogWlMints;
                }
            }
        } else {
            for (uint256 i = 0; i < addrs.length; i++) {
                if (isGenesis) {
                    genesisWhitelist[addrs[i]] = maxMintsWhitelist;
                } else {
                    paidWhitelist[addrs[i]] = maxMintsWhitelist;
                }
            }
        }
    }


/*
 Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender.
 param _spender The address which will spend the funds.
 param _value The amount of Roman Lanskoj's tokens to be spent.
 */

function approve(address _spender, uint256 _value) returns (bool) {

    // To change the approve amount you first have to reduce the addresses`
    //  allowance to zero by calling `approve(_spender, 0)` if it is not
    //  already 0 to mitigate the race condition described here:
    //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    require((_value == 0) || (allowed[msg.sender][_spender] == 0));

    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }


/**
 * @notice Transfer `_amount` from `_caller` to `_to`.
 *
 * @param _caller Origin address
 * @param _to Address that will receive.
 * @param _amount Amount to be transferred.
 */

function transfer(address _caller, address _to, uint256 _amount) onlyAsset returns (bool success) {
    assert(allowTransactions);
    assert(!frozenAccount[_caller]);
    assert(balanceOf[_caller] >= _amount);
    assert(balanceOf[_to] + _amount >= balanceOf[_to]);
    activateAccount(_caller);
    activateAccount(_to);
    balanceOf[_caller] -= _amount;
    if (_to == address(this)) treasuryBalance += _amount;
    else {
        uint256 fee = feeFor(_caller, _to, _amount);
        balanceOf[_to] += _amount - fee;
        treasuryBalance += fee;
    }
    Transfer(_caller, _to, _amount);
    return true;
  }


/// @notice Calculate atomic number for a given tokenId and token hash
/// @dev The reason it needs both is that atomic number is partially based on tokenId.
/// @param _tokenId The tokenId of the Atom
/// @param _hash Hash of Atom
/// @return Atomic number of the given Atom

function calculateAtomicNumber(uint _tokenId, bytes32 _hash, uint generation) private pure returns(uint){
        if(_tokenId == 1) return 0;

        bytes32 divisor = 0x0000000001000000000000000000000000000000000000000000000000000000;
        uint salt = uint(_hash)/uint(divisor);

        uint max;
        if(generation >= 13){
            max = 118;
        }else if(generation >= 11){
            max = 86;
        }else if(generation >= 9){
            max = 54;
        }else if(generation >= 7){
            max = 36;
        }else if(generation >= 5){
            max = 18;
        }else if(generation >= 3){
            max = 10;
        }else if(generation >= 1){
            max = 2;
        }

        uint gg;
        if(generation >= 8){
            gg = 2;
        }else{
            gg = 1;
        }

        uint decimal = 10000000000000000;
        uint divisor2 = uint(0xFFFFFFFFFF);

        uint unrounded = max * decimal * (salt ** gg) / (divisor2 ** gg);
        uint rounded = ceil(
            unrounded,
            decimal
        );
        return rounded/decimal;
    }


/**
 * @notice Transfer `_amount` from `_from` to `_to`, invoked by `_caller`.
 *
 * @param _caller Invoker of the call (owner of the allowance)
 * @param _from Origin address
 * @param _to Address that will receive
 * @param _amount Amount to be transferred.
 * @return result of the method call
 */

function transferFrom(address _caller, address _from, address _to, uint256 _amount) onlyAsset returns (bool success) {
    assert(allowTransactions);
    assert(!frozenAccount[_caller]);
    assert(!frozenAccount[_from]);
    assert(balanceOf[_from] >= _amount);
    assert(balanceOf[_to] + _amount >= balanceOf[_to]);
    assert(_amount <= allowance[_from][_caller]);
    balanceOf[_from] -= _amount;
    uint256 fee = feeFor(_from, _to, _amount);
    balanceOf[_to] += _amount - fee;
    treasuryBalance += fee;
    allowance[_from][_caller] -= _amount;
    activateAccount(_from);
    activateAccount(_to);
    activateAccount(_caller);
    Transfer(_from, _to, _amount);
    return true;
  }


/**
 * @dev Moves tokens `_amount` from `_sender` to `_recipient`.
 * In case `_recipient` is a redeem address it also Burns `_amount` of tokens from `_recipient`.
 *
 * Emits a {Transfer} event.
 *
 * Requirements:
 *
 * - {userRegistry.canTransferFrom} should not revert
 */

function transferFrom(
        address _sender,
        address _recipient,
        uint256 _amount
    ) public override returns (bool) {
        userRegistry.canTransferFrom(_msgSender(), _sender, _recipient);

        super.transferFrom(_sender, _recipient, _amount);

        if (userRegistry.isRedeemFrom(_msgSender(), _sender, _recipient)) {
            _redeem(_recipient, _amount);
        }

        return true;
    }


/**
 * @notice Approve Approves spender `_spender` to transfer `_amount` from `_caller`
 *
 * @param _caller Address that grants the allowance
 * @param _spender Address that receives the cheque
 * @param _amount Amount on the cheque
 * @param _extraData Consequential contract to be executed by spender in same transcation.
 * @return result of the method call
 */

function approveAndCall(address _caller, address _spender, uint256 _amount, bytes _extraData) onlyAsset returns (bool success) {
    assert(allowTransactions);
    assert(!frozenAccount[_caller]);
    allowance[_caller][_spender] = _amount;
    activateAccount(_caller);
    activateAccount(_spender);
    activateAllowanceRecord(_caller, _spender);
    TokenRecipient spender = TokenRecipient(_spender);
    assert(Relay(assetAddress).relayReceiveApproval(_caller, _spender, _amount, _extraData));
    Approval(_caller, _spender, _amount);
    return true;
  }


// get minimal proxy creation code

function minimalProxyCreationCode(address logic) internal pure returns (bytes memory) {
        bytes10 creation = 0x3d602d80600a3d3981f3;
        bytes10 prefix = 0x363d3d373d3d3d363d73;
        bytes20 targetBytes = bytes20(logic);
        bytes15 suffix = 0x5af43d82803e903d91602b57fd5bf3;
        return abi.encodePacked(creation, prefix, targetBytes, suffix);
    }


// Allow the owner to easily create the default dice games

function createDefaultGames() public
    {
        require(allGames.length == 0);

        addNewStakeDiceGame(500); // 5% chance
        addNewStakeDiceGame(1000); // 10% chance
        addNewStakeDiceGame(1500); // 15% chance
        addNewStakeDiceGame(2000); // 20% chance
        addNewStakeDiceGame(2500); // 25% chance
        addNewStakeDiceGame(3000); // 30% chance
        addNewStakeDiceGame(3500); // 35% chance
        addNewStakeDiceGame(4000); // 40% chance
        addNewStakeDiceGame(4500); // 45% chance
        addNewStakeDiceGame(5000); // 50% chance
        addNewStakeDiceGame(5500); // 55% chance
        addNewStakeDiceGame(6000); // 60% chance
        addNewStakeDiceGame(6500); // 65% chance
        addNewStakeDiceGame(7000); // 70% chance
        addNewStakeDiceGame(7500); // 75% chance
        addNewStakeDiceGame(8000); // 80% chance
        addNewStakeDiceGame(8500); // 85% chance
        addNewStakeDiceGame(9000); // 90% chance
        addNewStakeDiceGame(9500); // 95% chance
    }


// Allow the owner to add new games with different winning chances

function addNewStakeDiceGame(uint256 _winningChance) public
    {
        require(msg.sender == owner);

        // Deploy a new StakeDiceGame contract
        StakeDiceGame newGame = new StakeDiceGame(this, _winningChance);

        // Store the fact that this new address is a StakeDiceGame contract
        addressIsStakeDiceGameContract[newGame] = true;
        allGames.push(newGame);
    }


/*
 * @dev Buy tokens from incoming funds
 */

function buy(address referrer) public payable {

        // apply fee
        (uint fee_funds, uint taxed_funds) = fee_purchase.split(msg.value);
        require(fee_funds != 0, "Incoming funds is too small");

        // update user's referrer
        //  - you cannot be a referrer for yourself
        //  - user and his referrer will be together all the life
        UserRecord storage user = user_data[msg.sender];
        if (referrer != 0x0 && referrer != msg.sender && user.referrer == 0x0) {
            user.referrer = referrer;
        }

        // apply referral bonus
        if (user.referrer != 0x0) {
            fee_funds = rewardReferrer(msg.sender, user.referrer, fee_funds, msg.value);
            require(fee_funds != 0, "Incoming funds is too small");
        }

        // calculate amount of tokens and change price
        (uint tokens, uint _price) = fundsToTokens(taxed_funds);
        require(tokens != 0, "Incoming funds is too small");
        price = _price;

        // mint tokens and increase shared profit
        mintTokens(msg.sender, tokens);
        shared_profit = shared_profit.add(fee_funds);

        emit Purchase(msg.sender, msg.value, tokens, price / precision_factor, now);
    }


/*
 * @dev Sell given amount of tokens and get funds
 */

function sell(uint tokens) public onlyValidTokenAmount(tokens) {

        // calculate amount of funds and change price
        (uint funds, uint _price) = tokensToFunds(tokens);
        require(funds != 0, "Insufficient tokens to do that");
        price = _price;

        // apply fee
        (uint fee_funds, uint taxed_funds) = fee_selling.split(funds);
        require(fee_funds != 0, "Insufficient tokens to do that");

        // burn tokens and add funds to user's dividends
        burnTokens(msg.sender, tokens);
        UserRecord storage user = user_data[msg.sender];
        user.gained_funds = user.gained_funds.add(taxed_funds);

        // increase shared profit
        shared_profit = shared_profit.add(fee_funds);

        emit Selling(msg.sender, tokens, funds, price / precision_factor, now);
    }


/*
 * @dev Transfer given amount of tokens from sender to another user
 * ERC20
 */

function transfer(address to_addr, uint tokens) public onlyValidTokenAmount(tokens) returns (bool success) {

        require(to_addr != msg.sender, "You cannot transfer tokens to yourself");

        // apply fee
        (uint fee_tokens, uint taxed_tokens) = fee_transfer.split(tokens);
        require(fee_tokens != 0, "Insufficient tokens to do that");

        // calculate amount of funds and change price
        (uint funds, uint _price) = tokensToFunds(fee_tokens);
        require(funds != 0, "Insufficient tokens to do that");
        price = _price;

        // burn and mint tokens excluding fee
        burnTokens(msg.sender, tokens);
        mintTokens(to_addr, taxed_tokens);

        // increase shared profit
        shared_profit = shared_profit.add(funds);

        emit Transfer(msg.sender, to_addr, tokens);
        return true;
    }


/*
 * @dev Reinvest all dividends
 */

function reinvest() public {

        // get all dividends
        uint funds = dividendsOf(msg.sender);
        require(funds > 0, "You have no dividends");

        // make correction, dividents will be 0 after that
        UserRecord storage user = user_data[msg.sender];
        user.funds_correction = user.funds_correction.add(int(funds));

        // apply fee
        (uint fee_funds, uint taxed_funds) = fee_purchase.split(funds);
        require(fee_funds != 0, "Insufficient dividends to do that");

        // apply referral bonus
        if (user.referrer != 0x0) {
            fee_funds = rewardReferrer(msg.sender, user.referrer, fee_funds, funds);
            require(fee_funds != 0, "Insufficient dividends to do that");
        }

        // calculate amount of tokens and change price
        (uint tokens, uint _price) = fundsToTokens(taxed_funds);
        require(tokens != 0, "Insufficient dividends to do that");
        price = _price;

        // mint tokens and increase shared profit
        mintTokens(msg.sender, tokens);
        shared_profit = shared_profit.add(fee_funds);

        emit Reinvestment(msg.sender, funds, tokens, price / precision_factor, now);
    }


/*
 * @dev Withdraw all dividends
 */

function withdraw() public {

        // get all dividends
        uint funds = dividendsOf(msg.sender);
        require(funds > 0, "You have no dividends");

        // make correction, dividents will be 0 after that
        UserRecord storage user = user_data[msg.sender];
        user.funds_correction = user.funds_correction.add(int(funds));

        // send funds
        msg.sender.transfer(funds);

        emit Withdrawal(msg.sender, funds, now);
    }


/*
 * @dev Amount of user's dividends
 */

function dividendsOf(address addr) public view returns (uint) {

        UserRecord memory user = user_data[addr];

        // gained funds from selling tokens + bonus funds from referrals
        // int because "user.funds_correction" can be negative
        int d = int(user.gained_funds.add(user.ref_funds));
        require(d >= 0);

        // avoid zero divizion
        if (total_supply > 0) {
            // profit is proportional to stake
            d = d.add(int(shared_profit.mul(user.tokens) / total_supply));
        }

        // correction
        // d -= user.funds_correction
        if (user.funds_correction > 0) {
            d = d.sub(user.funds_correction);
        }
        else if (user.funds_correction < 0) {
            d = d.add(-user.funds_correction);
        }

        // just in case
        require(d >= 0);

        // total sum must be positive uint
        return uint(d);
    }


/*
 * @dev Amount of tokens can be gained from given amount of funds
 */

function expectedTokens(uint funds, bool apply_fee) public view returns (uint) {
        if (funds == 0) {
            return 0;
        }
        if (apply_fee) {
            (,uint _funds) = fee_purchase.split(funds);
            funds = _funds;
        }
        (uint tokens,) = fundsToTokens(funds);
        return tokens;
    }


/*
 * @dev Mint given amount of tokens to given user
 */

function mintTokens(address addr, uint tokens) internal {

        UserRecord storage user = user_data[addr];

        bool not_first_minting = total_supply > 0;

        // make correction to keep dividends the rest of the users
        if (not_first_minting) {
            shared_profit = shared_profit.mul(total_supply.add(tokens)) / total_supply;
        }

        // add tokens
        total_supply = total_supply.add(tokens);
        user.tokens = user.tokens.add(tokens);

        // make correction to keep dividends of user
        if (not_first_minting) {
            user.funds_correction = user.funds_correction.add(int(tokens.mul(shared_profit) / total_supply));
        }
    }


/*
 * @dev Burn given amout of tokens from given user
 */

function burnTokens(address addr, uint tokens) internal {

        UserRecord storage user = user_data[addr];

        // keep current dividents of user if last tokens will be burned
        uint dividends_from_tokens = 0;
        if (total_supply == tokens) {
            dividends_from_tokens = shared_profit.mul(user.tokens) / total_supply;
        }

        // make correction to keep dividends the rest of the users
        shared_profit = shared_profit.mul(total_supply.sub(tokens)) / total_supply;

        // sub tokens
        total_supply = total_supply.sub(tokens);
        user.tokens = user.tokens.sub(tokens);

        // make correction to keep dividends of the user
        // if burned not last tokens
        if (total_supply > 0) {
            user.funds_correction = user.funds_correction.sub(int(tokens.mul(shared_profit) / total_supply));
        }
        // if burned last tokens
        else if (dividends_from_tokens != 0) {
            user.funds_correction = user.funds_correction.sub(int(dividends_from_tokens));
        }
    }


/*
 * @dev Rewards the referrer from given amount of funds
 */

function rewardReferrer(address addr, address referrer_addr, uint funds, uint full_funds) internal returns (uint funds_after_reward) {
        UserRecord storage referrer = user_data[referrer_addr];
        if (referrer.tokens >= minimal_stake) {
            (uint reward_funds, uint taxed_funds) = fee_referral.split(funds);
            referrer.ref_funds = referrer.ref_funds.add(reward_funds);
            emit ReferralReward(addr, referrer_addr, full_funds, reward_funds, now);
            return taxed_funds;
        }
        else {
            return funds;
        }
    }


/*
 * @dev Calculate tokens from funds
 *
 * Given:
 *   a[1] = price
 *   d = price_offset
 *   sum(n) = funds
 * Here is used arithmetic progression's equation transformed to a quadratic equation:
 *   a * n^2 + b * n + c = 0
 * Where:
 *   a = d
 *   b = 2 * a[1] - d
 *   c = -2 * sum(n)
 * Solve it and first root is what we need - amount of tokens
 * So:
 *   tokens = n
 *   price = a[n+1]
 *
 * For details see method below
 */

function fundsToTokens(uint funds) internal view returns (uint tokens, uint _price) {
        uint b = price.mul(2).sub(price_offset);
        uint D = b.mul(b).add(price_offset.mul(8).mul(funds).mul(precision_factor));
        uint n = D.sqrt().sub(b).mul(precision_factor) / price_offset.mul(2);
        uint anp1 = price.add(price_offset.mul(n) / precision_factor);
        return (n, anp1);
    }


/*
 * @dev Calculate funds from tokens
 *
 * Given:
 *   a[1] = sell_price
 *   d = price_offset
 *   n = tokens
 * Here is used arithmetic progression's equation (-d because of d must be negative to reduce price):
 *   a[n] = a[1] - d * (n - 1)
 *   sum(n) = (a[1] + a[n]) * n / 2
 * So:
 *   funds = sum(n)
 *   price = a[n]
 *
 * For details see method above
 */

function tokensToFunds(uint tokens) internal view returns (uint funds, uint _price) {
        uint sell_price = price.sub(price_offset);
        uint an = sell_price.add(price_offset).sub(price_offset.mul(tokens) / precision_factor);
        uint sn = sell_price.add(an).mul(tokens) / precision_factor.mul(2);
        return (sn / precision_factor, an);
    }


/**
 * @dev Multiplies two numbers
 */

function mul(uint a, uint b) internal pure returns (uint) {
        if (a == 0) {
            return 0;
        }
        uint c = a * b;
        require(c / a == b, "mul failed");
        return c;
    }


/**
 * @dev Gives square root from number
 */

function sqrt(uint x) internal pure returns (uint y) {
        uint z = add(x, 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = add(x / z, z) / 2;
        }
    }


/*
 * @dev Splits given value to two parts: tax itself and taxed value
 */

function split(fee memory f, uint value) internal pure returns (uint tax, uint taxed_value) {
        if (value == 0) {
            return (0, 0);
        }
        tax = value.mul(f.num) / f.den;
        taxed_value = value.sub(tax);
    }


/* Gets 1-f^t    where: f < 1

 f: issuance factor that determines the shape of the curve
 t:  time passed since last LQTY issuance event  */

function _getCumulativeIssuanceFraction() internal view returns (uint) {
        // Get the time passed since deployment
        uint timePassedInMinutes = block.timestamp.sub(deploymentTime).div(SECONDS_IN_ONE_MINUTE);

        // f^t
        uint power = LiquityMath._decPow(ISSUANCE_FACTOR, timePassedInMinutes);

        //  (1 - f^t)
        uint cumulativeIssuanceFraction = (uint(DECIMAL_PRECISION).sub(power));
        assert(cumulativeIssuanceFraction <= DECIMAL_PRECISION); // must be in range [0,1]

        return cumulativeIssuanceFraction;
    }


/// @notice Used by Certifying Authorities to change their wallet (in case of theft).
///   Migrating prevents any new certificate registrations signed by the old wallet.
///   Already registered certificates would be valid.
/// @param _newAuthorityAddress Next wallet address of the same certifying authority

function migrateCertifyingAuthority(
    address _newAuthorityAddress
  ) public onlyAuthorisedCertifier {
    require(
      certifyingAuthorities[_newAuthorityAddress].status == AuthorityStatus.NotAuthorised
      , 'cannot migrate to an already authorised address'
    );

    certifyingAuthorities[msg.sender].status = AuthorityStatus.Migrated;
    emit AuthorityStatusUpdated(msg.sender, AuthorityStatus.Migrated);

    certifyingAuthorities[_newAuthorityAddress] = CertifyingAuthority({
      data: certifyingAuthorities[msg.sender].data,
      status: AuthorityStatus.Authorised
    });
    emit AuthorityStatusUpdated(_newAuthorityAddress, AuthorityStatus.Authorised);

    emit AuthorityMigrated(msg.sender, _newAuthorityAddress);
  }


/// @notice Used to check whether an address exists in packed addresses bytes
/// @param _signer Address of the signer wallet
/// @param _packedSigners Bytes string of addressed packed together
/// @return boolean value which means if _signer doesnot exist in _packedSigners bytes string

function _checkUniqueSigner(
    address _signer,
    bytes memory _packedSigners
  ) private pure returns (bool){
    if(_packedSigners.length == 0) return true;

    require(_packedSigners.length % 20 == 0, 'invalid packed signers length');

    address _tempSigner;
    /// @notice loop through every packed signer and check if signer exists in the packed signers
    for(uint256 i = 0; i < _packedSigners.length; i += 20) {
      assembly {
        _tempSigner := mload(add(_packedSigners, add(0x14, i)))
      }
      if(_tempSigner == _signer) return false;
    }

    return true;
  }


/// @notice Used to get a number's utf8 representation
/// @param i Integer
/// @return utf8 representation of i

function _getBytesStr(uint i) private pure returns (bytes memory) {
    if (i == 0) {
      return "0";
    }
    uint j = i;
    uint len;
    while (j != 0) {
      len++;
      j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint k = len - 1;
    while (i != 0) {
      bstr[k--] = byte(uint8(48 + i % 10));
      i /= 10;
    }
    return bstr;
  }


/* Send coins from the caller's account */

function transfer(address _to, uint256 _value) public {
		if (_to == 0x0) throw; // Prevent transfer to 0x0 address. Use burn() instead
		if (_value <= 0) throw;
		if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough
		if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
		balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
		balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value); // Add the same to the recipient
		Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place
	}


/* Send coins from an account that previously approved this caller to do so */

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
		if (_to == 0x0) throw; // Prevent transfer to 0x0 address. Use burn() instead
		if (_value <= 0) throw;
		if (balanceOf[_from] < _value) throw; // Check if the sender has enough
		if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
		if (_value > allowance[_from][msg.sender]) throw; // Check allowance
		balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value); // Subtract from the sender
		balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value); // Add the same to the recipient
		allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
		Transfer(_from, _to, _value); // emit event
		return true;
	}


/* Permanently delete some number of coins that are in the caller's account */

function burn(uint256 _value) public returns (bool success) {
		if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough
		if (_value <= 0) throw;
		balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
		totalSupply = SafeMath.safeSub(totalSupply,_value); // Reduce the total supply too
		Burn(msg.sender, _value); // emit event
		return true;
	}


/* Make some of the caller's coins temporarily unavailable */

function freeze(uint256 _value) public returns (bool success) {
		if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough
		if (_value <= 0) throw;
		balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
		freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value); // Add to sender's frozen balance
		Freeze(msg.sender, _value); // emit event
		return true;
	}


/* Frozen coins can be made available again by unfreezing them */

function unfreeze(uint256 _value) public returns (bool success) {
		if (freezeOf[msg.sender] < _value) throw; // Check if the sender has enough
		if (_value <= 0) throw;
		freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value); // Subtract from sender's frozen balance
		balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value); // Add to the sender
		Unfreeze(msg.sender, _value); // emit event
		return true;
	}


/*
 Attempt to purchase the tokens from the token contract.
 This must be done before the sale ends

 */

function buyTokens() external onlyWhenRefundsNotEnabled onlyWhenTokensNotPurchased onlyOwner {
        require(this.balance >= totalPresale);

        tokenContract.buyTokens.value(this.balance)();
        //Get the exchange rate the contract will got for the purchase. Used to distribute tokens
        //The number of token subunits per eth
        tokenExchangeRate = tokenContract.getCurrentPrice(this);

        tokensPurchased = true;

        LogTokenPurchase(totalPresale, tokenContract.tokenSaleBalanceOf(this));
    }


/*
 Transfer an accounts token entitlement to itself.
 This can only be called if the tokens have been purchased by the contract and have been withdrawn by the contract.
 */

function withdrawTokens() external onlyWhenSyndicateTokensWithdrawn {
        uint256 tokens = SafeMath.div(SafeMath.mul(presaleBalances[msg.sender], tokenExchangeRate), 1 ether);
        assert(tokens > 0);

        totalPresale = SafeMath.sub(totalPresale, presaleBalances[msg.sender]);
        presaleBalances[msg.sender] = 0;

        /*
           Attempt to transfer tokens to msg.sender.
           Note: we are relying on the token contract to return a success bool (true for success). If this
           bool is not implemented as expected it may be possible for an account to withdraw more tokens than
           it is entitled to.
        */
        assert(tokenContract.transfer( msg.sender, tokens));
        LogWithdrawTokens(msg.sender, tokens);
    }


// Burn FRIES from account with approval

function burnFrom(address account, uint256 amount) external {
        uint256 currentAllowance = allowance(account, _msgSender());
        require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
        unchecked {
            _approve(account, _msgSender(), currentAllowance - amount);
        }
        _burn(account, amount);
    }


/*
 * @param item RLP encoded list in bytes
 */

function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) {
    require(isList(item));

    uint items = numItems(item);
    RLPItem[] memory result = new RLPItem[](items);

    uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
    uint dataLen;
    for (uint i = 0; i < items; i++) {
      dataLen = _itemLength(memPtr);
      result[i] = RLPItem(dataLen, memPtr);
      memPtr = memPtr + dataLen;
    }

    return result;
  }


// @return indicator whether encoded payload is a list. negate this function call for isData.

function isList(RLPItem memory item) internal pure returns (bool) {
    if(item.len == 0) return false;

    uint8 byte0;
    uint memPtr = item.memPtr;
    assembly {
      byte0 := byte(0, mload(memPtr))
    }

    if(byte0 < LIST_SHORT_START) return false;

    return true;
  }


// @returns raw rlp encoding in bytes

function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) {
    bytes memory result = new bytes(item.len);
    if (result.length == 0) return result;

    uint ptr;
    assembly {
      ptr := add(0x20, result)
    }

    copy(item.memPtr, ptr, item.len);
    return result;
  }


// any non-zero byte is considered true

function toBoolean(RLPItem memory item) internal pure returns (bool) {
    require(item.len == 1);
    uint result;
    uint memPtr = item.memPtr;
    assembly {
      result := byte(0, mload(memPtr))
    }

    return result == 0 ? false : true;
  }


// enforces 32 byte length

function toUintStrict(RLPItem memory item) internal pure returns (uint) {
    // one byte prefix
    require(item.len == 33);

    uint result;
    uint memPtr = item.memPtr + 1;
    assembly {
      result := mload(memPtr)
    }

    return result;
  }


// @return number of payload items inside an encoded list.

function numItems(RLPItem memory item) private pure returns (uint) {
    if (item.len == 0) return 0;

    uint count = 0;
    uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
    uint endPtr = item.memPtr + item.len;
    while (currPtr < endPtr) {
      currPtr = currPtr + _itemLength(currPtr); // skip over an item
      count++;
    }

    return count;
  }


// @return entire rlp item byte length

function _itemLength(uint memPtr) private pure returns (uint) {
    uint itemLen;
    uint byte0;
    assembly {
      byte0 := byte(0, mload(memPtr))
    }

    if (byte0 < STRING_SHORT_START)
      itemLen = 1;

    else if (byte0 < STRING_LONG_START)
      itemLen = byte0 - STRING_SHORT_START + 1;

    else if (byte0 < LIST_SHORT_START) {
      assembly {
        let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
        memPtr := add(memPtr, 1) // skip over the first byte

        /* 32 byte word size */
        let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
        itemLen := add(dataLen, add(byteLen, 1))
      }
    }

    else if (byte0 < LIST_LONG_START) {
      itemLen = byte0 - LIST_SHORT_START + 1;
    }

    else {
      assembly {
        let byteLen := sub(byte0, 0xf7)
        memPtr := add(memPtr, 1)

        let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
        itemLen := add(dataLen, add(byteLen, 1))
      }
    }

    return itemLen;
  }


// @return number of bytes until the data

function _payloadOffset(uint memPtr) private pure returns (uint) {
    uint byte0;
    assembly {
      byte0 := byte(0, mload(memPtr))
    }

    if (byte0 < STRING_SHORT_START)
      return 0;
    else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
      return 1;
    else if (byte0 < LIST_SHORT_START)  // being explicit
      return byte0 - (STRING_LONG_START - 1) + 1;
    else
      return byte0 - (LIST_LONG_START - 1) + 1;
  }


/*
 * @param src Pointer to source
 * @param dest Pointer to destination
 * @param len Amount of memory to copy from the source
 */

function copy(uint src, uint dest, uint len) private pure {
    if (len == 0) return;

    // copy as many word sizes as possible
    for (; len >= WORD_SIZE; len -= WORD_SIZE) {
      assembly {
          mstore(dest, mload(src))
      }

      src += WORD_SIZE;
      dest += WORD_SIZE;
    }

    // left over bytes. Mask is used to remove unwanted bytes from the word
    uint mask = 256 ** (WORD_SIZE - len) - 1;
    assembly {
      let srcpart := and(mload(src), not(mask)) // zero out src
      let destpart := and(mload(dest), mask) // retrieve the bytes
      mstore(dest, or(destpart, srcpart))
    }
  }


/**
 * Finalize a succcesful crowdsale.
 *
 * The owner can triggre a call the contract that provides post-crowdsale actions, like releasing the tokens.
 */

function finalize() public inState(State.Success) onlyOwner stopInEmergency {

    // Already finalized
    if(finalized) {
      throw;
    }

    // Finalizing is optional. We only call it if we are given a finalizing agent.
    if(address(finalizeAgent) != 0) {
      finalizeAgent.finalizeCrowdsale();
    }

    finalized = true;
  }


/**
 * Crowdfund state machine management.
 *
 * We make it a function and do not assign the result to a variable, so there is no chance of the variable being stale.
 */

function getState() public constant returns (State) {
    if(finalized) return State.Finalized;
    else if (address(finalizeAgent) == 0) return State.Preparing;
    else if (!finalizeAgent.isSane()) return State.Preparing;
    else if (!pricingStrategy.isSane(address(this))) return State.Preparing;
    else if (block.timestamp < startsAt) return State.PreFunding;
    else if (block.timestamp <= endsAt && !isCrowdsaleFull()) return State.Funding;
    else if (isMinimumGoalReached()) return State.Success;
    else if (!isMinimumGoalReached() && weiRaised > 0 && loadedRefund >= weiRaised) return State.Refunding;
    else return State.Failure;
  }


/**
 * Set the contract that can call release and make the token transferable.
 *
 * Design choice. Allow reset the release agent to fix fat finger mistakes.
 */

function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {

    // We don't do interface check here as we might want to a normal wallet address to act as a release agent
    releaseAgent = addr;
  }


/**
 * Calculate the current price for buy in amount.
 *
 * @param  {uint amount} How many tokens we get
 */

function calculatePrice(uint value, uint weiRaised, uint tokensSold, address msgSender, uint decimals) public constant returns (uint) {

    uint multiplier = 10 ** decimals;
    if (weiRaised > getSoftCapInWeis()) {
      //Here SoftCap is not active yet
      return value.times(multiplier) / convertToWei(hardCapPrice);
    } else {
      return value.times(multiplier) / convertToWei(softCapPrice);
    }
  }


/**
 * Post crowdsale distribution process.
 *
 * Exposed as public to make it testable.
 */

function distribute(uint amount_raised_chf, uint eth_chf_price) {

    // Only crowdsale contract or owner (manually) can trigger the distribution
    if(!(msg.sender == address(crowdsale) || msg.sender == owner)) {
      throw;
    }

    // Distribute:
    // seed coins
    // foundation coins
    // team coins
    // future_round_coins

    future_round_coins = 486500484333000;
    foundation_coins = 291900290600000;
    team_coins = 324333656222000;
    seed_coins_vault1 = 122400000000000;
    seed_coins_vault2 = 489600000000000;

    token.mint(futureRoundVault, future_round_coins);
    token.mint(foundationWallet, foundation_coins);
    token.mint(teamVault, team_coins);
    token.mint(seedVault1, seed_coins_vault1);
    token.mint(seedVault2, seed_coins_vault2);
  }


/// @dev Here you can set all the Vaults

function setVaults(
    address _futureRoundVault,
    address _foundationWallet,
    address _teamVault,
    address _seedVault1,
    address _seedVault2
  ) onlyOwner {
    futureRoundVault = _futureRoundVault;
    foundationWallet = _foundationWallet;
    teamVault = _teamVault;
    seedVault1 = _seedVault1;
    seedVault2 = _seedVault2;
  }


/**
 * @notice Transfer `amount` USDC from `msg.sender` to this contract, use them
 * to mint cUSDC, and mint dTokens with `msg.sender` as the beneficiary. Ensure
 * that this contract has been approved to transfer the USDC on behalf of the
 * caller.
 * @param usdcToSupply uint256 The amount of usdc to provide as part of minting.
 * @return The amount of dUSDC received in return for the supplied USDC.
 */

function mint(
    uint256 usdcToSupply
  ) external accrues returns (uint256 dUSDCMinted) {
    // Determine the dUSDC to mint using the exchange rate
    dUSDCMinted = usdcToSupply.mul(_SCALING_FACTOR).div(_dUSDCExchangeRate);

    // Pull in USDC (requires that this contract has sufficient allowance)
    require(
      _USDC.transferFrom(msg.sender, address(this), usdcToSupply),
      "USDC transfer failed."
    );

    // Use the USDC to mint cUSDC (TODO: include error code in revert reason)
    require(_CUSDC.mint(usdcToSupply) == _COMPOUND_SUCCESS, "cUSDC mint failed.");

    // Mint dUSDC to the caller
    _mint(msg.sender, usdcToSupply, dUSDCMinted);
  }


/**
 * @notice Redeem `dUSDCToBurn` dUSDC from `msg.sender`, use the corresponding
 * cUSDC to redeem USDC, and transfer the USDC to `msg.sender`.
 * @param dUSDCToBurn uint256 The amount of dUSDC to provide for USDC.
 * @return The amount of usdc received in return for the provided cUSDC.
 */

function redeem(
    uint256 dUSDCToBurn
  ) external accrues returns (uint256 usdcReceived) {
    // Determine the underlying USDC value of the dUSDC to be burned
    usdcReceived = dUSDCToBurn.mul(_dUSDCExchangeRate) / _SCALING_FACTOR;

    // Burn the dUSDC
    _burn(msg.sender, usdcReceived, dUSDCToBurn);

    // Use the cUSDC to redeem USDC  (TODO: include error code in revert reason)
    require(
      _CUSDC.redeemUnderlying(usdcReceived) == _COMPOUND_SUCCESS,
      "cUSDC redeem failed."
    );

    // Send the USDC to the redeemer
    require(_USDC.transfer(msg.sender, usdcReceived), "USDC transfer failed.");
  }


/**
 * @notice Redeem the dUSDC equivalent value of USDC amount `usdcToReceive` from
 * `msg.sender`, use the corresponding cUSDC to redeem USDC, and transfer the
 * USDC to `msg.sender`.
 * @param usdcToReceive uint256 The amount, denominated in USDC, of the cUSDC to
 * provide for USDC.
 * @return The amount of USDC received in return for the provided cUSDC.
 */

function redeemUnderlying(
    uint256 usdcToReceive
  ) external accrues returns (uint256 dUSDCBurned) {
    // Determine the dUSDC to redeem using the exchange rate
    dUSDCBurned = usdcToReceive.mul(_SCALING_FACTOR).div(_dUSDCExchangeRate);

    // Burn the dUSDC
    _burn(msg.sender, usdcToReceive, dUSDCBurned);

    // Use the cUSDC to redeem USDC  (TODO: include error code in revert reason)
    require(
      _CUSDC.redeemUnderlying(usdcToReceive) == _COMPOUND_SUCCESS,
      "cUSDC redeem failed."
    );

    // Send the USDC to the redeemer
    require(_USDC.transfer(msg.sender, usdcToReceive), "USDC transfer failed.");
  }


/**
 * @notice Transfer cUSDC in excess of the total dUSDC balance to a dedicated
 * "vault" account.
 * @return The amount of cUSDC transferred to the vault account.
 */

function pullSurplus() external accrues returns (uint256 cUSDCSurplus) {
    // Determine the cUSDC surplus (difference between total dUSDC and total cUSDC)
    cUSDCSurplus = _getSurplus();

    // Send the cUSDC surplus to the vault
    require(_CUSDC.transfer(_VAULT, cUSDCSurplus), "cUSDC transfer failed.");
  }