// Purchase tokens

function buyTokens(address beneficiary) public nonReentrant payable whenNotPaused {
    require(beneficiary != address(0));
    require(validPurchase());

    uint256 weiAmount = msg.value;
    uint256 returnWeiAmount;

    // calculate token amount to be created
    uint rate = getRate();
    assert(rate > 0);
    uint256 tokens = weiAmount.mul(rate);

    uint256 newsoldTokens = soldTokens.add(tokens);

    if (newsoldTokens > contractCap) {
      newsoldTokens = contractCap;
      tokens = contractCap.sub(soldTokens);
      uint256 newWeiAmount = tokens.div(rate);
      returnWeiAmount = weiAmount.sub(newWeiAmount);
      weiAmount = newWeiAmount;
    }

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

    token.transfer(beneficiary, tokens);
    soldTokens = newsoldTokens;
    if (returnWeiAmount > 0){
      msg.sender.transfer(returnWeiAmount);
    }

    emit TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);

    forwardFunds();
  }


/**
 * @dev Bootstrap the supply distribution and fund the UniswapV2 liquidity pool
 */

function bootstrap() external returns (bool){

      require(isBootStrapped == false, 'Require unintialized token');
      require(msg.sender == owner, 'Require ownership');

      //Distribute tokens
      uint256 premineAmount = 100000000 * (10 ** 18); //100 mil
      uint256 marketingAmount = 1000000000 * (10 ** 18); // 1 bil for justin sun

      balances[marketingAccount] = marketingAmount;
      emit Transfer(address(0), marketingAccount, marketingAmount);

      for (uint256 i = 0; i < 15; i++) {
        balances[ambassadorList[i]] = premineAmount;
        emit Transfer(address(0), ambassadorList[i], balances[ambassadorList[i]]);
      }
      balances[owner] = _totalSupply.sub(marketingAmount + 15 * premineAmount);

      emit Transfer(address(0), owner, balances[owner]);

      isBootStrapped = true;

      return isBootStrapped;

  }


// the function is name differently to not cause inheritance clash in truffle and allows tests

function initializeVault(address _storage,
        address _underlying,
        uint256 _toInvestNumerator,
        uint256 _toInvestDenominator
    ) public initializer {
        require(_toInvestNumerator <= _toInvestDenominator, "cannot invest more than 100%");
        require(_toInvestDenominator != 0, "cannot divide by 0");

        ERC20Detailed.initialize(
        string(abi.encodePacked("DELTA_", ERC20Detailed(_underlying).symbol())),
        string(abi.encodePacked("d", ERC20Detailed(_underlying).symbol())),
        ERC20Detailed(_underlying).decimals()
        );
        ControllableInit.initialize(
        _storage
        );

        uint256 underlyingUnit = 10 ** uint256(ERC20Detailed(address(_underlying)).decimals());
        uint256 implementationDelay = 12 hours;
        uint256 strategyChangeDelay = 12 hours;
        VaultStorage.initialize(
        _underlying,
        _toInvestNumerator,
        _toInvestDenominator,
        underlyingUnit,
        implementationDelay,
        strategyChangeDelay
        );
    }


//Get the tokens that exist for this wallet

function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokenIds;
  }


// if any applicable tokens are held by this contract, it should be able to deposit them

function _depositAssets ()
    internal {
        uint LPBalance = IERC20(liquidity).balanceOf(address(this));    // load LP balance into memory

        if(LPBalance > 0)                                               // are there any LP tokens?
            IStakingRewards(staking).deposit(LPBalance);             // deposit the entire amount into the StakingRewards contract

        uint krillBalance = IERC20(krill).balanceOf(address(this));      // load KRILL balance into memory

        if(krillBalance > 0 )                                           // is there any krill here dumped from another contract?
            ICompounder(cKrill).buy(krillBalance);                          // buy cKRILL with whatever balance is held
    }


// claim rewards from NFTs, staking, and cKRILL position

function _claimRewardsAndDisburse()
    internal {
        IClaimable(staking).claim();    // Claim rewards from the LP staking contract
        IClaimable(whalesGame).claim(); // Claim rewards from the whales game contract

        if(ICompounder(cKrill).dividendsOf(address(this)) > 0)      // If this contract has rewards to claim
            ICompounder(cKrill).withdraw();                         // Claim rewards from the cKRILL held by this contract

        uint krillBalance = IERC20(krill).balanceOf(address(this)); // load KRILL balance into memory

        if(krillBalance > 0)                                // is there any krill here dumped from another contract?
            IERC20(krill).burn(krillBalance);               // burn it all
    }


// Callback function, distribute tokens to sender when ETH donation is recieved

function () payable public {

        require(isLive);
        uint256 donation = msg.value;
        uint256 amountZNT = donation * rateOfZNT;
        uint256 amountZLT = donation * rateOfZLT;
        require(availableZNT >= amountZNT && availableZLT >= amountZLT);
        donationOf[msg.sender] += donation;
        amountEthRaised += donation;
        availableZNT -= amountZNT;
        availableZLT -= amountZLT;
        tokenZNT.transfer(msg.sender, amountZNT);
        tokenZLT.transfer(msg.sender, amountZLT);
        beneficiary.transfer(donation);
    }


/**
 * @dev Resets the player's current progress.
 */

function resetProgress()
        public
        returns (bool) {

            if (playerCurrentProgress[msg.sender] > 0) {

                playerCurrentProgress[msg.sender] = 0;

                playerAddressToBid[msg.sender] = 0;

                playerAddressToRollUnder[msg.sender] = 0;

                emit ResetProgress(
                    msg.sender,
                    true
                );

                return true;

            } else {

                return false;
            }

        }


/**
 * @dev Calculates the payout based on roll under in Wei.
 */

function _calculatePayout(
        uint256 rollUnder,
        uint256 value)
        internal
        view
        isWithinLimits(value)
        returns (uint256) {

            require(rollUnder >= minRoll && rollUnder <= maxRoll, "invalid roll under number!");

            uint256 _totalPayout = 0 wei;

            _totalPayout = ((((value * (100-(SafeMath.sub(rollUnder,1)))) / (SafeMath.sub(rollUnder,1))+value))*platformCutPayout/platformCutPayoutDivisor)-value;

            return _totalPayout;
        }


/*  at initialization, setup the owner */

function WowanderICOPrivateCrowdSale(
        address addressOfTokenUsedAsReward,
		address addressOfBeneficiary
    ) public
    {
        beneficiary = addressOfBeneficiary;
        //startTime = 1516021200;
        startTime = 1516021200 - 3600 * 24; // TODO remove
        duration = 744 hours;
		tokensContractBalance =  5 * 0.1 finney;
        price = 0.000000000005 * 1 ether;
        discountPrice = 0.000000000005 * 1 ether * 0.9;
        tokenReward = WWWToken(addressOfTokenUsedAsReward);
    }


/**
 * @dev Transfer several token for a specified addresses
 * @param _to The array of addresses to transfer to.
 * @param _value The array of amounts to be transferred.
 */

function massTransfer(address[] memory _to, uint[] memory _value) public returns (bool){
        require(_to.length == _value.length, "You have different amount of addresses and amounts");

        uint len = _to.length;
        for(uint i = 0; i < len; i++){
            if(!_transfer(_to[i], _value[i])){
                return false;
            }
        }
        return true;
    }


/**
 * @dev Emit new tokens and transfer from 0 to client address. This function will generate 21.5% of tokens for management address as well.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 */

function _mint(address _to, uint _value) private returns (bool){
        require(_to != address(0), "Receiver address cannot be null");
        require(_to != address(this), "Receiver address cannot be ITCM contract address");
        require(_value > 0 && _value <= leftToMint, "Looks like we are unable to mint such amount");

        // 21.5% of token amount to management address
        uint managementAmount = _value.mul(215).div(1000);

        leftToMint = leftToMint.sub(_value);
        totalSupply = totalSupply.add(_value);
        totalSupply = totalSupply.add(managementAmount);

        balances[_to] = balances[_to].add(_value);
        balances[managementProfitAddr] = balances[managementProfitAddr].add(managementAmount);

        emit Transfer(address(0), _to, _value);
        emit Transfer(address(0), managementProfitAddr, managementAmount);

        return true;
    }


/**
 * @dev This is wrapper for _mint.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 */

function mint(address _to, uint _value) public returns (bool){
        require(msg.sender != address(0), "Sender address cannon be null");
        require(msg.sender == mintingAllowedForAddr || mintingAllowedForAddr == address(0) && msg.sender == contractCreator, "You are unavailable to mint tokens");

        return _mint(_to, _value);
    }


/**
 * @dev Similar to mint function but take array of addresses and values.
 * @param _to The addresses to transfer to.
 * @param _value The amounts to be transferred.
 */

function mint(address[] memory _to, uint[] memory _value) public returns (bool){
        require(_to.length == _value.length, "You have different amount of addresses and amounts");
        require(msg.sender != address(0), "Sender address cannon be null");
        require(msg.sender == mintingAllowedForAddr || mintingAllowedForAddr == address(0) && msg.sender == contractCreator, "You are unavailable to mint tokens");

        uint len = _to.length;
        for(uint i = 0; i < len; i++){
            if(!_mint(_to[i], _value[i])){
                return false;
            }
        }
        return true;
    }


/**
 * @dev Set a contract address that allowed to mint tokens.
 * @param _address The address of another contract.
 */

function setMintingContractAddress(address _address) public returns (bool){
        require(mintingAllowedForAddr == address(0) && msg.sender == contractCreator, "Only contract creator can set minting contract and only when it is not set");
        mintingAllowedForAddr = _address;
        return true;
    }


// Guards against integer overflows

function safeMultiply(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        } else {
            uint256 c = a * b;
            assert(c / a == b);
            return c;
        }
    }


/**
 * @dev this function will send the Team tokens to given address
 * @param _teamAddress ,address of the bounty receiver.
 * @param _value , number of tokens to be sent.
 */

function sendTeamTokens(address _teamAddress, uint256 _value) external whenNotPaused onlyOwner returns (bool) {

        require(teamTokens >= _value);
        totalReleased = totalReleased.add(_value);
        require(totalReleased <= totalSupply());
        teamTokens = teamTokens.sub(_value);
        teamTokenHolder[_teamAddress] = true;
        teamTokenInitially[_teamAddress] = teamTokenInitially[_teamAddress].add((_value.mul(95)).div(100));
        teamLockPeriodStart[_teamAddress] = now;
        super._transfer(address(this),_teamAddress,(_value.mul(5)).div(100));
        return true;

   }


/**
 * @notice Mints the vault shares to the msg.sender
 * @param amount is the amount of `asset` deposited
 */

function _deposit(uint256 amount) private {
        uint256 totalWithDepositedAmount = totalBalance();

        // amount needs to be subtracted from totalBalance because it has already been
        // added to it from either IWETH.deposit and IERC20.safeTransferFrom
        uint256 total = totalWithDepositedAmount.sub(amount);

        uint256 shareSupply = totalSupply();

        // Following the pool share calculation from Alpha Homora:
        // solhint-disable-next-line
        // https://github.com/AlphaFinanceLab/alphahomora/blob/340653c8ac1e9b4f23d5b81e61307bf7d02a26e8/contracts/5/Bank.sol#L104
        uint256 share = shareSupply == 0
            ? amount
            : amount.mul(shareSupply).div(total);

        _mint(msg.sender, share);
    }


/**
 * @dev Get withdraw amount of shares value
 * @param shares How many shares will be exchanged
 */

function getWithdrawAmount(uint256 shares, address _addr)
        public
        view
        returns (uint256 amount)
    {
        uint256 PPS;

        //check if last user: cannot use shares, could be partial withdraw
        uint256 userShareBalance = balanceOf(_addr);

        if (userShareBalance == totalSupply()) {
            //last user
            PPS = getPricePerShare(currentRound);
        } else {
            PPS = getPricePerShare(currentRound.sub(1));
        }

        amount = (shares.mul(PPS)).div(ppsMultiplier);
    }


/**
 * @dev Get mint amount for roll()
 * @param timestamp current timestamp
 */

function getMintAmount(uint timestamp) public view returns (uint256 amount) {
        if (timestamp < deployTime + 31557600) {
            amount = am1 ;
        } else {
            if (timestamp < deployTime + 63115200) {
                amount = am2 ;
            } else {
                if (timestamp < deployTime + 94672800) {
                    amount = am3 ;
                } else {
                    amount = am4 ;
                   }
                }
            }
        }


/*
 * @notice Rolls the vault's funds into a new short position.
 */

function roll() external onlyOwner {
        require(block.timestamp >= readyAt, "not ready to roll yet");

        uint mintAmount = getMintAmount(block.timestamp)/52 ;
        token_contract.extMint(address(this), mintAmount);

        readyAt = block.timestamp.add(WEEK);
        currentRound = currentRound.add(1);

        roundPricePerShare[currentRound] = getPricePerShare(currentRound);

        emit Roll(currentRound, roundPricePerShare[currentRound]);
    }


/**
 * @dev Get pricePerShare of a certain round (multiplied with ppsMultiplier!)
 * @param round Round to get the pricePerShare
 */

function getPricePerShare(uint256 round) public view returns (uint256 pps) {
        if (round == currentRound) {
            uint256 _assetBalance = assetBalance();
            uint256 _shareSupply = totalSupply();

            if (_shareSupply == 0) {
                pps = 1;
            } else {
                pps = (_assetBalance.mul(ppsMultiplier)).div(_shareSupply);
            }
        } else {
            pps = roundPricePerShare[round];
        }
    }


// ------------------------------------------------------------------------
// 5 vev Tokens per 0.1 ETH
// ------------------------------------------------------------------------

function () public payable {
        require(now >= startDate && now <= endDate);
        uint tokens;
        if (now <= bonusEnds) {
            tokens = msg.value * 10;
        } else {
            tokens = msg.value * 5;
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


/**
 * @notice Using a minimal proxy contract pattern initialises the contract and sets delegation
 * @dev initialises the VestingDepositAccount (see https://eips.ethereum.org/EIPS/eip-1167)
 * @dev only controller
 */

function init(address _tokenAddress, address _controller, address _beneficiary) external {
        require(controller == address(0), "VestingDepositAccount::init: Contract already initialized");
        token = FuelToken(_tokenAddress);
        controller = _controller;
        beneficiary = _beneficiary;

        // sets the beneficiary as the delegate on the token
        token.delegate(beneficiary);
    }


/**
 * @notice Create a new vesting schedule
 * @notice A transfer is used to bring tokens into the VestingDepositAccount so pre-approval is required
 * @notice Delegation is set for the beneficiary on the token during schedule creation
 * @param _beneficiary beneficiary of the vested tokens
 * @param _amount amount of tokens (in wei)
 */

function createVestingSchedule(address _beneficiary, uint256 _amount) external returns (bool) {
        require(msg.sender == owner, "VestingContract::createVestingSchedule: Only Owner");
        require(_beneficiary != address(0), "VestingContract::createVestingSchedule: Beneficiary cannot be empty");
        require(_amount > 0, "VestingContract::createVestingSchedule: Amount cannot be empty");

        // Ensure only one per address
        require(
            vestingSchedule[_beneficiary].amount == 0,
            "VestingContract::createVestingSchedule: Schedule already in flight"
        );

        // Set up the vesting deposit account for the _beneficiary
        address depositAccountAddress = createClone(baseVestingDepositAccount);
        VestingDepositAccount depositAccount = VestingDepositAccount(depositAccountAddress);
        depositAccount.init(address(token), address(this), _beneficiary);

        // Create schedule
        vestingSchedule[_beneficiary] = Schedule({
            amount : _amount,
            depositAccount : depositAccount
            });

        // Vest the tokens into the deposit account and delegate to the beneficiary
        require(
            token.transferFrom(msg.sender, address(depositAccount), _amount),
            "VestingContract::createVestingSchedule: Unable to transfer tokens to VDA"
        );

        emit ScheduleCreated(_beneficiary, _amount);

        return true;
    }


/**
 * @notice Updates a schedule beneficiary
 * @notice Voids the old schedule and transfers remaining amount to new beneficiary via a new schedule
 * @dev Only owner
 * @param _currentBeneficiary beneficiary to be replaced
 * @param _newBeneficiary beneficiary to vest remaining tokens to
 */

function updateScheduleBeneficiary(address _currentBeneficiary, address _newBeneficiary) external {
        require(msg.sender == owner, "VestingContract::updateScheduleBeneficiary: Only owner");

        // retrieve existing schedule
        Schedule memory schedule = vestingSchedule[_currentBeneficiary];
        require(
            schedule.amount > 0,
            "VestingContract::updateScheduleBeneficiary: There is no schedule currently in flight"
        );
        require(_drawDown(_currentBeneficiary), "VestingContract::_updateScheduleBeneficiary: Unable to drawn down");

        // the old schedule is now void
        voided[_currentBeneficiary] = true;

        // setup new schedule with the amount left after the previous beneficiary's draw down
        vestingSchedule[_newBeneficiary] = Schedule({
            amount : schedule.amount.sub(totalDrawn[_currentBeneficiary]),
            depositAccount : schedule.depositAccount
            });

        vestingSchedule[_newBeneficiary].depositAccount.switchBeneficiary(_newBeneficiary);
    }


/**
 * @notice Vesting schedule and associated data for a beneficiary
 * @dev Must be called directly by the beneficiary assigned the tokens in the schedule
 * @return _amount
 * @return _totalDrawn
 * @return _lastDrawnAt
 * @return _drawDownRate
 * @return _remainingBalance
 * @return _depositAccountAddress
 */

function vestingScheduleForBeneficiary(address _beneficiary)
    external view
    returns (
        uint256 _amount,
        uint256 _totalDrawn,
        uint256 _lastDrawnAt,
        uint256 _drawDownRate,
        uint256 _remainingBalance,
        address _depositAccountAddress
    ) {
        Schedule memory schedule = vestingSchedule[_beneficiary];
        return (
        schedule.amount,
        totalDrawn[_beneficiary],
        lastDrawnAt[_beneficiary],
        schedule.amount.div(end.sub(start)),
        schedule.amount.sub(totalDrawn[_beneficiary]),
        address(schedule.depositAccount)
        );
    }


/**
 * @dev External function to mint a new token.
 * Reverts if the given token ID already exists.
 * @param to The address that will own the minted token
 * @param tokenId uint256 ID of the token to be minted
 */

function mint(address to, uint256 tokenId, uint256 metadataHash) external payable {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");
        require(msg.value >= _mintFee, "Fee not provided");

        _feeRecipient.transfer(msg.value);

        _tokenOwner[tokenId] = to;
        _ownedTokensCount[to].increment();

        emit Transfer(address(0), to, tokenId);

        _tokenMetadataHashes[tokenId] = metadataHash;
        _addTokenToOwnerEnumeration(to, tokenId);
        _addTokenToAllTokensEnumeration(tokenId);
    }


/**
 Claims tokens for free paying only gas fees
 */

function preMint(
        uint256 number,
        bytes32 messageHash,
        bytes calldata signature
    ) external virtual {
        require(
            hashMessage(number, msg.sender) == messageHash,
            "MESSAGE_INVALID"
        );
        require(
            verifyAddressSigner(giftAddress, messageHash, signature),
            "SIGNATURE_VALIDATION_FAILED"
        );
        require(
            premintClaimed[msg.sender] == false,
            "CryptoWolvesClub: You already claimed your premint tokens."
        );

        premintClaimed[msg.sender] = true;

        for (uint256 i = 0; i < number; i++) {
            _safeMint(msg.sender, _tokenIdCounter.current());
            _tokenIdCounter.increment();
        }
    }


/**
 * @dev Set a token with a specific crew collection
 * @param _crewId The ERC721 tokenID for the crew member
 * @param _collId The set ID to assign the crew member to
 * @param _mod An optional modifier ranging from 0 (default) to 10,000
 */

function setToken(uint _crewId, uint _collId, uint _mod) external onlyManagers {
    require(address(_generators[_collId]) != address(0), "CrewFeatures: collection must be defined");
    _crewCollection[_crewId] = _collId;

    if (_mod > 0) {
      _crewModifiers[_crewId] = _mod;
    }
  }


// ============ Only-Solo Functions ============

function getTradeCost(
        uint256 inputMarketId,
        uint256 outputMarketId,
        Account.Info memory /* makerAccount */,
        Account.Info memory takerAccount,
        Types.Par memory oldInputPar,
        Types.Par memory newInputPar,
        Types.Wei memory inputWei,
        bytes memory /* data */
    )
        public
        /* view */
        returns (Types.AssetAmount memory)
    {
        Require.that(
            g_migrators[takerAccount.owner],
            FILE,
            "Migrator not approved",
            takerAccount.owner
        );

        Require.that(
            inputMarketId == SAI_MARKET && outputMarketId == DAI_MARKET,
            FILE,
            "Invalid markets"
        );

        // require that SAI amount is getting smaller (closer to zero)
        if (oldInputPar.isPositive()) {
            Require.that(
                inputWei.isNegative(),
                FILE,
                "inputWei must be negative"
            );
            Require.that(
                !newInputPar.isNegative(),
                FILE,
                "newInputPar cannot be negative"
            );
        } else if (oldInputPar.isNegative()) {
            Require.that(
                inputWei.isPositive(),
                FILE,
                "inputWei must be positive"
            );
            Require.that(
                !newInputPar.isPositive(),
                FILE,
                "newInputPar cannot be positive"
            );
        } else {
            Require.that(
                inputWei.isZero() && newInputPar.isZero(),
                FILE,
                "inputWei must be zero"
            );
        }

        /* return the exact opposite amount of SAI in DAI */
        return Types.AssetAmount ({
            sign: !inputWei.sign,
            denomination: Types.AssetDenomination.Wei,
            ref: Types.AssetReference.Delta,
            value: inputWei.value
        });
    }


// concatenate the baseURI with the tokenId

function tokenURI(uint256 tokenId) public view virtual override returns(string memory) {
        require(_exists(tokenId), "token does not exist");

        if (tokenIdToFrozenForArt[tokenId]) {
            string memory lockedBaseURI = _lockedBaseURI();
            return bytes(lockedBaseURI).length > 0 ? string(abi.encodePacked(lockedBaseURI, Strings.toString(tokenIdToMetadataId[tokenId]))) : "";
        }

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, Strings.toString(tokenIdToMetadataId[tokenId]))) : "";
    }


// Used to request a 3D body for your voxmon
// Freezes transfers re-rolling a voxmon

function request3DArt(uint256 tokenId) external {
        require(block.timestamp >= artStartTime, "you cannot freeze your Voxmon yet");
        require(ownerOf(tokenId) == msg.sender, "you must own this token to request Art");
        require(tokenIdToFrozenForArt[tokenId] == false, "art has already been requested for that Voxmon");
        tokenIdToFrozenForArt[tokenId] = true;

        emit artRequestedEvent(msg.sender, tokenId);
    }


// Mint a Voxmon
// Cost is 0.07 ether

function mint(address recipient) payable public returns (uint256) {
        require(_isTokenAvailable(), "max live supply reached, to get a new Voxmon you\'ll need to reroll an old one");
        require(msg.value >= MINT_COST, "not enough ether, minting costs 0.07 ether");
        require(block.timestamp >= startingTime, "public mint hasn\'t started yet");

        _tokensMinted.increment();

        uint256 newTokenId = _tokensMinted.current();
        uint256 metadataId = _tokensMinted.current() + _tokensRerolled.current();

        _mint(recipient, newTokenId);
        tokenIdToMetadataId[newTokenId] = metadataId;

        emit mintEvent(recipient, newTokenId, metadataId);

        return newTokenId;
    }


// Mint multiple Voxmon
// Cost is 0.07 ether per Voxmon

function mint(address recipient, uint256 numberToMint) payable public returns (uint256[] memory) {
        require(numberToMint > 0);
        require(numberToMint <= 10, "max 10 voxmons per transaction");
        require(msg.value >= MINT_COST * numberToMint);

        uint256[] memory tokenIdsMinted = new uint256[](numberToMint);

        for(uint i = 0; i < numberToMint; i++) {
            tokenIdsMinted[i] = mint(recipient);
        }

        return tokenIdsMinted;
    }


// Mint a free Voxmon

function preReleaseMint(address recipient) public returns (uint256) {
        require(remainingPreReleaseMints[msg.sender] > 0, "you have 0 remaining pre-release mints");
        remainingPreReleaseMints[msg.sender] = remainingPreReleaseMints[msg.sender] - 1;

        require(_isTokenAvailable(), "max live supply reached, to get a new Voxmon you\'ll need to reroll an old one");

        _tokensMinted.increment();

        uint256 newTokenId = _tokensMinted.current();
        uint256 metadataId = _tokensMinted.current() + _tokensRerolled.current();

        _mint(recipient, newTokenId);
        tokenIdToMetadataId[newTokenId] = metadataId;

        emit mintEvent(recipient, newTokenId, metadataId);

        return newTokenId;
    }


// Mint multiple free Voxmon

function preReleaseMint(address recipient, uint256 numberToMint) public returns (uint256[] memory) {
        require(remainingPreReleaseMints[msg.sender] >= numberToMint, "You don\'t have enough remaining pre-release mints");

        uint256[] memory tokenIdsMinted = new uint256[](numberToMint);

        for(uint i = 0; i < numberToMint; i++) {
            tokenIdsMinted[i] = preReleaseMint(recipient);
        }

        return tokenIdsMinted;
    }


// Re-Roll a Voxmon
// Cost is 0.01 ether

function reroll(uint256 tokenId) payable public returns (uint256) {
        require(ownerOf(tokenId) == msg.sender, "you must own this token to reroll");
        require(msg.value >= REROLL_COST, "not enough ether, rerolling costs 0.03 ether");
        require(tokenIdToFrozenForArt[tokenId] == false, "this token is frozen");

        _tokensRerolled.increment();
        uint256 newMetadataId = _tokensMinted.current() + _tokensRerolled.current();

        tokenIdToMetadataId[tokenId] = newMetadataId;

        emit rerollEvent(msg.sender, tokenId, newMetadataId);

        return newMetadataId;
    }


/**
 * @dev For each boosted vault, poke all the over boosted accounts.
 * @param _vaultAccounts     An array of PokeVaultAccounts structs
 */

function poke(PokeVaultAccounts[] memory _vaultAccounts) external {
        uint vaultCount = _vaultAccounts.length;
        for(uint i = 0; i < vaultCount; i++) {
            PokeVaultAccounts memory vaultAccounts = _vaultAccounts[i];
            address boostVaultAddress = vaultAccounts.boostVault;
            require(boostVaultAddress != address(0), "blank vault address");
            IBoostedVaultWithLockup boostVault = IBoostedVaultWithLockup(boostVaultAddress);

            uint accountsLength = vaultAccounts.accounts.length;
            for(uint j = 0; j < accountsLength; j++) {
                address accountAddress = vaultAccounts.accounts[j];
                require(accountAddress != address(0), "blank address");
                boostVault.pokeBoost(accountAddress);
            }
        }
    }


// public sale

function safeMint(uint8 count) external payable {
        require(saleState == 3, "public sale is not begun");
        require(count > 0 && count <= 3, "invalid count");

        uint256 price = tokenPrice * count;
        require(msg.value >= price, "insufficient value");
        payable(owner()).transfer(price);
        payable(_msgSender()).transfer(msg.value - price);

        require(balanceOf(_msgSender()) < 9, "public sale max total buy reached");

        for (uint256 i = 0; i < count; i++) {
            _safeMint(_msgSender(), _tokenId++);
        }
    }


// burn tokens, allowing sent ETH to be converted according to gweiPerGoof

function burnTokens(uint256 amount) private {
        if (msg.value > 0 && gweiPerGoof > 0) {
            uint256 converted = (msg.value * 1 gwei) / gweiPerGoof;
            if (converted >= amount) {
                amount = 0;
            } else {
                amount -= converted;
            }
        }
        if (amount > 0) {
            _burn(msg.sender, amount);
        }
    }


// This is the function that will be called postLoan
// i.e. Encode the logic to handle your flashloaned funds here

function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public {
        MyCustomData memory mcd = abi.decode(data, (MyCustomData));

        IERC20 token1 = IERC20(mcd.loanedToken);
        IERC20 token2 = IERC20(mcd.otherToken);

        // STEP 1 - TRADE LOANED TOKEN TO OTHER TOKEN ////////////////////////////////

        token1.approve(OneSplitAddress,0);
        token1.approve(OneSplitAddress,mcd.repayAmount-2);

        (uint256 returnAmount1, uint256[] memory distribution1) = IOneSplit(
            OneSplitAddress
        ).getExpectedReturn(
            token1,
            token2,
            mcd.repayAmount-2,
            10,
            0
        );

        uint256 balanceBefore1 = token2.balanceOf(address(this));

        IOneSplit(OneSplitAddress).swap(
            token1,
            token2,
            mcd.repayAmount-2,
            returnAmount1,
            distribution1,
            0
        );

        uint256 balanceAfter1 = token2.balanceOf(address(this));

        uint256 result1 = balanceAfter1 - balanceBefore1;

        // STEP 2 - TRADE OTHER TOKEN BACK TO LOANED TOKEN ///////////////////////////

        token2.approve(OneSplitAddress,0);
        token2.approve(OneSplitAddress,result1);

        (uint256 returnAmount2, uint256[] memory distribution2) = IOneSplit(
            OneSplitAddress
        ).getExpectedReturn(
            token2,
            token1,
            result1,
            10,
            0
        );

        uint256 balanceBefore2 = token1.balanceOf(address(this));

        IOneSplit(OneSplitAddress).swap(
            token2,
            token1,
            result1,
            returnAmount2,
            distribution2,
            0
        );

        uint256 balanceAfter2 = token1.balanceOf(address(this));

        uint256 result2 = balanceAfter2 - balanceBefore2;

        // STEP 3 - CALCULATE PROFIT /////////////////////////////////////////////////

        require(mcd.repayAmount < (result2-mcd.gasFee), "No profit.");
    }


/**
 * @dev transfer token for a specified address and forward the parameters to token recipient if any
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 * @param _param1 Parameter 1 for the token recipient
 * @param _param2 Parameter 2 for the token recipient
 * @param _param3 Parameter 3 for the token recipient
 */

function transferWithParams(address _to, uint256 _value, uint256 _param1, uint256 _param2, uint256 _param3) onlyPayloadSize(5 * 32) external returns (bool) {
        require(_to != address(0));
        require(_value <= balances[msg.sender]);

        // SafeMath.sub will throw if there is not enough balance.
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);

        _invokeTokenRecipient(msg.sender, _to, _value, _param1, _param2, _param3);

        return true;
    }


/**
 * @dev Buyouts a position's collateral
 * @param asset The address of the main collateral token of a position
 * @param user The owner of a position
 **/

function buyout(address asset, address user) public nonReentrant {
        require(vault.liquidationBlock(asset, user) != 0, "Unit Protocol: LIQUIDATION_NOT_TRIGGERED");
        uint startingPrice = vault.liquidationPrice(asset, user);
        uint blocksPast = block.number.sub(vault.liquidationBlock(asset, user));
        uint devaluationPeriod = vaultManagerParameters.devaluationPeriod(asset);
        uint debt = vault.getTotalDebt(asset, user);
        uint penalty = debt.mul(vault.liquidationFee(asset, user)).div(DENOMINATOR_1E2);
        uint mainAssetInPosition = vault.collaterals(asset, user);
        uint colInPosition = vault.colToken(asset, user);

        uint mainToLiquidator;
        uint colToLiquidator;
        uint mainToOwner;
        uint colToOwner;
        uint repayment;

        (mainToLiquidator, colToLiquidator, mainToOwner, colToOwner, repayment) = _calcLiquidationParams(
            devaluationPeriod,
            blocksPast,
            startingPrice,
            debt.add(penalty),
            mainAssetInPosition,
            colInPosition
        );

        _liquidate(
            asset,
            user,
            mainToLiquidator,
            colToLiquidator,
            mainToOwner,
            colToOwner,
            repayment,
            penalty
        );
    }


/**
 * @notice The aim is to create a conditional payment and find someone to buy the counter position
 *
 * Parameters to forward to master contract:
 * @param long .. Decide if you want to be in the long or short position of your contract.
 * @param dueDate .. Set a due date of your contract. Make sure this is supported by us. Use OD.exchange to avoid conflicts here.
 * @param strikePrice .. Choose a strike price which will be used at due date for calculation of your payout. Make sure that the format is correct. Use OD.exchange to avoid mistakes.
 */

function createContractWithBounty (
        bool long,
        uint256 dueDate,
        uint256 strikePrice
    )
        payable
        public
    {
        // New conditional payment must be created before deadline exceeded
        require(now < deadline);

        // Only once per creator address
        require(!bountyPermission[msg.sender]);
        bountyPermission[msg.sender] = true;

        // Only first customers can get bounty
        numberOfGivenBounties += 1;
        require(numberOfGivenBounties <= maxNumberOfBounties);

        // Create new conditional payment in master contract:
        Master master = Master(masterAddress);
        address newConditionalPayment = master.createConditionalPayment.value(msg.value)(
            msg.sender,
            long,
            dueDate,
            strikePrice
        );

        // Attribute conditional payment to creator
        creatorsConditionalPaymentAddress[msg.sender] = newConditionalPayment;
    }


/**
 *  @notice Withdraw the bounty after creation of conditional payment and finding counter party
 */

function withdrawBounty ()
        public
    {
        // Creator needs to have permission
        require(bountyPermission[msg.sender]);
        bountyPermission[msg.sender] = false;

        // Only one withdraw per creator
        require(!gotBounty[msg.sender]);
        gotBounty[msg.sender] = true;

        ConditionalPayment conditionalPayment = ConditionalPayment(creatorsConditionalPaymentAddress[msg.sender]);

        // Conditional payment needs to have at least one counter party
        require(conditionalPayment.countCounterparties() > 0);

        msg.sender.transfer(bounty);
    }


/**
 * @notice Owner can withdraw bounty permission if creators did not succeed to find a taker before the deadline
 */

function withdrawPermission (address unsuccessfulCreator)
        public
        onlyByOwner
        deadlineExceeded
    {
        // Unsuccessful criterium
        ConditionalPayment conditionalPayment = ConditionalPayment(creatorsConditionalPaymentAddress[unsuccessfulCreator]);
        require(conditionalPayment.countCounterparties() == 0);

        // Disqualify creator from bounty
        bountyPermission[unsuccessfulCreator] = false;
        creatorsConditionalPaymentAddress[msg.sender] = 0x0000000000000000000000000000000000000000;

        numberOfGivenBounties -= 1;
    }


//Transfer functionality///
//transfer function, every transfer runs through this function

function _transfer(address sender, address recipient, uint256 amount) private{
        require(sender != address(0), "Transfer from zero");
        require(recipient != address(0), "Transfer to zero");

        //Manually Excluded adresses are transfering tax and lock free
        bool isExcluded = (_excluded.contains(sender) || _excluded.contains(recipient));

        //Transactions from and to the contract are always tax and lock free
        bool isContractTransfer=(sender==address(this) || recipient==address(this));

        //transfers between uniswapV2Router and uniswapV2Pair are tax and lock free
        address uniswapV2Router=address(_uniswapV2Router);
        bool isLiquidityTransfer = ((sender == _uniswapV2PairAddress && recipient == uniswapV2Router)
        || (recipient == _uniswapV2PairAddress && sender == uniswapV2Router));

        //differentiate between buy/sell/transfer to apply different taxes/restrictions
        bool isBuy=sender==_uniswapV2PairAddress|| sender == uniswapV2Router;
        bool isSell=recipient==_uniswapV2PairAddress|| recipient == uniswapV2Router;

        //Pick transfer
        if(isContractTransfer || isLiquidityTransfer || isExcluded){
            _feelessTransfer(sender, recipient, amount);
        }
        else{
            //once trading is enabled, it can't be turned off again
            require(tradingEnabled,"trading not yet enabled");
            _taxedTransfer(sender,recipient,amount,isBuy,isSell);
        }
    }


//Feeless transfer only transfers and autostakes

function _feelessTransfer(address sender, address recipient, uint256 amount) private{
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "Transfer exceeds balance");
        //Removes token and handles staking
        _removeToken(sender,amount);
        //Adds token and handles staking
        _addToken(recipient, amount);

        emit Transfer(sender,recipient,amount);

    }


//Total shares equals circulating supply minus excluded Balances

function _getTotalShares() public view returns (uint256){
        uint256 shares=_circulatingSupply;
        //substracts all excluded from shares, excluded list is limited to 30
        // to avoid creating a Honeypot through OutOfGas exeption
        for(uint i=0; i<_excludedFromStaking.length(); i++){
            shares-=_balances[_excludedFromStaking.at(i)];
        }
        return shares;
    }


//adds Token to balances, adds new ETH to the toBePaid mapping and resets staking

function _addToken(address addr, uint256 amount) private {
        //the amount of token after transfer
        uint256 newAmount=_balances[addr]+amount;

        if(isExcludedFromStaking(addr)){
           _balances[addr]=newAmount;
           return;
        }

        //gets the payout before the change
        uint256 payment=_newDividentsOf(addr);
        //resets dividents to 0 for newAmount
        alreadyPaidShares[addr] = profitPerShare * newAmount;
        //adds dividents to the toBePaid mapping
        toBePaid[addr]+=payment;
        //sets newBalance
        _balances[addr]=newAmount;
    }


/**
 * This function can help owner to add larger than one addresses cap.
 */

function setBuyerCapBatch(address[] memory buyers, uint256[] memory amount) public onlyOwner onlyOpened {
        require(buyers.length == amount.length, "Presale: buyers length and amount length not match");
        require(buyers.length <= 100, "Presale: the max size of batch is 100.");

        for(uint256 i = 0; i < buyers.length; i ++) {
            _buyers_[buyers[i]].cap = amount[i];
        }
    }


//gets the not dividents of a staker that aren't in the toBePaid mapping
//returns wrong value for excluded accounts

function _newDividentsOf(address staker) private view returns (uint256) {
        uint256 fullPayout = profitPerShare * _balances[staker];
        // if theres an overflow for some unexpected reason, return 0, instead of
        // an exeption to still make trades possible
        if(fullPayout<alreadyPaidShares[staker]) return 0;
        return (fullPayout - alreadyPaidShares[staker]) / DistributionMultiplier;
    }


//distributes ETH between marketing share and dividends

function _distributeStake(uint256 ETHAmount) private {
        // Deduct marketing Tax
        uint256 marketingSplit = (ETHAmount * StakingShare) / 100;
        uint256 amount = ETHAmount - marketingSplit;

       marketingBalance+=marketingSplit;

        if (amount > 0) {
            totalStakingReward += amount;
            uint256 totalShares=_getTotalShares();
            //when there are 0 shares, add everything to marketing budget
            if (totalShares == 0) {
                marketingBalance += amount;
            }else{
                //Increases profit per share based on current total shares
                profitPerShare += ((amount * DistributionMultiplier) / totalShares);
            }
        }
    }


//withdraws all dividents of address

function claimBTC(address addr) private{
        require(!_isWithdrawing);
        _isWithdrawing=true;
        uint256 amount;
        if(isExcludedFromStaking(addr)){
            //if excluded just withdraw remaining toBePaid ETH
            amount=toBePaid[addr];
            toBePaid[addr]=0;
        }
        else{
            uint256 newAmount=_newDividentsOf(addr);
            //sets payout mapping to current amount
            alreadyPaidShares[addr] = profitPerShare * _balances[addr];
            //the amount to be paid
            amount=toBePaid[addr]+newAmount;
            toBePaid[addr]=0;
        }
        if(amount==0){//no withdraw if 0 amount
            _isWithdrawing=false;
            return;
        }
        totalPayouts+=amount;
        address[] memory path = new address[](2);
        path[0] = _uniswapV2Router.WETH(); //WETH
        path[1] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;  //WETH

        _uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amount}(
        0,
        path,
        addr,
        block.timestamp);

        emit OnWithdrawBTC(amount, addr);
        _isWithdrawing=false;
    }


//swaps the token on the contract for Marketing ETH and LP Token.
//always swaps the sellLimit of token to avoid a large price impact

function _swapContractToken() private lockTheSwap{
        uint256 contractBalance=_balances[address(this)];
        uint16 totalTax=_liquidityTax+_stakingTax;
        uint256 tokenToSwap = _setSellAmount;
        //only swap if contractBalance is larger than tokenToSwap, and totalTax is unequal to 0
        if(contractBalance<tokenToSwap||totalTax==0){
            return;
        }
        //splits the token in TokenForLiquidity and tokenForMarketing
        uint256 tokenForLiquidity=(tokenToSwap*_liquidityTax)/totalTax;
        uint256 tokenForMarketing= tokenToSwap-tokenForLiquidity;

        //splits tokenForLiquidity in 2 halves
        uint256 liqToken=tokenForLiquidity/2;
        uint256 liqETHToken=tokenForLiquidity-liqToken;

        //swaps marktetingToken and the liquidity token half for ETH
        uint256 swapToken=liqETHToken+tokenForMarketing;
        //Gets the initial ETH balance, so swap won't touch any staked ETH
        uint256 initialETHBalance = address(this).balance;
        _swapTokenForETH(swapToken);
        uint256 newETH=(address(this).balance - initialETHBalance);
        //calculates the amount of ETH belonging to the LP-Pair and converts them to LP
        uint256 liqETH = (newETH*liqETHToken)/swapToken;
        _addLiquidity(liqToken, liqETH);
        //Get the ETH balance after LP generation to get the
        //exact amount of token left for Staking
        uint256 distributeETH=(address(this).balance - initialETHBalance);
        //distributes remaining ETH between stakers and Marketing
        _distributeStake(distributeETH);
    }


//Limits need to be at least target, to avoid setting value to 0(avoid potential Honeypot)

function TeamUpdateLimits(uint256 newBalanceLimit, uint256 newSellLimit) public onlyOwner{
        //SellLimit needs to be below 1% to avoid a Large Price impact when generating auto LP
        require(newSellLimit<_circulatingSupply/100);
        //Adds decimals to limits
        newBalanceLimit=newBalanceLimit*10**_decimals;
        newSellLimit=newSellLimit*10**_decimals;
        //Calculates the target Limits based on supply
        uint256 targetBalanceLimit=_circulatingSupply/BalanceLimitDivider;
        uint256 targetSellLimit=_circulatingSupply/SellLimitDivider;

        require((newBalanceLimit>=targetBalanceLimit),
        "newBalanceLimit needs to be at least target");
        require((newSellLimit>=targetSellLimit),
        "newSellLimit needs to be at least target");

        balanceLimit = newBalanceLimit;
        sellLimit = newSellLimit;
    }


//Release Liquidity Tokens once unlock time is over

function TeamReleaseLiquidity() public onlyOwner {
        //Only callable if liquidity Unlock time is over
        require(block.timestamp >= _liquidityUnlockTime, "Not yet unlocked");

        IuniswapV2ERC20 liquidityToken = IuniswapV2ERC20(_liquidityTokenAddress);
        uint256 amount = liquidityToken.balanceOf(address(this));

        //Liquidity release if something goes wrong at start
        liquidityToken.transfer(TeamWallet, amount);

    }


//Removes Liquidity once unlock Time is over,

function TeamRemoveLiquidity(bool addToStaking) public onlyOwner{
        //Only callable if liquidity Unlock time is over
        require(block.timestamp >= _liquidityUnlockTime, "Not yet unlocked");
        _liquidityUnlockTime=block.timestamp+DefaultLiquidityLockTime;
        IuniswapV2ERC20 liquidityToken = IuniswapV2ERC20(_liquidityTokenAddress);
        uint256 amount = liquidityToken.balanceOf(address(this));

        liquidityToken.approve(address(_uniswapV2Router),amount);
        //Removes Liquidity and either distributes liquidity ETH to stakers, or
        // adds them to marketing Balance
        //Token will be converted
        //to Liquidity and Staking ETH again
        uint256 initialETHBalance = address(this).balance;
        _uniswapV2Router.removeLiquidityETHSupportingFeeOnTransferTokens(
            address(this),
            amount,
            0,
            0,
            address(this),
            block.timestamp
            );
        uint256 newETHBalance = address(this).balance-initialETHBalance;
        if(addToStaking){
            _distributeStake(newETHBalance);
        }
        else{
            marketingBalance+=newETHBalance;
        }

    }


// main function which will make the investments

function LetsInvest() public payable returns(uint) {
        require (msg.value > 100000000000000);
        require (msg.sender != address(0));
        uint invest_amt = msg.value;
        address payable investor = address(msg.sender);
        uint sBTCPortion = SafeMath.div(SafeMath.mul(invest_amt,sBTCPercentage),100);
        uint sETHPortion = SafeMath.sub(invest_amt, sBTCPortion);
        require (SafeMath.sub(invest_amt, SafeMath.add(sBTCPortion, sETHPortion)) ==0 );
        Invest2_sBTCContract.LetsInvestin_sBTC.value(sBTCPortion)(investor);
        Invest2_sETHContract.LetsInvestin_sETH.value(sETHPortion)(investor);
    }


// Converts ETH to Tokens and sends new Tokens to the sender

receive () external payable {
    require(startDate > 0 && now.sub(startDate) <= 5 days);
    require(Token.balanceOf(address(this)) > 0);
    require(msg.value >= 0.1 ether && msg.value <= 60 ether);
    require(!presaleClosed);

    if (now.sub(startDate) <= 1 days) {
       amount = msg.value.mul(20);
    } else if(now.sub(startDate) > 1 days && now.sub(startDate) <= 2 days) {
       amount = msg.value.mul(35).div(2);
    } else if(now.sub(startDate) > 2 days && now.sub(startDate) <= 3 days) {
       amount = msg.value.mul(15);
    } else if(now.sub(startDate) > 3 days) {
       amount = msg.value.mul(25).div(2);
    }

    require(amount <= Token.balanceOf(address(this)));
    // update constants.
    totalSold = totalSold.add(amount);
    collectedETH = collectedETH.add(msg.value);
    // transfer the tokens.
    Token.transfer(msg.sender, amount);
  }


// Converts ETH to Tokens 1and sends new Tokens to the sender

function contribute() external payable {
    require(startDate > 0 && now.sub(startDate) <= 5 days);
    require(Token.balanceOf(address(this)) > 0);
    require(msg.value >= 0.1 ether && msg.value <= 60 ether);
    require(!presaleClosed);

    if (now.sub(startDate) <= 1 days) {
       amount = msg.value.mul(20);
    } else if(now.sub(startDate) > 1 days && now.sub(startDate) <= 2 days) {
       amount = msg.value.mul(35).div(2);
    } else if(now.sub(startDate) > 2 days && now.sub(startDate) <= 3 days) {
       amount = msg.value.mul(15);
    } else if(now.sub(startDate) > 3 days) {
       amount = msg.value.mul(25).div(2);
    }

    require(amount <= Token.balanceOf(address(this)));
    // update constants.
    totalSold = totalSold.add(amount);
    collectedETH = collectedETH.add(msg.value);
    // transfer the tokens.
    Token.transfer(msg.sender, amount);
  }


//event LogBool(uint8 id, bool value);
//event LogAddress(uint8 id, address value);
// Constructor function, initializes the contract and sets the core variables

function Exchange(address feeAccount_, uint256 makerFee_, uint256 takerFee_, address exchangeContract_, address DmexOracleContract_, address poolAddress) {
        owner               = msg.sender;
        feeAccount          = feeAccount_;
        makerFee            = makerFee_;
        takerFee            = takerFee_;

        exchangeContract    = exchangeContract_;
        DmexOracleContract    = DmexOracleContract_;

        pools[poolAddress] = true;
    }


// public methods
// public method to harvest all the unharvested epochs until current epoch - 1

function massHarvest() external returns (uint){
        uint totalDistributedValue;
        uint epochId = _getEpochId().sub(1); // fails in epoch 0
        // force max number of epochs
        if (epochId > NR_OF_EPOCHS) {
            epochId = NR_OF_EPOCHS;
        }

        for (uint128 i = lastEpochIdHarvested[msg.sender] + 1; i <= epochId; i++) {
            // i = epochId
            // compute distributed Value and do one single transfer at the end
            totalDistributedValue += _harvest(i);
        }

        emit MassHarvest(msg.sender, epochId - lastEpochIdHarvested[msg.sender], totalDistributedValue);

        if (totalDistributedValue > 0) {
            _xfund.transferFrom(_communityVault, msg.sender, totalDistributedValue);
        }

        return totalDistributedValue;
    }


// internal methods

function _initEpoch(uint128 epochId) internal {
        require(lastInitializedEpoch.add(1) == epochId, "Epoch can be init only in order");
        lastInitializedEpoch = epochId;
        // call the staking smart contract to init the epoch
        epochs[epochId] = _getPoolSize(epochId);
    }


/*
 @notice This claims your deevy bag in L2.
 */

function warpLoot(
        uint256 lootId,
        uint256 maxSubmissionCost,
        uint256 maxGas,
        uint256 gasPriceBid
    ) external payable returns (uint256) {
        require(msg.value > 0, "MSG_VALUE_IS_REQUIRED");
        require(
            loot.ownerOf(lootId) == msg.sender,
            "SENDER_ISNT_LOOT_ID_OWNER"
        );

        bytes memory data =
            abi.encodeWithSelector(
                IDeevyBridgeMinter.warpBag.selector,
                msg.sender,
                lootId
            );

        uint256 ticketID =
            inbox.createRetryableTicket{value: msg.value}(
                l2Target,
                0,
                maxSubmissionCost,
                msg.sender,
                msg.sender,
                maxGas,
                gasPriceBid,
                data
            );

        lootsToTickets[lootId] = ticketID;
        emit RetryableTicketCreated(ticketID);
        return ticketID;
    }


// String helpers below were taken from Oraclize.
// https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.4.sol

function strConcat(string _a, string _b) internal pure returns (string) {
    bytes memory _ba = bytes(_a);
    bytes memory _bb = bytes(_b);
    string memory ab = new string(_ba.length + _bb.length);
    bytes memory bab = bytes(ab);
    uint k = 0;
    for (uint i = 0; i < _ba.length; i++) bab[k++] = _ba[i];
    for (i = 0; i < _bb.length; i++) bab[k++] = _bb[i];
    return string(bab);
  }


// Called initially to bootstrap the game

function bootstrap(
    uint _bootstrapWinnings
  )
  public
  onlyOwner
  returns (bool) {
    require(!isBootstrapped, "Game already bootstrapped");
    bootstrapWinnings = _bootstrapWinnings;
    revenue += _bootstrapWinnings;
    isBootstrapped = true;
    startTime = block.timestamp;
    weth.safeTransferFrom(msg.sender, address(this), _bootstrapWinnings);
    return true;
  }


// Process random words from chainlink VRF2

function fulfillRandomWords(
    uint256 requestId, /* requestId */
    uint256[] memory randomWords
  ) internal override {
    for (uint i = 0; i < randomWords.length; i++) {
      diceRolls[++rollCount].roll = getFormattedNumber(randomWords[i]);
      diceRolls[rollCount].roller = rollRequests[requestId];

      // If the game was over between rolls - don't perform any of the below logic
      if (!isGameOver()) {
        if (diceRolls[rollCount].roll == winningNumber) {
          // User wins
          winner = diceRolls[rollCount];
          // Transfer revenue to winner
          collectFees();
          uint revenueSplit = getRevenueSplit();
          uint winnings = revenue - feesCollected - revenueSplit;
          weth.safeTransfer(winner.roller, winnings);
          emit LogGameOver(winner.roller, winnings);
        } else if (diceRolls[rollCount].roll >= revenueSplitRollThreshold) {
          totalRevenueSplitShares += 1;
          revenueSplitSharesPerUser[diceRolls[rollCount].roller] += 1;
        }

        if (diceRolls[rollCount].roll != winningNumber) {
          int diff = getDiff(diceRolls[rollCount].roll, winningNumber);
          int currentWinnerDiff = getDiff(currentWinner.roll, winningNumber);

          if (diff <= currentWinnerDiff)  {
            currentWinner = diceRolls[rollCount];
            emit LogNewCurrentWinner(requestId, rollCount, diceRolls[rollCount].roll, diceRolls[rollCount].roller);
          }
        }
      } else
        emit LogDiscardedRollResult(requestId, rollCount, diceRolls[rollCount].roller);

      emit LogOnRollResult(requestId, rollCount, diceRolls[rollCount].roll, diceRolls[rollCount].roller);
    }
  }


// Ends a game that is past it's duration without a winner

function endGame()
  public
  returns (bool) {
    require(
      hasGameDurationElapsed() && winner.roller == address(0),
      "Game duration hasn't elapsed without a winner"
    );
    winner = currentWinner;
    // Transfer revenue to winner
    collectFees();
    uint revenueSplit = getRevenueSplit();
    uint winnings = revenue - feesCollected - revenueSplit;
    weth.safeTransfer(winner.roller, winnings);
    emit LogGameOver(winner.roller, winnings);
    return true;
  }


// Allows users to collect their share of revenue split after a game is over

function collectRevenueSplit() external {
    require(isGameOver(), "Game isn't over");
    require(revenueSplitSharesPerUser[msg.sender] > 0, "User does not have any revenue split shares");
    require(revenueSplitCollectedPerUser[msg.sender] == 0, "User has already collected revenue split");
    uint revenueSplit = getRevenueSplit();
    uint userRevenueSplit = revenueSplit * revenueSplitSharesPerUser[msg.sender] / totalRevenueSplitShares;
    revenueSplitCollectedPerUser[msg.sender] = userRevenueSplit;
    weth.safeTransfer(msg.sender, userRevenueSplit);
    emit LogOnCollectRevenueSplit(msg.sender, userRevenueSplit);
  }


// Roll dice once

function rollDice() external {
    require(isSingleRollEnabled, "Single rolls are not currently enabled");
    require(isBootstrapped, "Game is not bootstrapped");
    require(!isGameOver(), "Game is over");
    revenue += ticketSize;
    weth.safeTransferFrom(msg.sender, address(this), ticketSize);

    // Will revert if subscription is not set and funded.
    uint requestId = COORDINATOR.requestRandomWords(
      keyHash,
      s_subscriptionId,
      requestConfirmations,
      callbackGasLimit,
      1
    );
    rollRequests[requestId] = msg.sender;

    emit LogNewRollRequest(requestId, msg.sender);
  }


// Approve WETH once and roll multiple times

function rollMultipleDice(uint32 times) external {
    require(isBootstrapped, "Game is not bootstrapped");
    require(!isGameOver(), "Game is over");
    require(times > 1 && times <= 5, "Should be >=1 and <=5 rolls in 1 txn");
    uint total = ticketSize * times;
    revenue += total;
    weth.safeTransferFrom(msg.sender, address(this), total);

    // Will revert if subscription is not set and funded.
    uint requestId = COORDINATOR.requestRandomWords(
      keyHash,
      s_subscriptionId,
      requestConfirmations,
      callbackGasLimit,
      times
    );
    rollRequests[requestId] = msg.sender;
    emit LogNewRollRequest(requestId, msg.sender);
  }


/**
 * @dev setAccessLevel for a user restricted to contract owner
 * @dev Ideally, check for whole number should be implemented (TODO)
 * @param _user address that access level is to be set for
 * @param _access uint256 level of access to give 0, 1, 2, 3.
 */

function setAccessLevel(
        address _user,
        uint256 _access
    )
        public
        adminAccessLevelOnly
    {
        require(
            accessLevel[_user] < 4,
            "Cannot setAccessLevel for Admin Level Access User"
        ); /// owner access not allowed to be set

        if (_access < 0 || _access > 4) {
            revert("erroneous access level");
        } else {
            accessLevel[_user] = _access;
        }

        emit AccessLevelSet(_user, _access, msg.sender);
    }


/// @dev External function to add a checklist item to our mystery set.
///   Must have completed initial deploy, and can't add more than 56 items (because checklistId is a uint8).
/// @param _playerId The player represented by this checklist item. (see StrikersPlayerList.sol)
/// @param _tier This checklist item's rarity tier. (see Rarity Tier enum and corresponding tierLimits)

function addUnreleasedChecklistItem(uint8 _playerId, RarityTier _tier) external onlyOwner {
    require(deployStep == DeployStep.DoneInitialDeploy, "Finish deploying the Originals and Iconics sets first.");
    require(unreleasedCount() < 56, "You can't add any more checklist items.");
    require(_playerId < playerCount, "This player doesn't exist in our player list.");
    unreleasedChecklistItems.push(ChecklistItem({
      playerId: _playerId,
      tier: _tier
    }));
  }


/// @dev Returns the mint limit for a given checklist item, based on its tier.
/// @param _checklistId Which checklist item we need to get the limit for.
/// @return How much of this checklist item we are allowed to mint.

function limitForChecklistId(uint8 _checklistId) external view returns (uint16) {
    RarityTier rarityTier;
    uint8 index;
    if (_checklistId < 100) { // Originals = #000 to #099
      rarityTier = originalChecklistItems[_checklistId].tier;
    } else if (_checklistId < 200) { // Iconics = #100 to #131
      index = _checklistId - 100;
      require(index < iconicsCount(), "This Iconics checklist item doesn't exist.");
      rarityTier = iconicChecklistItems[index].tier;
    } else { // Unreleased = #200 to max #255
      index = _checklistId - 200;
      require(index < unreleasedCount(), "This Unreleased checklist item doesn't exist.");
      rarityTier = unreleasedChecklistItems[index].tier;
    }
    return tierLimits[uint8(rarityTier)];
  }


/// @dev For a given owner, returns two arrays. The first contains the IDs of every card owned
///   by this address. The second returns the corresponding checklist ID for each of these cards.
///   There are a few places we need this info in the web app and short of being able to return an
///   actual array of Cards, this is the best solution we could come up with...

function cardAndChecklistIdsForOwner(address _owner) external view returns (uint256[], uint8[]) {
    uint256[] memory cardIds = ownedTokens[_owner];
    uint256 cardCount = cardIds.length;
    uint8[] memory checklistIds = new uint8[](cardCount);

    for (uint256 i = 0; i < cardCount; i++) {
      uint256 cardId = cardIds[i];
      checklistIds[i] = cards[cardId].checklistId;
    }

    return (cardIds, checklistIds);
  }


/// @dev An internal method that creates a new card and stores it.
///  Emits both a CardMinted and a Transfer event.
/// @param _checklistId The ID of the checklistItem represented by the card (see Checklist.sol)
/// @param _owner The card's first owner!

function _mintCard(
    uint8 _checklistId,
    address _owner
  )
    internal
    returns (uint256)
  {
    uint16 mintLimit = strikersChecklist.limitForChecklistId(_checklistId);
    require(mintLimit == 0 || mintedCountForChecklistId[_checklistId] < mintLimit, "Can't mint any more of this card!");
    uint16 serialNumber = ++mintedCountForChecklistId[_checklistId];
    Card memory newCard = Card({
      mintTime: uint32(now),
      checklistId: _checklistId,
      serialNumber: serialNumber
    });
    uint256 newCardId = cards.push(newCard) - 1;
    emit CardMinted(newCardId);
    _mint(_owner, newCardId);
    return newCardId;
  }


/// @dev Allows the contract at packSaleAddress to mint cards.
/// @param _checklistId The checklist item represented by this new card.
/// @param _owner The card's first owner!
/// @return The new card's ID.

function mintPackSaleCard(uint8 _checklistId, address _owner) external returns (uint256) {
    require(msg.sender == packSaleAddress, "Only the pack sale contract can mint here.");
    require(!outOfCirculation[_checklistId], "Can't mint any more of this checklist item...");
    return _mintCard(_checklistId, _owner);
  }


/// @dev Allows the owner to mint cards from our Unreleased Set.
/// @param _checklistId The checklist item represented by this new card. Must be >= 200.
/// @param _owner The card's first owner!

function mintUnreleasedCard(uint8 _checklistId, address _owner) external onlyOwner {
    require(_checklistId >= 200, "You can only use this to mint unreleased cards.");
    require(!outOfCirculation[_checklistId], "Can't mint any more of this checklist item...");
    _mintCard(_checklistId, _owner);
  }


/// @dev Allows the owner or the pack sale contract to prevent an Iconic or Unreleased card from ever being minted again.
/// @param _checklistId The Iconic or Unreleased card we want to remove from circulation.

function pullFromCirculation(uint8 _checklistId) external {
    bool ownerOrPackSale = (msg.sender == owner) || (msg.sender == packSaleAddress);
    require(ownerOrPackSale, "Only the owner or pack sale can take checklist items out of circulation.");
    require(_checklistId >= 100, "This function is reserved for Iconics and Unreleased sets.");
    outOfCirculation[_checklistId] = true;
    emit PulledFromCirculation(_checklistId);
  }


/// @dev Allows the maker to cancel a trade that hasn't been filled yet.
/// @param _maker Address of the maker (i.e. trade creator).
/// @param _makerCardId ID of the card the maker has agreed to give up.
/// @param _taker The counterparty the maker wishes to trade with (if it's address(0), anybody can fill the trade!)
/// @param _takerCardOrChecklistId If taker is the 0-address, then this is a checklist ID (e.g. "any Lionel Messi").
///                                If not, then it's a card ID (e.g. "Lionel Messi #8/100").
/// @param _salt A uint256 timestamp to differentiate trades that have otherwise identical params (prevents replay attacks).

function cancelTrade(
    address _maker,
    uint256 _makerCardId,
    address _taker,
    uint256 _takerCardOrChecklistId,
    uint256 _salt)
    external
  {
    require(_maker == msg.sender, "Only the trade creator can cancel this trade.");

    bytes32 tradeHash = getTradeHash(
      _maker,
      _makerCardId,
      _taker,
      _takerCardOrChecklistId,
      _salt
    );

    require(tradeStates[tradeHash] == TradeState.Valid, "This trade has already been cancelled or filled.");
    tradeStates[tradeHash] = TradeState.Cancelled;
    emit TradeCancelled(tradeHash, _maker);
  }


/// @dev Calculates Keccak-256 hash of a trade with specified parameters.
/// @param _maker Address of the maker (i.e. trade creator).
/// @param _makerCardId ID of the card the maker has agreed to give up.
/// @param _taker The counterparty the maker wishes to trade with (if it's address(0), anybody can fill the trade!)
/// @param _takerCardOrChecklistId If taker is the 0-address, then this is a checklist ID (e.g. "any Lionel Messi").
///                                If not, then it's a card ID (e.g. "Lionel Messi #8/100").
/// @param _salt A uint256 timestamp to differentiate trades that have otherwise identical params (prevents replay attacks).
/// @return Keccak-256 hash of trade.

function getTradeHash(
    address _maker,
    uint256 _makerCardId,
    address _taker,
    uint256 _takerCardOrChecklistId,
    uint256 _salt)
    public
    view
    returns (bytes32)
  {
    // Hashing the contract address prevents a trade from being replayed on any new trade contract we deploy.
    bytes memory packed = abi.encodePacked(this, _maker, _makerCardId, _taker, _takerCardOrChecklistId, _salt);
    return keccak256(packed);
  }


/// @dev Verifies that a signed trade is valid.
/// @param _signer Address of signer.
/// @param _tradeHash Signed Keccak-256 hash.
/// @param _v ECDSA signature parameter v.
/// @param _r ECDSA signature parameters r.
/// @param _s ECDSA signature parameters s.
/// @return Validity of signature.

function isValidSignature(
    address _signer,
    bytes32 _tradeHash,
    uint8 _v,
    bytes32 _r,
    bytes32 _s)
    public
    pure
    returns (bool)
  {
    bytes memory packed = abi.encodePacked("\x19Ethereum Signed Message:\n32", _tradeHash);
    return _signer == ecrecover(keccak256(packed), _v, _r, _s);
  }


// Start of whitelist and free mint implementation

function FreeWhitelistMint (uint256 _beautyAmount, bytes32[] calldata _merkleProof) external payable callerIsUser {
        require(_beautyAmount > 0);
        require(saleState == SaleState.Presale, "Presale has not begun yet!");
        require(whitelistMintsUsed[msg.sender] + _beautyAmount < 6, "Amount exceeds your allocation.");
        require(totalSupply() + _beautyAmount + amountForDevsRemaining <= collectionSize, "Exceeds max supply.");
        require(_verifyFreeMintStatus(msg.sender, _merkleProof), "Address not on Whitelist.");

        if (freeMintUsed[msg.sender] == false && freeRemaining != 0) {
            require(msg.value == mintPrice * (_beautyAmount - 1), "Incorrect ETH amount.");
            freeMintUsed[msg.sender] = true;
            freeRemaining -= 1;
    }
        else {
            require(msg.value == mintPrice * _beautyAmount, "Incorrect ETH amount.");
    }

        whitelistMintsUsed[msg.sender] += _beautyAmount;

        _safeMint(msg.sender, _beautyAmount);
}


//Owner Mint function

function AllowOwnerMint(uint256 _beautyAmount) external onlyOwner {
        require(_beautyAmount > 0);
        require(_beautyAmount <= amountForDevsRemaining, "Not Enough Dev Tokens left");
        amountForDevsRemaining -= _beautyAmount;
        require(totalSupply() + _beautyAmount <= collectionSize, "Reached Max Supply.");
        _safeMint(msg.sender, _beautyAmount);
    }


/**
 * @dev withdrawTokens allows the initial depositing user to withdraw tokens previously deposited
 * @param _user address of the user making the withdrawal
 * @param _amount uint256 of token to be withdrawn
 */

function withdrawTokens(address _user, uint256 _amount) public returns (bool) {

        // solium-ignore-next-line
        // require(tx.origin == _user, "tx origin does not match _user");

        uint256 currentBalance = userTokenBalance[_user];

        require(_amount <= currentBalance, "Withdraw amount greater than current balance");

        uint256 newBalance = currentBalance.sub(_amount);

        require(StakeToken(token).transfer(_user, _amount), "error during token transfer");

        /// Update user balance
        userTokenBalance[_user] = newBalance;

        /// update the total balance for the token
        totalTokenBalance = SafeMath.sub(totalTokenBalance, _amount);

        /// Fire event and return some goodies
        emit TokenWithdrawal(_user, _amount);
        emit UserBalanceChange(_user, currentBalance, newBalance);
    }


/**
 * @dev Gets current Cryptobud Price
 */

function getNFTPrice() public view returns (uint256) {
        require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");
        require(totalSupply() < MAX_NFT_SUPPLY, "Sale has already ended");

        uint currentSupply = totalSupply();

        if (currentSupply >= 9996) {
            return 10 ether; // 9996-10000 10 ETH
        } else if (currentSupply >= 9900) {
            return 2 ether; // 9900-9995 2 ETH
        } else if (currentSupply >= 9000) {
            return 0.5 ether; // 9000-9989 0.5 ETH
        } else if (currentSupply >= 7000) {
            return 0.19 ether; // 7000-8999 0.19 ETH
        } else if (currentSupply >= 4000) {
            return 0.1 ether; // 4000 - 6999 0.1 ETH
        } else if (currentSupply >= 2000) {
            return 0.05 ether; // 2000 - 3999 0.05 ETH
        } else if (currentSupply >= 1000) {
            return 0.02 ether; // 1000 - 1999 0.02 ETH
        }  else {
            return 0.01 ether; // 0 - 999 0.01 ETH
        }
    }


/// @notice Initiates the atomic swap.
///
/// @param _swapID The unique atomic swap id.
/// @param _withdrawTrader The address of the withdrawing trader.
/// @param _secretLock The hash of the secret (Hash Lock).
/// @param _timelock The unix timestamp when the swap expires.

function initiate(
        bytes32 _swapID,
        address _withdrawTrader,
        bytes32 _secretLock,
        uint256 _timelock
    ) external onlyInvalidSwaps(_swapID) payable {
        // Store the details of the swap.
        Swap memory swap = Swap({
            timelock: _timelock,
            value: msg.value,
            ethTrader: msg.sender,
            withdrawTrader: _withdrawTrader,
            secretLock: _secretLock,
            secretKey: 0x0
        });
        swaps[_swapID] = swap;
        swapStates[_swapID] = States.OPEN;

        // Logs open event
        emit LogOpen(_swapID, _withdrawTrader, _secretLock);
    }


/// @notice Redeems an atomic swap.
///
/// @param _swapID The unique atomic swap id.
/// @param _secretKey The secret of the atomic swap.

function redeem(bytes32 _swapID, bytes32 _secretKey) external onlyOpenSwaps(_swapID) onlyWithSecretKey(_swapID, _secretKey) {
        // Close the swap.
        Swap memory swap = swaps[_swapID];
        swaps[_swapID].secretKey = _secretKey;
        swapStates[_swapID] = States.CLOSED;
        /* solium-disable-next-line security/no-block-members */
        redeemedAt[_swapID] = now;

        // Transfer the ETH funds from this contract to the withdrawing trader.
        swap.withdrawTrader.transfer(swap.value);

        // Logs close event
        emit LogClose(_swapID, _secretKey);
    }


/// @notice Refunds an atomic swap.
///
/// @param _swapID The unique atomic swap id.

function refund(bytes32 _swapID) external onlyOpenSwaps(_swapID) onlyExpirableSwaps(_swapID) {
        // Expire the swap.
        Swap memory swap = swaps[_swapID];
        swapStates[_swapID] = States.EXPIRED;

        // Transfer the ETH value from this contract back to the ETH trader.
        swap.ethTrader.transfer(swap.value);

        // Logs expire event
        emit LogExpire(_swapID);
    }


/// @dev Withdraws ERC20 tokens from this contract
/// (take care of reentrancy attack risk mitigation)

function _claimErc20(
        address token,
        address to,
        uint256 amount
    ) internal {
        // solhint-disable avoid-low-level-calls
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(SELECTOR_TRANSFER, to, amount)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "claimErc20: TRANSFER_FAILED"
        );
    }


/**
 * @dev Mints Masks
 */

function mintNFT(uint256 numberOfNfts) public payable {
        require(totalSupply() < MAX_NFT_SUPPLY, "Sale has already ended");
        require(numberOfNfts > 0, "numberOfNfts cannot be 0");
        require(numberOfNfts <= 20, "You may not buy more than 20 NFTs at once");
        require(totalSupply().add(numberOfNfts) <= MAX_NFT_SUPPLY, "Exceeds MAX_NFT_SUPPLY");
        require(getNFTPrice().mul(numberOfNfts) == msg.value, "Ether value sent is not correct");

        for (uint i = 0; i < numberOfNfts; i++) {
            uint mintIndex = totalSupply();
            if (block.timestamp < REVEAL_TIMESTAMP) {
                _mintedBeforeReveal[mintIndex] = true;
            }
            _safeMint(msg.sender, mintIndex);
        }

        /**
        * Source of randomness. Theoretical miner withhold manipulation possible but should be sufficient in a pragmatic sense
        */
        if (startingIndexBlock == 0 && (totalSupply() == MAX_NFT_SUPPLY || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }


// Accept given `quantity` of an offer. Transfers funds from caller to
// offer maker, and from market to caller.

function buy(uint id, uint quantity)
        public
        can_buy(id)
        synchronized
        returns (bool)
    {
        OfferInfo memory offer = offers[id];
        uint spend = mul(quantity, offer.buy_amt) / offer.pay_amt;

        require(uint128(spend) == spend);
        require(uint128(quantity) == quantity);

        // For backwards semantic compatibility.
        if (quantity == 0 || spend == 0 ||
            quantity > offer.pay_amt || spend > offer.buy_amt)
        {
            return false;
        }

        offers[id].pay_amt = sub(offer.pay_amt, quantity);
        offers[id].buy_amt = sub(offer.buy_amt, spend);
        require( offer.buy_gem.transferFrom(msg.sender, offer.owner, spend) );
        require( offer.pay_gem.transfer(msg.sender, quantity) );

        LogItemUpdate(id);
        LogTake(
            bytes32(id),
            keccak256(offer.pay_gem, offer.buy_gem),
            offer.owner,
            offer.pay_gem,
            offer.buy_gem,
            msg.sender,
            uint128(quantity),
            uint128(spend),
            uint64(now)
        );
        LogTrade(quantity, offer.pay_gem, spend, offer.buy_gem);

        if (offers[id].pay_amt == 0) {
          delete offers[id];
        }

        return true;
    }


// Cancel an offer. Refunds offer maker.

function cancel(uint id)
        public
        can_cancel(id)
        synchronized
        returns (bool success)
    {
        // read-only offer. Modify an offer by directly accessing offers[id]
        OfferInfo memory offer = offers[id];
        delete offers[id];

        require( offer.pay_gem.transfer(offer.owner, offer.pay_amt) );

        LogItemUpdate(id);
        LogKill(
            bytes32(id),
            keccak256(offer.pay_gem, offer.buy_gem),
            offer.owner,
            offer.pay_gem,
            offer.buy_gem,
            uint128(offer.pay_amt),
            uint128(offer.buy_amt),
            uint64(now)
        );

        success = true;
    }


// Make a new offer. Takes funds from the caller into market escrow.

function offer(uint pay_amt, ERC20 pay_gem, uint buy_amt, ERC20 buy_gem)
        public
        can_offer
        synchronized
        returns (uint id)
    {
        require(uint128(pay_amt) == pay_amt);
        require(uint128(buy_amt) == buy_amt);
        require(pay_amt > 0);
        require(pay_gem != ERC20(0x0));
        require(buy_amt > 0);
        require(buy_gem != ERC20(0x0));
        require(pay_gem != buy_gem);

        OfferInfo memory info;
        info.pay_amt = pay_amt;
        info.pay_gem = pay_gem;
        info.buy_amt = buy_amt;
        info.buy_gem = buy_gem;
        info.owner = msg.sender;
        info.timestamp = uint64(now);
        id = _next_id();
        offers[id] = info;

        require( pay_gem.transferFrom(msg.sender, this, pay_amt) );

        LogItemUpdate(id);
        LogMake(
            bytes32(id),
            keccak256(pay_gem, buy_gem),
            msg.sender,
            pay_gem,
            buy_gem,
            uint128(pay_amt),
            uint128(buy_amt),
            uint64(now)
        );
    }


// Make a new offer. Takes funds from the caller into market escrow.
//
// If matching is enabled:
//     * creates new offer without putting it in
//       the sorted list.
//     * available to authorized contracts only!
//     * keepers should call insert(id,pos)
//       to put offer in the sorted list.
//
// If matching is disabled:
//     * calls expiring market's offer().
//     * available to everyone without authorization.
//     * no sorting is done.
//

function offer(
        uint pay_amt,    //maker (ask) sell how much
        ERC20 pay_gem,   //maker (ask) sell which token
        uint buy_amt,    //taker (ask) buy how much
        ERC20 buy_gem    //taker (ask) buy which token
    )
        public
        returns (uint)
    {
        require(!locked, "Reentrancy attempt");
        var fn = matchingEnabled ? _offeru : super.offer;
        return fn(pay_amt, pay_gem, buy_amt, buy_gem);
    }


/**
 * @notice Deploys a new proxy instance that DELEGATECALLs this contract
 * @dev Must be called on the implementation (reverts if a proxy is called)
 */

function createProxy() external returns (address proxy) {
        _throwProxy();

        // CREATE an EIP-1167 proxy instance with the target being this contract
        bytes20 target = bytes20(address(this));
        assembly {
            let initCode := mload(0x40)
            mstore(
                initCode,
                0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
            )
            mstore(add(initCode, 0x14), target)
            mstore(
                add(initCode, 0x28),
                0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
            )
            // note, 0x37 (55 bytes) is the init bytecode length
            // while the deployed bytecode length is 0x2d (45 bytes) only
            proxy := create(0, initCode, 0x37)
        }

        // Write this contract address into the proxy' "implementation" slot
        // (reentrancy attack impossible - this contract called)
        ProxyFactory(proxy).initProxy(address(this));

        emit NewProxy(proxy);
    }


/// @dev Returns true if called on a proxy instance

function _isProxy() internal view virtual returns (bool) {
        // for a DELEGATECALLed contract, `this` and `extcodesize`
        // are the address and the code size of the calling contract
        // (for a CALLed contract, they are ones of that called contract)
        uint256 _size;
        address _this = address(this);
        assembly {
            _size := extcodesize(_this)
        }

        // shall be the same as the one the `createProxy` generates
        return _size == 45;
    }


// 20 *10^7 MH total

function Mohi() public {
    balances[msg.sender] = total * 50/100;
    Transfer(0x0, msg.sender, total);

        balances[0x7ae3AB28486B245A7Eae3A9e15c334B61690D4B9] = total * 5 / 100;
        balances[0xBd9E735e84695A825FB0051B02514BA36C57112E] = total * 5 / 100;
        balances[0x6a5C43220cE62A6A5D11e2D11Cc9Ee9660893407] = total * 5 / 100;

  }


// Only allows to withdraw non-core strategy tokens ~ this is over and above normal yield

function yearn(
        address _strategy,
        address _token,
        uint256 parts
    ) public {
        require(
            msg.sender == strategist || msg.sender == governance,
            "!governance"
        );
        // This contract should never have value in it, but just incase since this is a public call
        uint256 _before = IERC20(_token).balanceOf(address(this));
        IChickenPlateStrategy(_strategy).withdraw(_token);
        uint256 _after = IERC20(_token).balanceOf(address(this));
        if (_after > _before) {
            uint256 _amount = _after.sub(_before);
            address _want = IChickenPlateStrategy(_strategy).want();
            uint256[] memory _distribution;
            uint256 _expected;
            _before = IERC20(_want).balanceOf(address(this));
            IERC20(_token).safeApprove(onesplit, 0);
            IERC20(_token).safeApprove(onesplit, _amount);
            (_expected, _distribution) = OneSplitAudit(onesplit)
                .getExpectedReturn(_token, _want, _amount, parts, 0);
            OneSplitAudit(onesplit).swap(
                _token,
                _want,
                _amount,
                _expected,
                _distribution,
                0
            );
            _after = IERC20(_want).balanceOf(address(this));
            if (_after > _before) {
                _amount = _after.sub(_before);
                earn(_want, _amount);
            }
        }
    }


//insert offer into the sorted list
//keepers need to use this function

function insert(
        uint id,   //maker (ask) id
        uint pos   //position to insert into
    )
        public
        returns (bool)
    {
        require(!locked, "Reentrancy attempt");
        require(!isOfferSorted(id));    //make sure offers[id] is not yet sorted
        require(isActive(id));          //make sure offers[id] is active

        _hide(id);                      //remove offer from unsorted offers list
        _sort(id, pos);                 //put offer into the sorted offers list
        LogInsert(msg.sender, id);
        return true;
    }


//set the minimum sell amount for a token
//    Function is used to avoid "dust offers" that have
//    very small amount of tokens to sell, and it would
//    cost more gas to accept the offer, than the value
//    of tokens received.

function setMinSell(
        ERC20 pay_gem,     //token to assign minimum sell amount to
        uint dust          //maker (ask) minimum sell amount
    )
        public
        auth
        note
        returns (bool)
    {
        _dust[pay_gem] = dust;
        LogMinSell(pay_gem, dust);
        return true;
    }


//--Risk-parameter-config-------------------------------------------

function mold(bytes32 param, uint val) public note auth {
        if      (param == 'cap') cap = val;
        else if (param == 'mat') { require(val >= RAY); mat = val; }
        else if (param == 'tax') { require(val >= RAY); drip(); tax = val; }
        else if (param == 'fee') { require(val >= RAY); drip(); fee = val; }
        else if (param == 'axe') { require(val >= RAY); axe = val; }
        else if (param == 'gap') { require(val >= WAD); gap = val; }
        else return;
    }


//find the id of the next higher offer after offers[id]

function _find(uint id)
        internal
        view
        returns (uint)
    {
        require( id > 0 );

        address buy_gem = address(offers[id].buy_gem);
        address pay_gem = address(offers[id].pay_gem);
        uint top = _best[pay_gem][buy_gem];
        uint old_top = 0;

        // Find the larger-than-id order whose successor is less-than-id.
        while (top != 0 && _isPricedLtOrEq(id, top)) {
            old_top = top;
            top = _rank[top].prev;
        }
        return old_top;
    }


// force settlement of the system at a given price (sai per gem).
// This is nearly the equivalent of biting all cups at once.
// Important consideration: the gems associated with free skr can
// be tapped to make sai whole.

function cage(uint price) internal {
        require(!tub.off() && price != 0);
        caged = era();

        tub.drip();  // collect remaining fees
        tap.heal();  // absorb any pending fees

        fit = rmul(wmul(price, vox.par()), tub.per());
        // Most gems we can get per sai is the full balance of the tub.
        // If there is no sai issued, we should still be able to cage.
        if (sai.totalSupply() == 0) {
            fix = rdiv(WAD, price);
        } else {
            fix = min(rdiv(WAD, price), rdiv(tub.pie(), sai.totalSupply()));
        }

        tub.cage(fit, rmul(fix, sai.totalSupply()));
        tap.cage(fix);

        tap.vent();    // burn pending sale skr
    }


// Liquidation Ratio   150%
// Liquidation Penalty 13%
// Stability Fee       0.05%
// PETH Fee            0%
// Boom/Bust Spread   -3%
// Join/Exit Spread    0%
// Debt Ceiling        0

function configParams() public auth {
        require(step == 3);

        tub.mold("cap", 0);
        tub.mold("mat", ray(1.5  ether));
        tub.mold("axe", ray(1.13 ether));
        tub.mold("fee", 1000000000158153903837946257);  // 0.5% / year
        tub.mold("tax", ray(1 ether));
        tub.mold("gap", 1 ether);

        tap.mold("gap", 0.97 ether);

        step += 1;
    }


// Make a new offer without putting it in the sorted list.
// Takes funds from the caller into market escrow.
// ****Available to authorized contracts only!**********
// Keepers should call insert(id,pos) to put offer in the sorted list.

function _offeru(
        uint pay_amt,      //maker (ask) sell how much
        ERC20 pay_gem,     //maker (ask) sell which token
        uint buy_amt,      //maker (ask) buy how much
        ERC20 buy_gem      //maker (ask) buy which token
    )
        internal
        returns (uint id)
    {
        require(_dust[pay_gem] <= pay_amt);
        id = super.offer(pay_amt, pay_gem, buy_amt, buy_gem);
        _near[id] = _head;
        _head = id;
        LogUnsortedOffer(id);
    }


//put offer into the sorted list

function _sort(
        uint id,    //maker (ask) id
        uint pos    //position to insert into
    )
        internal
    {
        require(isActive(id));

        address buy_gem = address(offers[id].buy_gem);
        address pay_gem = address(offers[id].pay_gem);
        uint prev_id;                                      //maker (ask) id

        pos = pos == 0 || offers[pos].pay_gem != pay_gem || offers[pos].buy_gem != buy_gem || !isOfferSorted(pos)
        ?
            _find(id)
        :
            _findpos(id, pos);

        if (pos != 0) {                                    //offers[id] is not the highest offer
            //requirement below is satisfied by statements above
            //require(_isPricedLtOrEq(id, pos));
            prev_id = _rank[pos].prev;
            _rank[pos].prev = id;
            _rank[id].next = pos;
        } else {                                           //offers[id] is the highest offer
            prev_id = _best[pay_gem][buy_gem];
            _best[pay_gem][buy_gem] = id;
        }

        if (prev_id != 0) {                               //if lower offer does exist
            //requirement below is satisfied by statements above
            //require(!_isPricedLtOrEq(id, prev_id));
            _rank[prev_id].next = id;
            _rank[id].prev = prev_id;
        }

        _span[pay_gem][buy_gem]++;
        LogSortedOffer(id);
    }


// Remove offer from the sorted list (does not cancel offer)

function _unsort(
        uint id    //id of maker (ask) offer to remove from sorted list
    )
        internal
        returns (bool)
    {
        address buy_gem = address(offers[id].buy_gem);
        address pay_gem = address(offers[id].pay_gem);
        require(_span[pay_gem][buy_gem] > 0);

        require(_rank[id].delb == 0 &&                    //assert id is in the sorted list
                 isOfferSorted(id));

        if (id != _best[pay_gem][buy_gem]) {              // offers[id] is not the highest offer
            require(_rank[_rank[id].next].prev == id);
            _rank[_rank[id].next].prev = _rank[id].prev;
        } else {                                          //offers[id] is the highest offer
            _best[pay_gem][buy_gem] = _rank[id].prev;
        }

        if (_rank[id].prev != 0) {                        //offers[id] is not the lowest offer
            require(_rank[_rank[id].prev].next == id);
            _rank[_rank[id].prev].next = _rank[id].next;
        }

        _span[pay_gem][buy_gem]--;
        _rank[id].delb = block.number;                    //mark _rank[id] for deletion
        return true;
    }


/// @notice Backup function for activating token purchase
///  requires sender to be a member of the group or CLevel
/// @param _tokenId The ID of the Token group

function activatePurchase(uint256 _tokenId) external whenNotPaused {
    var group = tokenIndexToGroup[_tokenId];
    require(group.addressToContribution[msg.sender] > 0 ||
            msg.sender == ceoAddress ||
            msg.sender == cooAddress1 ||
            msg.sender == cooAddress2 ||
            msg.sender == cooAddress3 ||
            msg.sender == cfoAddress);

    // Safety check that enough money has been contributed to group
    var price = linkedContract.priceOf(_tokenId);
    require(group.contributedBalance >= price);

    // Safety check that token had not be purchased yet
    require(group.purchasePrice == 0);

    _purchase(_tokenId, price);
  }


/// @notice Allow user to leave purchase group; note that their contribution
///  will be added to their withdrawable balance, and not directly refunded.
///  User can call withdrawBalance to retrieve funds.
/// @param _tokenId The ID of the Token purchase group to be left

function leaveTokenGroup(uint256 _tokenId) external whenNotPaused {
    address userAdd = msg.sender;

    var group = tokenIndexToGroup[_tokenId];
    var contributor = userAddressToContributor[userAdd];

    // Safety check to prevent against an unexpected 0x0 default.
    require(_addressNotNull(userAdd));

    // Safety check to make sure group exists;
    require(group.exists);

    // Safety check to make sure group hasn't purchased token already
    require(group.purchasePrice == 0);

    // Safety checks to ensure contributor has contributed to group
    require(group.addressToContributorArrIndex[userAdd] > 0);
    require(contributor.tokenIdToGroupArrIndex[_tokenId] > 0);

    uint refundBalance = _clearContributorRecordInGroup(_tokenId, userAdd);
    _clearGroupRecordInContributor(_tokenId, userAdd);

    userAddressToContributor[userAdd].withdrawableBalance += refundBalance;
    FundsDeposited(userAdd, refundBalance);

    LeaveGroup(
      _tokenId,
      userAdd,
      tokenIndexToGroup[_tokenId].contributedBalance,
      refundBalance
    );
  }


/// @notice Allow user to leave purchase group; note that their contribution
///  and any funds they have in their withdrawableBalance will transfered to them.
/// @param _tokenId The ID of the Token purchase group to be left

function leaveTokenGroupAndWithdrawBalance(uint256 _tokenId) external whenNotPaused {
    address userAdd = msg.sender;

    var group = tokenIndexToGroup[_tokenId];
    var contributor = userAddressToContributor[userAdd];

    // Safety check to prevent against an unexpected 0x0 default.
    require(_addressNotNull(userAdd));

    // Safety check to make sure group exists;
    require(group.exists);

    // Safety check to make sure group hasn't purchased token already
    require(group.purchasePrice == 0);

    // Safety checks to ensure contributor has contributed to group
    require(group.addressToContributorArrIndex[userAdd] > 0);
    require(contributor.tokenIdToGroupArrIndex[_tokenId] > 0);

    uint refundBalance = _clearContributorRecordInGroup(_tokenId, userAdd);
    _clearGroupRecordInContributor(_tokenId, userAdd);

    userAddressToContributor[userAdd].withdrawableBalance += refundBalance;
    FundsDeposited(userAdd, refundBalance);

    _withdrawUserFunds(userAdd);

    LeaveGroup(
      _tokenId,
      userAdd,
      tokenIndexToGroup[_tokenId].contributedBalance,
      refundBalance
    );
  }


/// @dev In the event of needing a fork, this function moves all
///  of a group's contributors' contributions into their withdrawable balance.
/// @notice Group is dissolved after fn call
/// @param _tokenId The ID of the Token purchase group

function dissolveTokenGroup(uint256 _tokenId) external onlyCOO whenForking {
    var group = tokenIndexToGroup[_tokenId];

    // Safety check to make sure group exists and had not purchased a token
    require(group.exists);
    require(group.purchasePrice == 0);

    for (uint i = 0; i < tokenIndexToGroup[_tokenId].contributorArr.length; i++) {
      address userAdd = tokenIndexToGroup[_tokenId].contributorArr[i];

      var userContribution = group.addressToContribution[userAdd];

      _clearGroupRecordInContributor(_tokenId, userAdd);

      // clear contributor record on group
      tokenIndexToGroup[_tokenId].addressToContribution[userAdd] = 0;
      tokenIndexToGroup[_tokenId].addressToContributorArrIndex[userAdd] = 0;

      // move contributor's contribution to their withdrawable balance
      userAddressToContributor[userAdd].withdrawableBalance += userContribution;
      ProceedsDeposited(_tokenId, userAdd, userContribution);
    }
    activeGroups -= 1;
    tokenIndexToGroup[_tokenId].exists = false;
  }


/// @dev Backup fn to allow distribution of funds after sale,
///  for the special scenario where an alternate sale platform is used;
/// @notice Group is dissolved after fn call
/// @param _tokenId The ID of the Token purchase group
/// @param _amount Funds to be distributed

function distributeCustomSaleProceeds(uint256 _tokenId, uint256 _amount) external onlyCOO {
    var group = tokenIndexToGroup[_tokenId];

    // Safety check to make sure group exists and had purchased the token
    require(group.exists);
    require(group.purchasePrice > 0);
    require(_amount > 0);

    _distributeProceeds(_tokenId, _amount);
  }


/// @dev Distribute funds after a token is sold.
///  Group is dissolved after fn call
/// @param _tokenId The ID of the Token purchase group

function distributeSaleProceeds(uint256 _tokenId) external onlyCOO {
    var group = tokenIndexToGroup[_tokenId];

    // Safety check to make sure group exists and had purchased the token
    require(group.exists);
    require(group.purchasePrice > 0);

    // Safety check to make sure token had been sold
    uint256 currPrice = linkedContract.priceOf(_tokenId);
    uint256 soldPrice = _newPrice(group.purchasePrice);
    require(currPrice > soldPrice);

    uint256 paymentIntoContract = uint256(SafeMath.div(SafeMath.mul(soldPrice, 94), 100));
    _distributeProceeds(_tokenId, paymentIntoContract);
  }


/// @dev Backup fn to allow transfer of token out of
///  contract, for use where a purchase group wants to use an alternate
///  selling platform
/// @param _tokenId The ID of the Token purchase group
/// @param _to Address to transfer token to

function transferToken(uint256 _tokenId, address _to) external onlyCOO {
    var group = tokenIndexToGroup[_tokenId];

    // Safety check to make sure group exists and had purchased the token
    require(group.exists);
    require(group.purchasePrice > 0);

    linkedContract.transfer(_to, _tokenId);
  }


/// @dev Withdraws sale commission, CFO-only functionality
/// @param _to Address for commission to be sent to

function withdrawCommission(address _to) external onlyCFO {
    uint256 balance = commissionBalance;
    address transferee = (_to == address(0)) ? cfoAddress : _to;
    commissionBalance = 0;
    if (balance > 0) {
      transferee.transfer(balance);
    }
    FundsWithdrawn(transferee, balance);
  }