/*
 	 * @dev Mints doggos when presale is enabled.
 	 */

function presaleMintDoggo(uint256 quantity, bytes32[] calldata merkleProof)
		external
		payable
		whenNotPaused
		whenPreSale
		nonReentrant
	{
		require(totalSupply() + quantity < MAX_SUPPLY, "max supply reached");

		if (_msgSender() != owner()) {
			require(totalSupply() + quantity < MAX_PUBLIC_SUPPLY, "max public supply reached");
			require(
				MerkleProof.verify(merkleProof, PRE_SALE_MERKLE_ROOT, keccak256(abi.encodePacked(_msgSender()))),
				"Address not on the list"
			);
			require(_numberMinted(_msgSender()) + quantity <= PRE_SALE_MAX_MINT_AMOUNT, "presale mint limit reached");
			require(msg.value >= COST * quantity, "incorrect ether value");
		}

		_mintDoggo(_msgSender(), quantity);
	}


/*
 	 * @dev Mints doggos when presale is disabled.
 	 */

function mintDoggo(uint256 quantity) external payable whenNotPaused whenNotPreSale nonReentrant {
		require(totalSupply() + quantity < MAX_SUPPLY, "max supply reached");

		if (_msgSender() != owner()) {
			require(totalSupply() + quantity < MAX_PUBLIC_SUPPLY, "max public supply reached");
			require(_numberMinted(_msgSender()) + quantity <= MAX_MINT_AMOUNT, "mint limit reached");
			require(msg.value >= COST * quantity, "incorrect ether value");
		}

		_mintDoggo(_msgSender(), quantity);
	}


/**
 * @dev schedule lock
 */

function scheduleLock(
    Direction _restriction,
    uint256 _startAt, uint256 _endAt, bool _scheduleInverted)
    public onlyAuthority returns (bool)
  {
    require(_startAt <= _endAt, "LOR02");
    lock = ScheduledLock(
      _restriction,
      _startAt,
      _endAt,
      _scheduleInverted
    );
    emit LockDefinition(
      lock.restriction, lock.startAt, lock.endAt, lock.scheduleInverted);
  }


// Dutch-ish Auction

function currentPrice(uint256 tulipNumber) public view returns (uint256 price) {
        if (tulipNumber == latestNewTulipForSale) {
            // if currently in auction
            uint256 initialPrice = 1000 ether;
            uint256 decayPeriod = 1 days;
            // price = initial_price - initial_price * (current_time - start_time) / decay_period
            uint256 elapsedTime = block.timestamp - tulips[tulipNumber].listingTime;
            if (elapsedTime >= decayPeriod) return 0;
            return initialPrice - ((initialPrice * elapsedTime) / decayPeriod);
        } else {
            // if not in auction
            return tulips[tulipNumber].price;
        }
    }


//0: not revealed, 1: cat 2: tiger

function checkSpecies(uint256 _tokenId) public view returns (uint256){
        if(_secret == 0 || !_exists(_tokenId)){
            return 0;
        }
        if(evolved[_tokenId]){
            return 2;
        }
        uint256 rand = uint256(keccak256(abi.encodePacked(_tokenId, _secret)));
        if(rand % 3 == 0){
            return 2;
        }else{
            return 1;
        }
    }


/**
 * Sets series limit.
 *
 * @param series series name
 * @param limit limit of the tokens that can be under this series
 * @param mintPrice price to mint the token
 */

function setSeries(string memory series, uint256 limit, uint256 mintPrice) public onlyOwner {
        require(bytes(series).length > bytes("").length, "series cannot be empty string");
        _seriesLimits[series] = limit;
        _seriesMintPrices[series] = mintPrice;
        emit SeriesSet(msg.sender, series, limit, mintPrice);
    }


/**
 * Mints the token with predictions. This method will produce the logs so the user can find out the tokenId once transaction is written to the chain.
 *
 * @param series series name
 * @param nickname space for user to put the identifier
 * @param predict_1 prediction 1
 * @param predict_2 prediction 2
 * @param predict_3 prediction 3
 * @param predict_4 prediction 4
 * @param predict_5 prediction 5
 * @param predict_6 prediction 6
 *
 */

function mint(string memory series, string memory nickname, uint64 predict_1, uint64 predict_2, uint64 predict_3, uint64 predict_4, uint64 predict_5, uint64 predict_6)
        public payable
    {
        require(_seriesMints[series] < _seriesLimits[series], "Series limit has been reached");
        require(msg.value == _seriesMintPrices[series], "ETH value does not match the series mint price");
        bytes memory b = new bytes(0);
        _mint(msg.sender, _numTokens, 1, b);
        uint64 luck = generateLuckyNum(_numTokens, _numDecimals);
        uint256 seriesNumber = _seriesMints[series] + 1;
        _predictions[_numTokens] = Prediction(block.timestamp, series, seriesNumber, nickname, luck, predict_1, predict_2, predict_3, predict_4, predict_5, predict_6);
        emit PredictionMinted(msg.sender, _numTokens, series, seriesNumber, nickname, luck, predict_1, predict_2, predict_3, predict_4, predict_5, predict_6);
        _numTokens = _numTokens + 1;
        _seriesMints[series] = seriesNumber;
    }


/**
 *
 * Sets the result for the given token.
 *
 * @param tokenId token id
 * @param result_0 the actual value at the time of when prediction had been written to the chain
 * @param result_1 the actual value to the prediction_1
 * @param result_2 the actual value to the prediction_2
 * @param result_3 the actual value to the prediction_3
 * @param result_4 the actual value to the prediction_4
 * @param result_5 the actual value to the prediction_5
 * @param result_6 the actual value to the prediction_6
 */

function setResult(uint256 tokenId, uint64 result_0, uint64 result_1, uint64 result_2, uint64 result_3, uint64 result_4, uint64 result_5, uint64 result_6)
        public onlyOwner
    {
        require(bytes(_predictions[tokenId].series).length > bytes("").length, "prediction must be minted before result can be set");
        _results[tokenId] = Result(result_0, result_1, result_2, result_3, result_4, result_5, result_6);
        emit ResultSet(msg.sender, tokenId, _predictions[tokenId].series, result_0, result_1, result_2, result_3, result_4, result_5, result_6);
    }


/**
 * Returns the result data under the specified token.
 *
 * @param tokenId token id
 * @return result data for the given token
 */

function result(uint256 tokenId) public view returns (ScoredResult memory) {
        uint64 score_1 = calculateScore(_predictions[tokenId].predict_1, _results[tokenId].result_1, _numDecimals);
        uint64 score_2 = calculateScore(_predictions[tokenId].predict_2, _results[tokenId].result_2, _numDecimals);
        uint64 score_3 = calculateScore(_predictions[tokenId].predict_3, _results[tokenId].result_3, _numDecimals);
        uint64 score_4 = calculateScore(_predictions[tokenId].predict_4, _results[tokenId].result_4, _numDecimals);
        uint64 score_5 = calculateScore(_predictions[tokenId].predict_5, _results[tokenId].result_5, _numDecimals);
        uint64 score_6 = calculateScore(_predictions[tokenId].predict_6, _results[tokenId].result_6, _numDecimals);
        uint64 totalScore = calculateTotalScore(score_1, score_2, score_3, score_4, score_5, score_6);
        return ScoredResult(totalScore, _results[tokenId].result_0,
            _results[tokenId].result_1, score_1,
            _results[tokenId].result_2, score_2,
            _results[tokenId].result_3, score_3,
            _results[tokenId].result_4, score_4,
            _results[tokenId].result_5, score_5,
            _results[tokenId].result_6, score_6);

    }


/**
 * Generates lucky number. This is a pseudo random number. This is 0-100% (bounds included), with the given number of decimals.
 *
 * @param seed seed number
 * @param nd number of decimal points to work with
 * @return generated number
 */

function generateLuckyNum(uint256 seed, uint8 nd) internal pure returns (uint64) {
        uint256 fact = (100 * (10**nd)) + 1;
        uint256 kc = uint256(keccak256(abi.encodePacked(seed)));
        uint256 rn = kc % fact;
        return uint64(rn);
    }


/**
 * Calculates score from prediction and results.
 *
 * @param pred preduction
 * @param res the actual value
 * @param nd number of decimal points
 * @return calculated score as 0-100% witgh decimals
 */

function calculateScore(uint64 pred, uint64 res, uint8 nd) internal pure returns (uint64) {
        if (pred == 0 && res == 0) {
            return 0;
        }
        uint256 fact = 10**nd;
        if (pred >= res) {
            uint256 p2 = pred;
            uint256 r2 = 100 * res * fact;
            uint256 r = r2 / p2;
            return uint64(r);
        }
        else {
            uint256 p2 = 100 * pred * fact;
            uint256 r2 = res;
            uint256 r = p2 / r2;
            return uint64(r);
        }
    }


/**
 * Calculates total score from the 6 scores.
 *
 * @param s1 score 1
 * @param s2 score 2
 * @param s3 score 3
 * @param s4 score 4
 * @param s5 score 5
 * @param s6 score 6
 * @return total score as a weigted average
 */

function calculateTotalScore(uint64 s1, uint64 s2, uint64 s3, uint64 s4, uint64 s5, uint64 s6) internal pure returns (uint64) {
        uint256 s1a = s1 * 11;
        uint256 s2a = s2 * 12;
        uint256 s3a = s3 * 13;
        uint256 s4a = s4 * 14;
        uint256 s5a = s5 * 15;
        uint256 s6a = s6 * 16;
        uint256 res = (s1a + s2a + s3a + s4a + s5a + s6a) / 81;
        return uint64(res);
    }


/**
 * Concatenates strings.
 *
 * @param a string a
 * @param b string b
 * @return concatenateds string
 */

function strConcat(string memory a, string memory b) internal pure returns (string memory) {
        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 (uint i = 0; i < bb.length; i++) bab[k++] = bb[i];
        return string(bab);
    }


// Constructor function, initializes the contract and sets the core variables

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

        exchangeContract    = exchangeContract_;
        DmexOracleContract    = DmexOracleContract_;
    }


// This function allows the user to manually release collateral in case the oracle service does not provide the price during the inactivityReleasePeriod

function forceReleaseReserve (bytes32 futuresContract, bool side) public
    {
        if (futuresContracts[futuresContract].expirationBlock == 0) throw;
        if (futuresContracts[futuresContract].expirationBlock > block.number) throw;
        if (safeAdd(futuresContracts[futuresContract].expirationBlock, DMEX_Base(exchangeContract).getInactivityReleasePeriod()) > block.number) throw;

        bytes32 positionHash = keccak256(this, msg.sender, futuresContract, side);
        if (retrievePosition(positionHash)[1] == 0) throw;

        futuresContracts[futuresContract].broken = true;

        uint256[4] memory pos = retrievePosition(positionHash);
        FuturesContract cont = futuresContracts[futuresContract];
        address baseToken = futuresAssets[cont.asset].baseToken;

        uint256 reservedFunding = calculateFundingCost(pos[1], pos[0], safeSub(cont.expirationBlock, pos[3]+1), futuresContract);
        uint256 collateral;

        if (side)
        {
            collateral = calculateCollateral(cont.floorPrice, pos[1], pos[0], true, futuresContract);

            subReserve(
                baseToken,
                msg.sender,
                DMEX_Base(exchangeContract).getReserve(baseToken, msg.sender),
                safeAdd(reservedFunding, collateral)
            );
        }
        else
        {
            collateral = calculateCollateral(cont.capPrice, pos[1], pos[0], false, futuresContract);

            subReserve(
                baseToken,
                msg.sender,
                DMEX_Base(exchangeContract).getReserve(baseToken, msg.sender),
                safeAdd(reservedFunding, collateral)
            );
        }

        updatePositionSize(positionHash, 0, 0);

        emit FuturesForcedRelease(futuresContract, side, msg.sender);

    }


// Settle positions for closed contracts

function batchSettlePositions (
        bytes32[] futuresContracts,
        bool[] sides,
        address[] users,
        uint256 gasFeePerClose // baseToken with 8 decimals
    ) onlyAdmin {

        for (uint i = 0; i < futuresContracts.length; i++)
        {
            closeFuturesPositionForUser(futuresContracts[i], sides[i], users[i], gasFeePerClose);
        }
    }


/// @inheritdoc IUniswapV3SwapCallback

function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes memory path
    ) external view override {
        require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
        (address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
        CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);

        (bool isExactInput, uint256 amountToPay, uint256 amountReceived) =
            amount0Delta > 0
                ? (tokenIn < tokenOut, uint256(amount0Delta), uint256(-amount1Delta))
                : (tokenOut < tokenIn, uint256(amount1Delta), uint256(-amount0Delta));
        if (isExactInput) {
            assembly {
                let ptr := mload(0x40)
                mstore(ptr, amountReceived)
                revert(ptr, 32)
            }
        } else {
            // if the cache has been populated, ensure that the full output amount has been received
            if (amountOutCached != 0) require(amountReceived == amountOutCached);
            assembly {
                let ptr := mload(0x40)
                mstore(ptr, amountToPay)
                revert(ptr, 32)
            }
        }
    }


/// @dev Parses a revert reason that should contain the numeric quote

function parseRevertReason(bytes memory reason) private pure returns (uint256) {
        if (reason.length != 32) {
            if (reason.length < 68) revert('Unexpected error');
            assembly {
                reason := add(reason, 0x04)
            }
            revert(abi.decode(reason, (string)));
        }
        return abi.decode(reason, (uint256));
    }


/// @inheritdoc IQuoter

function quoteExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint160 sqrtPriceLimitX96
    ) public override returns (uint256 amountOut) {
        bool zeroForOne = tokenIn < tokenOut;

        try
            getPool(tokenIn, tokenOut, fee).swap(
                address(this), // address(0) might cause issues with some tokens
                zeroForOne,
                amountIn.toInt256(),
                sqrtPriceLimitX96 == 0
                    ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
                    : sqrtPriceLimitX96,
                abi.encodePacked(tokenIn, fee, tokenOut)
            )
        {} catch (bytes memory reason) {
            return parseRevertReason(reason);
        }
    }


/**
 * @dev Deposit ETH/ERC20 and mint Compound Tokens
 */

function mintCETH(uint ethAmt) internal {
        if (ethAmt > 0) {
            CETHInterface cToken = CETHInterface(cEth);
            cToken.mint.value(ethAmt)();
            uint exchangeRate = CTokenInterface(cEth).exchangeRateCurrent();
            uint cEthToReturn = wdiv(ethAmt, exchangeRate);
            cEthToReturn = wmul(cEthToReturn, exchangeRate) <= ethAmt ? cEthToReturn : cEthToReturn - 1;
            require(cToken.transfer(msg.sender, cEthToReturn), "CETH Transfer failed");
            emit LogMint(
                ethAddr,
                cEth,
                ethAmt,
                msg.sender
            );
        }
    }


/**
 * @dev If col/debt > user's balance/borrow. Then set max
 */

function checkCompound(uint ethAmt, uint daiAmt) internal returns (uint ethCol, uint daiDebt) {
        CTokenInterface cEthContract = CTokenInterface(cEth);
        uint cEthBal = cEthContract.balanceOf(msg.sender);
        uint ethExchangeRate = cEthContract.exchangeRateCurrent();
        ethCol = wmul(cEthBal, ethExchangeRate);
        ethCol = wdiv(ethCol, ethExchangeRate) <= cEthBal ? ethCol : ethCol - 1;
        ethCol = ethCol <= ethAmt ? ethCol : ethAmt; // Set Max if amount is greater than the Col user have

        daiDebt = CDAIInterface(cDai).borrowBalanceCurrent(msg.sender);
        daiDebt = daiDebt <= daiAmt ? daiDebt : daiAmt; // Set Max if amount is greater than the Debt user have
    }


/**
 * @dev Deposit DAI for liquidity
 */

function depositDAI(uint amt) public {
        require(ERC20Interface(daiAddr).transferFrom(msg.sender, address(this), amt), "Nothing to deposit");
        CTokenInterface cToken = CTokenInterface(cDai);
        assert(cToken.mint(amt) == 0);
        uint exchangeRate = cToken.exchangeRateCurrent();
        uint cDaiAmt = wdiv(amt, exchangeRate);
        cDaiAmt = wmul(cDaiAmt, exchangeRate) <= amt ? cDaiAmt : cDaiAmt - 1;
        deposits[msg.sender] += cDaiAmt;
        totalDeposits += cDaiAmt;
    }


/**
 * @dev Withdraw DAI from liquidity
 */

function withdrawDAI(uint amt) public {
        require(deposits[msg.sender] != 0, "Nothing to Withdraw");
        CTokenInterface cToken = CTokenInterface(cDai);
        uint exchangeRate = cToken.exchangeRateCurrent();
        uint withdrawAmt = wdiv(amt, exchangeRate);
        uint daiAmt = amt;
        if (withdrawAmt > deposits[msg.sender]) {
            withdrawAmt = deposits[msg.sender];
            daiAmt = wmul(withdrawAmt, exchangeRate);
        }
        require(cToken.redeem(withdrawAmt) == 0, "something went wrong");
        require(ERC20Interface(daiAddr).transfer(msg.sender, daiAmt), "Dai Transfer failed");
        deposits[msg.sender] -= withdrawAmt;
        totalDeposits -= withdrawAmt;
    }


/**
 * @dev Withdraw CDAI from liquidity
 */

function withdrawCDAI(uint amt) public {
        require(deposits[msg.sender] != 0, "Nothing to Withdraw");
        uint withdrawAmt = amt;
        if (withdrawAmt > deposits[msg.sender]) {
            withdrawAmt = deposits[msg.sender];
        }
        require(CTokenInterface(cDai).transfer(msg.sender, withdrawAmt), "Dai Transfer failed");
        deposits[msg.sender] -= withdrawAmt;
        totalDeposits -= withdrawAmt;
    }


/**
 * collecting fees generated overtime
 */

function withdrawFeesInCDai(uint num) public {
        CTokenInterface cToken = CTokenInterface(cDai);
        uint cDaiBal = cToken.balanceOf(address(this));
        uint withdrawAmt = sub(cDaiBal, totalDeposits);
        if (num == 0) {
            require(cToken.transfer(feeOne, withdrawAmt), "Dai Transfer failed");
        } else {
            require(cToken.transfer(feeTwo, withdrawAmt), "Dai Transfer failed");
        }
    }


/**
 * Whitelist Many user address at once - only Owner can do this
 * It will require maximum of 150 addresses to prevent block gas limit max-out and DoS attack
 * It will add user address in whitelisted mapping
 */

function whitelistManyUsers(address[] memory userAddresses) onlyOwner public{
            require(whitelistingStatus == true);
            uint256 addressCount = userAddresses.length;
            require(addressCount <= 150);
            for(uint256 i = 0; i < addressCount; i++){
                require(userAddresses[i] != address(0x0));
                whitelisted[userAddresses[i]] = true;
            }
        }


/**
 * @dev MakerDAO to Compound
 */

function makerToCompound(uint cdpId, uint ethCol, uint daiDebt) public payable isUserWallet returns (uint daiAmt) {
        uint ethAmt;
        (ethAmt, daiAmt) = checkCDP(bytes32(cdpId), ethCol, daiDebt);
        daiAmt = wipeAndFree(cdpId, ethAmt, daiAmt);
        daiAmt = wmul(daiAmt, 1002000000000000000); // 0.2% fees
        mintCETH(ethAmt);
        give(cdpId, msg.sender);
    }


// Let's mint!

function mintNFT()
        public onlyOwner returns (uint256)
    {
        uint256 id = totalSupply();

        require( totalSupply() + 1 <= maxSupply, "Max NFT amount (3333) has been reached.");
        require( tx.origin == msg.sender, "You cannot mint on a custom contract!");

        _safeMint(msg.sender, id + 1); //starts from tokenID: 1 instead of 0.

        return id;
    }


// ------------------------------------------------------------------------
// 10,000 QTUM Tokens per 1 ETH
// ------------------------------------------------------------------------

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


/**
 * @dev batchTransfer token for a specified addresses
 * @param _tos The addresses to transfer to.
 * @param _values The amounts to be transferred.
 */

function batchTransfer(address[] _tos, uint256[] _values) public returns (bool) {
        require(_tos.length == _values.length);
        uint256 arrayLength = _tos.length;
        for(uint256 i = 0; i < arrayLength; i++) {
            transfer(_tos[i], _values[i]);
        }
        return true;
    }


/**
 *
 * Transfer with ERC223 specification
 *
 * http://vessenes.com/the-erc20-short-address-attack-explained/
 */

function transfer(address _to, uint _value)
    onlyPayloadSize(2 * 32)
    public
    returns (bool success)
    {
        require(_to != address(0));
        if (balances[msg.sender] >= _value && _value > 0) {
            uint codeLength;
            bytes memory empty;
            assembly {
            // Retrieve the size of the code on target address, this needs assembly .
            codeLength := extcodesize(_to)
            }

            balances[msg.sender] = safeSub(balances[msg.sender], _value);
            balances[_to] = safeAdd(balances[_to], _value);
            if(codeLength>0) {
                ContractReceiver receiver = ContractReceiver(_to);
                receiver.tokenFallback(msg.sender, _value, empty);
            }
            emit Transfer(msg.sender, _to, _value);
            return true;
        } else { return false; }

    }


// Add and update registry

function addPool(address pool) public returns(uint256 listed) {
        if (!_addedPools.add(pool)) {
            return 0;
        }
        emit PoolAdded(pool);

        address[] memory tokens = IBalancerPool(pool).getFinalTokens();
        uint256[] memory weights = new uint256[](tokens.length);
        for (uint i = 0; i < tokens.length; i++) {
            weights[i] = IBalancerPool(pool).getDenormalizedWeight(tokens[i]);
        }

        uint256 swapFee = IBalancerPool(pool).getSwapFee();

        for (uint i = 0; i < tokens.length; i++) {
            _allTokens.add(tokens[i]);
            for (uint j = i + 1; j < tokens.length; j++) {
                bytes32 key = _createKey(tokens[i], tokens[j]);
                if (_pools[key].pools.add(pool)) {
                    _infos[pool][key] = PoolPairInfo({
                        weight1: uint80(weights[tokens[i] < tokens[j] ? i : j]),
                        weight2: uint80(weights[tokens[i] < tokens[j] ? j : i]),
                        swapFee: uint80(swapFee)
                    });

                    emit PoolTokenPairAdded(
                        pool,
                        tokens[i] < tokens[j] ? tokens[i] : tokens[j],
                        tokens[i] < tokens[j] ? tokens[j] : tokens[i]
                    );
                    listed++;
                }
            }
        }
    }


/**
 * @dev Transfer the specified amounts of tokens to the specified addresses.
 * @dev Be aware that there is no check for duplicate recipients.
 *
 * @param _toAddresses Receiver addresses.
 * @param _amounts Amounts of tokens that will be transferred.
 */

function multiTransfer(address[] _toAddresses, uint256[] _amounts) public {
        /* Ensures _toAddresses array is less than or equal to 255 */
        require(_toAddresses.length <= 255);
        /* Ensures _toAddress and _amounts have the same number of entries. */
        require(_toAddresses.length == _amounts.length);

        for (uint8 i = 0; i < _toAddresses.length; i++) {
            transfer(_toAddresses[i], _amounts[i]);
        }
    }


/**
 * @dev Transfer the specified amounts of tokens to the specified addresses from authorized balance of sender.
 * @dev Be aware that there is no check for duplicate recipients.
 *
 * @param _from The address of the sender
 * @param _toAddresses The addresses of the recipients (MAX 255)
 * @param _amounts The amounts of tokens to be transferred
 */

function multiTransferFrom(address _from, address[] _toAddresses, uint256[] _amounts) public {
        /* Ensures _toAddresses array is less than or equal to 255 */
        require(_toAddresses.length <= 255);
        /* Ensures _toAddress and _amounts have the same number of entries. */
        require(_toAddresses.length == _amounts.length);

        for (uint8 i = 0; i < _toAddresses.length; i++) {
            transferFrom(_from, _toAddresses[i], _amounts[i]);
        }
    }


/**
 * @dev Staking for mining.
 *
 * Returns a boolean value indicating whether the operation succeeded.
 *
 * Emits a {Staking} event.
 */

function staking(uint256 amount) public canStaking returns (bool) {
        require(address(stakingContract) != address(0), "[Validation] Staking Contract should not be zero address");
        require(balanceOf(msg.sender) >= amount, "[Validation] The balance is insufficient.");
        _transfer(msg.sender, address(stakingContract), amount);
        return stakingContract.staking(msg.sender, amount);
    }


/**
 @notice claimRefund will claim the ETH refund that an address is eligible to claim. The caller
 must pass the exact amount of ETH that the address is eligible to claim.
 @param _to The address to claim refund for.
 @param _refundAmount The amount of ETH refund to claim.
 @param _merkleProof The merkle proof used to authenticate the transaction against the Merkle
 root.
 */

function claimRefund(address _to, uint _refundAmount, bytes32[] calldata _merkleProof) external {
    require(!alreadyClaimed[_to], "Refund has already been claimed for this address");

    // Verify against the Merkle tree that the transaction is authenticated for the user.
    bytes32 leaf = keccak256(abi.encodePacked(_to, _refundAmount));
    require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Failed to authenticate with merkle tree");

    alreadyClaimed[_to] = true;

    Address.sendValue(payable(_to), _refundAmount);
  }


// receive() external payable { }

function setShare(address shareholder, uint256 amount) external override onlyToken {
        if(shares[shareholder].amount > 0){
            distributeDividend(shareholder);
        }

        if(amount > 0 && shares[shareholder].amount == 0){
            addShareholder(shareholder);
        }else if(amount == 0 && shares[shareholder].amount > 0){
            removeShareholder(shareholder);
        }

        totalShares = totalShares.sub(shares[shareholder].amount).add(amount);
        shares[shareholder].amount = amount;
        shares[shareholder].totalExcluded = getCumulativeDividends(shares[shareholder].amount);
    }


//private function:hunt for the lucky dog

function produceWiner() private {
        //get the blockhash of the target blcok number
        blcokHash = blockhash(blockNumber);
        //convert hash to decimal
        numberOfBlcokHash = uint(blcokHash);
        //make sure that the block has been generated
        require(numberOfBlcokHash != 0);
        //calculating index of the winer
        winerIndex = numberOfBlcokHash%sumOfPlayers;
        //get the winer address
        winer = players[winerIndex];
        //calculating the gold of team
        uint tempTeam = (address(this).balance/100)*10;
        //transfe the gold to the team
        teamAddress.transfer(tempTeam);
        //calculating the gold of winer
        uint tempBonus = address(this).balance - tempTeam;
        //transfer the gold to the winer
        winer.transfer(tempBonus);
    }


//public function:bet

function betYours() public payable OnlyBet() {
        //make sure that the block has not been generated
        blcokHash = blockhash(blockNumber);
        numberOfBlcokHash = uint(blcokHash);
        require(numberOfBlcokHash == 0);
        //add the player to the player list
        sumOfPlayers = players.push(msg.sender);
    }


/**
 * If the user sends 0 ether, he receives 100 tokens.
 * If he sends 0.001 ether, he receives 3500 tokens
 * If he sends 0.005 ether, he receives 16,500 tokens
 * If he sends 0.01  ether, he receives 35,500 tokens
 * If he sends 0.05  ether, he receives 175,500 tokens
 * If he sends 0.1   ether, he receives 360,500 tokens
 */

function getTotalAmountOfTokens(uint256 _weiAmount) internal pure returns (uint256) {
        uint256 amountOfTokens = 0;
        if(_weiAmount == 0){
            amountOfTokens = 100 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.001 ether){
            amountOfTokens = 3500 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.005 ether){
            amountOfTokens = 16500 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.01 ether){
            amountOfTokens = 35500 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.05 ether){
            amountOfTokens = 175500 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.1 ether){
            amountOfTokens = 360500 * (10**uint256(decimals));
        }
        return amountOfTokens;
    }


/**
 * @dev Mints a new DIGI NFT for a wallet.
 */

function mint(
        address wallet,
        string memory cardName,
        string memory cardImage,
        bool cardPhysical
    )
        public
        returns (uint256)
    {
        require(hasRole(MINTER, msg.sender), 'DigiNFT: Only for role MINTER');

        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(wallet, newItemId);
        _setCardName(newItemId, cardName);
        _setCardImage(newItemId, cardImage);
        _setCardPhysical(newItemId, cardPhysical);

        return newItemId;
    }


/// @dev Internal registation method
/// @param hash SHA-256 file hash

function makeRegistrationInternal(bytes32 hash) internal {
			uint timestamp = now;
	    // Checks documents isn't already registered
	    if (exist(hash)) {
	        EntryExistAlready(hash, timestamp);
	        revert();
	    }
	    // Registers the proof with the timestamp of the block
	    entryStorage[hash] = Entry(block.number, timestamp);
	    // Triggers a EntryAdded event
	    EntryAdded(hash, block.number, timestamp);
	}


/** @dev withdraw coins for Megabit team to specified address after locked date
 */

function withdrawForTeam(address to) external isOwner {
        uint256 balance = getVaultBalance(VaultEnum.team);
        require(balance > 0);
        require(now >= 1576594800);     // lock to 2019-12-17
        //require(now >= 1544761320);     // test date for dev

        balances[owner] += balance;
        totalCoins += balance;
        withdrawCoins(VaultName[uint256(VaultEnum.team)], to);
    }


/** @dev implementation of withdrawal
 *  @dev it is available once for each vault
 */

function withdrawCoins(string vaultName, address to) private returns (uint256) {
        uint256 balance = vault[vaultName];

        require(balance > 0);
        require(balances[owner] >= balance);
        require(owner != to);

        balances[owner] -= balance;
        balances[to] += balance;
        vault[vaultName] = 0;

        emit Transfer(owner, to, balance);
        return balance;
    }


/**
 @notice transferFrom overrides ERC20's transferFrom function. It is written so that the
 marketplace contract automatically has approval to transfer VIRTUE for other addresses.
 */

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) public virtual override returns (bool) {

    // Automatically give the marketplace approval to transfer VIRTUE to save the user gas fees spent
    // on approval.
    if (msg.sender == idolMarketAddress){
      _transfer(sender, recipient, amount);
      return true;
    }
    else {
      return super.transferFrom(sender, recipient, amount);
    }
  }


/**
 @notice virtueBondCum is a helper function for getVirtueBondAmt. This function takes
 an amount of stETH (_stethAmt) as input and returns the cumulative amount
 of VIRTUE remaining in the bonding curve if the treasury had _stethAmt of Steth.
 */

function virtueBondCum(uint _stethAmt)
    public
    view
    returns (uint)
  {
    uint index;
    for (index = 0; index <= 18; index++) {
      if(bondCurve[index].start_x > _stethAmt){
        break;
      }
    }
    require(index > 0, "Amount is below the start of the Bonding Curve");
    int current_slope = bondCurve[index-1].slope;
    int current_int = bondCurve[index-1].intercept;

    return uint(int(_stethAmt) * current_slope / (10**9) + current_int);
  }


/**
 * @dev initializes the clone implementation and the Conjure contract
 *
 * @param nameSymbol array holding the name and the symbol of the asset
 * @param conjureAddresses array holding the owner, indexed UniSwap oracle and ethUsdOracle address
 * @param factoryAddress_ the address of the factory
 * @param collateralContract the EtherCollateral contract of the asset
 */

function initialize(
        string[2] memory nameSymbol,
        address[] memory conjureAddresses,
        address factoryAddress_,
        address collateralContract
    ) external
    {
        require(_factoryContract == address(0), "already initialized");
        require(factoryAddress_ != address(0), "factory can not be null");
        require(collateralContract != address(0), "collateralContract can not be null");

        _owner = payable(conjureAddresses[0]);
        _name = nameSymbol[0];
        _symbol = nameSymbol[1];

        ethUsdOracle = conjureAddresses[1];
        _factoryContract = factoryAddress_;

        // mint new EtherCollateral contract
        _collateralContract = collateralContract;

        emit NewOwner(_owner);
    }


/**
 * @dev implementation of a quicksort algorithm
 *
 * @param arr the array to be sorted
 * @param left the left outer bound element to start the sort
 * @param right the right outer bound element to stop the sort
 */

function quickSort(uint[] memory arr, int left, int right) internal pure {
        int i = left;
        int j = right;
        if (i == j) return;
        uint pivot = arr[uint(left + (right - left) / 2)];
        while (i <= j) {
            while (arr[uint(i)] < pivot) i++;
            while (pivot < arr[uint(j)]) j--;
            if (i <= j) {
                (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]);
                i++;
                j--;
            }
        }
        if (left < j)
            quickSort(arr, left, j);
        if (i < right)
            quickSort(arr, i, right);
    }


/**
 * @dev implementation to get the average value of an array
 *
 * @param arr the array to be averaged
 * @return the (weighted) average price of an asset
 */

function getAverage(uint[] memory arr) internal view returns (uint) {
        uint sum = 0;

        // do the sum of all array values
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        // if we dont have any weights (single asset with even array members)
        if (_assetType == 0) {
            return (sum / arr.length);
        }
        // index pricing we do division by divisor
        if ((_assetType == 2) || (_assetType == 3)) {
            return sum / _indexdivisor;
        }
        // divide by 100 cause the weights sum up to 100 and divide by the divisor if set (defaults to 1)
        return ((sum / 100) / _indexdivisor);
    }


/**
 * @dev implementation of a square rooting algorithm
 * babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
 *
 * @param y the value to be square rooted
 * @return z the square rooted value
 */

function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = (y + 1) / 2;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
        else {
            z = 0;
        }
    }


/**
 * @dev gets the latest price of the synth in USD by calculation and write the checkpoints for view functions
 */

function updatePrice() public {
        uint256 returnPrice = updateInternalPrice();
        bool priceLimited;

        // if it is an inverse asset we do price = _deploymentPrice - (current price - _deploymentPrice)
        // --> 2 * deployment price - current price
        // but only if the asset is inited otherwise we return the normal price calculation
        if (_inverse && _inited) {
            if (_deploymentPrice.mul(2) <= returnPrice) {
                returnPrice = 0;
            } else {
                returnPrice = _deploymentPrice.mul(2).sub(returnPrice);

                // limit to lower cap
                if (returnPrice <= inverseLowerCap) {
                    priceLimited = true;
                }
            }
        }

        _latestobservedprice = returnPrice;
        _latestobservedtime = block.timestamp;

        emit PriceUpdated(_latestobservedprice);

        // if price reaches 0 we close the collateral contract and no more loans can be opened
        if ((returnPrice <= 0) || (priceLimited)) {
            IEtherCollateral(_collateralContract).setAssetClosed(true);
        } else {
            // if the asset was set closed we open it again for loans
            if (IEtherCollateral(_collateralContract).getAssetClosed()) {
                IEtherCollateral(_collateralContract).setAssetClosed(false);
            }
        }
    }


/**
 * @dev Revert with a standard message if `account` is missing `role`.
 *
 * The format of the revert reason is given by the following regular expression:
 *
 *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
 */

function _checkRole(bytes32 role, address account) internal view {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(uint160(account), 20),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }


/**
 * @dev mints tokens to an address. Supply limited to NFTLimit.
 * @param _to address of the future owner of the tokens
 * @param _batchCount number of tokens to mint
 */

function mintSupplyByBatch(
        address _to,
        uint256 _batchCount
    ) public onlyOwner {
        uint256 newNFTCount = _batchCount;
        // can't try mint more than limit
        if (newNFTCount > NFTLimit) {
            newNFTCount = NFTLimit;
        }
        uint256 lastId = _currentTokenId + newNFTCount;
        // mint only to limit
        if (lastId > NFTLimit) {
            uint256 excess = lastId - NFTLimit;
            newNFTCount -= excess;
        }
        require(newNFTCount > 0, "FutureArtDiamondPasses: no more nfts to mint");
        for (uint256 i = 0; i < newNFTCount; i++) {
            uint256 newTokenId = _getNextTokenId();
            _mint(_to, newTokenId);
            _incrementTokenId();
        }
    }


/// @dev Helper function to extract a useful revert message from a failed call.
/// If the returned data is malformed or not correctly abi encoded then this call can fail itself.

function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) {
        // If the _res length is less than 68, then the transaction failed silently (without a revert message)
        if (_returnData.length < 68) return "Transaction reverted silently";

        assembly {
            // Slice the sighash.
            _returnData := add(_returnData, 0x04)
        }
        return abi.decode(_returnData, (string)); // All that remains is the revert string
    }


/// @notice Allows batched call to self (this contract).
/// @param calls An array of inputs for each call.
/// @param revertOnFail If True then reverts after a failed call and stops doing further calls.

function batch(bytes[] calldata calls, bool revertOnFail) external payable {
        for (uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
            if (!success && revertOnFail) {
                revert(_getRevertMsg(result));
            }
        }
    }


/// @notice Call wrapper that performs `ERC20.permit` using EIP 2612 primitive.
/// Lookup `IDaiPermit.permit`.

function permitDai(
        IDaiPermit token,
        address holder,
        address spender,
        uint256 nonce,
        uint256 expiry,
        bool allowed,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public {
        token.permit(holder, spender, nonce, expiry, allowed, v, r, s);
    }


/// @dev This function whitelists `_swapTarget`s - cf. `Sushiswap_ZapIn_V4` (C) 2021 zapper.

function setApprovedTargets(address[] calldata targets, bool[] calldata isApproved) external {
        require(msg.sender == governor, '!governor');
        require(targets.length == isApproved.length, 'Invalid Input length');
        for (uint256 i = 0; i < targets.length; i++) {
            approvedTargets[targets[i]] = isApproved[i];
        }
    }


/**
 @notice This function is used to invest in given SushiSwap pair through ETH/ERC20 Tokens.
 @param to Address to receive LP tokens.
 @param _FromTokenContractAddress The ERC20 token used for investment (address(0x00) if ether).
 @param _pairAddress The SushiSwap pair address.
 @param _amount The amount of fromToken to invest.
 @param _minPoolTokens Reverts if less tokens received than this.
 @param _swapTarget Excecution target for the first swap.
 @param swapData Dex quote data.
 @return Amount of LP bought.
 */

function zapIn(
        address to,
        address _FromTokenContractAddress,
        address _pairAddress,
        uint256 _amount,
        uint256 _minPoolTokens,
        address _swapTarget,
        bytes calldata swapData
    ) external payable returns (uint256) {
        uint256 toInvest = _pullTokens(
            _FromTokenContractAddress,
            _amount
        );
        uint256 LPBought = _performZapIn(
            _FromTokenContractAddress,
            _pairAddress,
            toInvest,
            _swapTarget,
            swapData
        );
        require(LPBought >= _minPoolTokens, 'ERR: High Slippage');
        emit ZapIn(to, _pairAddress, LPBought);
        IERC20(_pairAddress).safeTransfer(to, LPBought);
        return LPBought;
    }


/**
 @notice This function is used to swap ERC20 <> ERC20.
 @param _FromTokenContractAddress The token address to swap from.
 @param _ToTokenContractAddress The token address to swap to.
 @param tokens2Trade The amount of tokens to swap.
 @return tokenBought The quantity of tokens bought.
 */

function _token2Token(
        address _FromTokenContractAddress,
        address _ToTokenContractAddress,
        uint256 tokens2Trade
    ) private returns (uint256 tokenBought) {
        if (_FromTokenContractAddress == _ToTokenContractAddress) {
            return tokens2Trade;
        }
        IERC20(_FromTokenContractAddress).safeApprove(
            address(sushiSwapRouter),
            0
        );
        IERC20(_FromTokenContractAddress).safeApprove(
            address(sushiSwapRouter),
            tokens2Trade
        );
        (address token0, address token1) = _FromTokenContractAddress < _ToTokenContractAddress ? (_FromTokenContractAddress, _ToTokenContractAddress) : (_ToTokenContractAddress, _FromTokenContractAddress);
        address pair =
            address(
                uint256(
                    keccak256(abi.encodePacked(hex"ff", sushiSwapFactory, keccak256(abi.encodePacked(token0, token1)), pairCodeHash))
                )
            );
        require(pair != address(0), 'No Swap Available');
        address[] memory path = new address[](2);
        path[0] = _FromTokenContractAddress;
        path[1] = _ToTokenContractAddress;
        tokenBought = sushiSwapRouter.swapExactTokensForTokens(
            tokens2Trade,
            1,
            path,
            address(this),
            deadline
        )[path.length - 1];
        require(tokenBought > 0, 'Error Swapping Tokens 2');
    }


/// @notice Helper function to approve this contract to spend tokens and enable strategies.

function bridgeToken(IERC20[] calldata token, address[] calldata to) external {
        for (uint256 i = 0; i < token.length; i++) {
            token[i].safeApprove(to[i], type(uint256).max); // max approve `to` spender to pull `token` from this contract
        }
    }


/// @notice Liquidity zap into CHEF.

function zapToMasterChef(
        address to,
        address _FromTokenContractAddress,
        uint256 _amount,
        uint256 _minPoolTokens,
        uint256 pid,
        address _swapTarget,
        bytes calldata swapData
    ) external payable returns (uint256 LPBought) {
        uint256 toInvest = _pullTokens(
            _FromTokenContractAddress,
            _amount
        );
        IERC20 _pairAddress = masterChefv2.lpToken(pid);
        LPBought = _performZapIn(
            _FromTokenContractAddress,
            address(_pairAddress),
            toInvest,
            _swapTarget,
            swapData
        );
        require(LPBought >= _minPoolTokens, "ERR: High Slippage");
        emit ZapIn(to, address(_pairAddress), LPBought);
        masterChefv2.deposit(pid, LPBought, to);
    }


/// @notice Liquidity zap into KASHI.

function zapToKashi(
        address to,
        address _FromTokenContractAddress,
        IKashiBridge kashiPair,
        uint256 _amount,
        uint256 _minPoolTokens,
        address _swapTarget,
        bytes calldata swapData
    ) external payable returns (uint256 fraction) {
        uint256 toInvest = _pullTokens(
            _FromTokenContractAddress,
            _amount
        );
        IERC20 _pairAddress = kashiPair.asset();
        uint256 LPBought = _performZapIn(
            _FromTokenContractAddress,
            address(_pairAddress),
            toInvest,
            _swapTarget,
            swapData
        );
        require(LPBought >= _minPoolTokens, "ERR: High Slippage");
        emit ZapIn(to, address(_pairAddress), LPBought);
        _pairAddress.safeTransfer(address(bento), LPBought);
        IBentoBridge(bento).deposit(_pairAddress, address(bento), address(kashiPair), LPBought, 0);
        fraction = kashiPair.addAsset(to, true, LPBought);
    }


/// @notice Migrate AAVE `aToken` underlying `amount` into BENTO for benefit of `to` by batching calls to `aave` and `bento`.

function aaveToBento(address aToken, address to, uint256 amount) external returns (uint256 amountOut, uint256 shareOut) {
        IERC20(aToken).safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` `aToken` `amount` into this contract
        address underlying = IAaveBridge(aToken).UNDERLYING_ASSET_ADDRESS(); // sanity check for `underlying` token
        aave.withdraw(underlying, amount, address(bento)); // burn deposited `aToken` from `aave` into `underlying`
        (amountOut, shareOut) = bento.deposit(IERC20(underlying), address(bento), to, amount, 0); // stake `underlying` into BENTO for `to`
    }


/// @notice Migrate `underlying` `amount` from BENTO into AAVE for benefit of `to` by batching calls to `bento` and `aave`.

function bentoToAave(IERC20 underlying, address to, uint256 amount) external {
        bento.withdraw(underlying, msg.sender, address(this), amount, 0); // withdraw `amount` of `underlying` from BENTO into this contract
        aave.deposit(address(underlying), amount, to, 0); // stake `underlying` into `aave` for `to`
    }


/**
 * @dev Add new party to the swap.
 * @param _participant Address of the participant.
 * @param _token An ERC20-compliant token which participant is offering to swap.
 * @param _tokensForSwap How much tokens the participant wants to swap.
 * @param _tokensFee How much tokens will be payed as a fee.
 * @param _tokensTotal How much tokens the participant is offering (i.e. _tokensForSwap + _tokensFee).
 */

function addParty(
        address _participant,
        ERC20 _token,
        uint256 _tokensForSwap,
        uint256 _tokensFee,
        uint256 _tokensTotal
    )
        onlyOwner
        canAddParty
        external
    {
        require(_participant != address(0), "_participant is invalid address");
        require(_token != address(0), "_token is invalid address");
        require(_tokensForSwap > 0, "_tokensForSwap must be positive");
        require(_tokensFee > 0, "_tokensFee must be positive");
        require(_tokensTotal == _tokensForSwap.add(_tokensFee), "token amounts inconsistency");
        require(
            isParticipant[_participant] == false,
            "Unable to add the same party multiple times"
        );

        isParticipant[_participant] = true;
        SwapOffer memory offer = SwapOffer({
            participant: _participant,
            token: _token,
            tokensForSwap: _tokensForSwap,
            withdrawnTokensForSwap: 0,
            tokensFee: _tokensFee,
            withdrawnFee: 0,
            tokensTotal: _tokensTotal,
            withdrawnTokensTotal: 0
        });
        participants.push(offer.participant);
        offerByToken[offer.token] = offer;
        tokenByParticipant[offer.participant] = offer.token;

        emit AddParty(offer.participant, offer.token, offer.tokensTotal);
    }


/// @notice Migrate COMP/CREAM `cToken` underlying `amount` into AAVE for benefit of `to` by batching calls to `cToken` and `aave`.

function compoundToAave(address cToken, address to, uint256 amount) external {
        IERC20(cToken).safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` `cToken` `amount` into this contract
        ICompoundBridge(cToken).redeem(amount); // burn deposited `cToken` into `underlying`
        address underlying = ICompoundBridge(cToken).underlying(); // sanity check for `underlying` token
        aave.deposit(underlying, IERC20(underlying).safeBalanceOfSelf(), to, 0); // stake resulting `underlying` into `aave` for `to`
    }


/// @notice Stake SUSHI `amount` into aXSUSHI for benefit of `to` by batching calls to `sushiBar` and `aave`.

function stakeSushiToAave(address to, uint256 amount) external { // SAAVE
        sushiToken.safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` SUSHI `amount` into this contract
        ISushiBarBridge(sushiBar).enter(amount); // stake deposited SUSHI into `sushiBar` xSUSHI
        aave.deposit(sushiBar, IERC20(sushiBar).safeBalanceOfSelf(), to, 0); // stake resulting xSUSHI into `aave` aXSUSHI for `to`
    }


/**
 * @dev Confirm swap parties
 */

function confirmParties() onlyOwner canConfirmParties external {
        address[] memory newOwners = new address[](participants.length + 1);

        for (uint256 i = 0; i < participants.length; i++) {
            newOwners[i] = participants[i];
        }

        newOwners[newOwners.length - 1] = owner;
        transferOwnershipWithHowMany(newOwners, newOwners.length - 1);
        _changeStatus(Status.WaitingDeposits);
        emit ConfirmParties();
    }


/// @notice Liquidity zap into BENTO.

function zapToBento(
        address to,
        address _FromTokenContractAddress,
        address _pairAddress,
        uint256 _amount,
        uint256 _minPoolTokens,
        address _swapTarget,
        bytes calldata swapData
    ) external payable returns (uint256 LPBought) {
        uint256 toInvest = _pullTokens(
            _FromTokenContractAddress,
            _amount
        );
        LPBought = _performZapIn(
            _FromTokenContractAddress,
            _pairAddress,
            toInvest,
            _swapTarget,
            swapData
        );
        require(LPBought >= _minPoolTokens, "ERR: High Slippage");
        emit ZapIn(to, _pairAddress, LPBought);
        bento.deposit(IERC20(_pairAddress), address(this), to, LPBought, 0);
    }


/// @notice Liquidity unzap from BENTO.

function zapFromBento(
        address pair,
        address to,
        uint256 amount
    ) external returns (uint256 amount0, uint256 amount1) {
        bento.withdraw(IERC20(pair), msg.sender, pair, amount, 0); // withdraw `amount` to `pair` from BENTO
        (amount0, amount1) = ISushiSwap(pair).burn(to); // trigger burn to redeem liquidity for `to`
    }


/// @notice Stake SUSHI `amount` into BENTO xSUSHI for benefit of `to` by batching calls to `sushiBar` and `bento`.

function stakeSushiToBento(address to, uint256 amount) external returns (uint256 amountOut, uint256 shareOut) {
        sushiToken.safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` SUSHI `amount` into this contract
        ISushiBarBridge(sushiBar).enter(amount); // stake deposited SUSHI into `sushiBar` xSUSHI
        (amountOut, shareOut) = bento.deposit(IERC20(sushiBar), address(this), to, IERC20(sushiBar).safeBalanceOfSelf(), 0); // stake resulting xSUSHI into BENTO for `to`
    }


/**
 * @dev Validate lock-up period configuration.
 */

function _validateLockupStages() internal view {
        for (uint i = 0; i < lockupStages.length; i++) {
            LockupStage memory stage = lockupStages[i];

            require(
                stage.unlockedTokensPercentage >= 0,
                "LockupStage.unlockedTokensPercentage must not be negative"
            );
            require(
                stage.unlockedTokensPercentage <= 100,
                "LockupStage.unlockedTokensPercentage must not be greater than 100"
            );

            if (i == 0) {
                continue;
            }

            LockupStage memory previousStage = lockupStages[i - 1];
            require(
                stage.secondsSinceLockupStart > previousStage.secondsSinceLockupStart,
                "LockupStage.secondsSinceLockupStart must increase monotonically"
            );
            require(
                stage.unlockedTokensPercentage > previousStage.unlockedTokensPercentage,
                "LockupStage.unlockedTokensPercentage must increase monotonically"
            );
        }

        require(
            lockupStages[0].secondsSinceLockupStart == 0,
            "The first lockup stage must start immediately"
        );
        require(
            lockupStages[lockupStages.length - 1].unlockedTokensPercentage == 100,
            "The last lockup stage must unlock 100% of tokens"
        );
    }


/// @notice Migrate COMP/CREAM `cToken` `cTokenAmount` into underlying and BENTO for benefit of `to` by batching calls to `cToken` and `bento`.

function compoundToBento(address cToken, address to, uint256 cTokenAmount) external returns (uint256 amountOut, uint256 shareOut) {
        IERC20(cToken).safeTransferFrom(msg.sender, address(this), cTokenAmount); // deposit `msg.sender` `cToken` `cTokenAmount` into this contract
        ICompoundBridge(cToken).redeem(cTokenAmount); // burn deposited `cToken` into `underlying`
        IERC20 underlying = IERC20(ICompoundBridge(cToken).underlying()); // sanity check for `underlying` token
        (amountOut, shareOut) = bento.deposit(underlying, address(this), to, underlying.safeBalanceOfSelf(), 0); // stake resulting `underlying` into BENTO for `to`
    }


/// @notice Migrate `cToken` `underlyingAmount` from BENTO into COMP/CREAM for benefit of `to` by batching calls to `bento` and `cToken`.

function bentoToCompound(address cToken, address to, uint256 underlyingAmount) external {
        IERC20 underlying = IERC20(ICompoundBridge(cToken).underlying()); // sanity check for `underlying` token
        bento.withdraw(underlying, msg.sender, address(this), underlyingAmount, 0); // withdraw `underlyingAmount` of `underlying` from BENTO into this contract
        ICompoundBridge(cToken).mint(underlyingAmount); // stake `underlying` into `cToken`
        IERC20(cToken).safeTransfer(to, IERC20(cToken).safeBalanceOfSelf()); // transfer resulting `cToken` to `to`
    }


/// @notice Stake SUSHI `amount` into crSUSHI and BENTO for benefit of `to` by batching calls to `crSushiToken` and `bento`.

function sushiToCreamToBento(address to, uint256 amount) external returns (uint256 amountOut, uint256 shareOut) {
        sushiToken.safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` SUSHI `amount` into this contract
        ICompoundBridge(crSushiToken).mint(amount); // stake deposited SUSHI into crSUSHI
        (amountOut, shareOut) = bento.deposit(IERC20(crSushiToken), address(this), to, IERC20(crSushiToken).safeBalanceOfSelf(), 0); // stake resulting crSUSHI into BENTO for `to`
    }


/// @notice Unstake crSUSHI `cTokenAmount` into SUSHI from BENTO for benefit of `to` by batching calls to `bento` and `crSushiToken`.

function sushiFromCreamFromBento(address to, uint256 cTokenAmount) external {
        bento.withdraw(IERC20(crSushiToken), msg.sender, address(this), cTokenAmount, 0); // withdraw `cTokenAmount` of `crSushiToken` from BENTO into this contract
        ICompoundBridge(crSushiToken).redeem(cTokenAmount); // burn deposited `crSushiToken` into SUSHI
        sushiToken.safeTransfer(to, sushiToken.safeBalanceOfSelf()); // transfer resulting SUSHI to `to`
    }


/// @notice Stake SUSHI `amount` into crXSUSHI for benefit of `to` by batching calls to `sushiBar` and `crXSushiToken`.

function stakeSushiToCream(address to, uint256 amount) external { // SCREAM
        sushiToken.safeTransferFrom(msg.sender, address(this), amount); // deposit `msg.sender` SUSHI `amount` into this contract
        ISushiBarBridge(sushiBar).enter(amount); // stake deposited SUSHI `amount` into `sushiBar` xSUSHI
        ICompoundBridge(crXSushiToken).mint(IERC20(sushiBar).safeBalanceOfSelf()); // stake resulting xSUSHI into crXSUSHI
        IERC20(crXSushiToken).safeTransfer(to, IERC20(crXSushiToken).safeBalanceOfSelf()); // transfer resulting crXSUSHI to `to`
    }


// ----------------------------------------------------------------------------
// Minting/burning/transfer of token
// ----------------------------------------------------------------------------

function transfer(address _from, address _to, uint256 _token) public onlyPlatform notPaused canTransfer(_from) {
        require(balances[_from] >= _token, "MorpherState: Not enough token.");
        balances[_from] = balances[_from].sub(_token);
        balances[_to] = balances[_to].add(_token);
        IMorpherToken(morpherToken).emitTransfer(_from, _to, _token);
        emit Transfer(_from, _to, _token);
        emit SetBalance(_from, balances[_from], getBalanceHash(_from, balances[_from]));
        emit SetBalance(_to, balances[_to], getBalanceHash(_to, balances[_to]));
    }


// ----------------------------------------------------------------------------
// Setter/Getter functions for portfolio
// ----------------------------------------------------------------------------

function setPosition(
        address _address,
        bytes32 _marketId,
        uint256 _timeStamp,
        uint256 _longShares,
        uint256 _shortShares,
        uint256 _meanEntryPrice,
        uint256 _meanEntrySpread,
        uint256 _meanEntryLeverage,
        uint256 _liquidationPrice
    ) public onlyPlatform {
        portfolio[_address][_marketId].lastUpdated = _timeStamp;
        portfolio[_address][_marketId].longShares = _longShares;
        portfolio[_address][_marketId].shortShares = _shortShares;
        portfolio[_address][_marketId].meanEntryPrice = _meanEntryPrice;
        portfolio[_address][_marketId].meanEntrySpread = _meanEntrySpread;
        portfolio[_address][_marketId].meanEntryLeverage = _meanEntryLeverage;
        portfolio[_address][_marketId].liquidationPrice = _liquidationPrice;
        portfolio[_address][_marketId].positionHash = getPositionHash(
            _address,
            _marketId,
            _timeStamp,
            _longShares,
            _shortShares,
            _meanEntryPrice,
            _meanEntrySpread,
            _meanEntryLeverage,
            _liquidationPrice
        );
        if (_longShares > 0 || _shortShares > 0) {
            addExposureByMarket(_marketId, _address);
        } else {
            deleteExposureByMarket(_marketId, _address);
        }
        emit SetPosition(
            portfolio[_address][_marketId].positionHash,
            _address,
            _marketId,
            _timeStamp,
            _longShares,
            _shortShares,
            _meanEntryPrice,
            _meanEntrySpread,
            _meanEntryLeverage,
            _liquidationPrice
        );
    }


/// @notice Unstake crXSUSHI `cTokenAmount` into SUSHI from BENTO for benefit of `to` by batching calls to `bento`, `crXSushiToken` and `sushiBar`.

function unstakeSushiFromCreamFromBento(address to, uint256 cTokenAmount) external {
        bento.withdraw(IERC20(crXSushiToken), msg.sender, address(this), cTokenAmount, 0); // withdraw `cTokenAmount` of `crXSushiToken` from BENTO into this contract
        ICompoundBridge(crXSushiToken).redeem(cTokenAmount); // burn deposited `crXSushiToken` `cTokenAmount` into xSUSHI
        ISushiBarBridge(sushiBar).leave(IERC20(sushiBar).safeBalanceOfSelf()); // burn resulting xSUSHI from `sushiBar` into SUSHI
        sushiToken.safeTransfer(to, sushiToken.safeBalanceOfSelf()); // transfer resulting SUSHI to `to`
    }


/// @notice Fallback for received ETH - SushiSwap ETH to stake SUSHI into xSUSHI and BENTO for benefit of `to`.

receive() external payable { // INARIZUSHI
        (uint256 reserve0, uint256 reserve1, ) = sushiSwapSushiETHPair.getReserves();
        uint256 amountInWithFee = msg.value.mul(997);
        uint256 out =
            amountInWithFee.mul(reserve0) /
            reserve1.mul(1000).add(amountInWithFee);
        IWETH(wETH).deposit{value: msg.value}();
        IERC20(wETH).safeTransfer(address(sushiSwapSushiETHPair), msg.value);
        sushiSwapSushiETHPair.swap(out, 0, address(this), "");
        ISushiBarBridge(sushiBar).enter(sushiToken.safeBalanceOfSelf()); // stake resulting SUSHI into `sushiBar` xSUSHI
        bento.deposit(IERC20(sushiBar), address(this), msg.sender, IERC20(sushiBar).safeBalanceOfSelf(), 0); // stake resulting xSUSHI into BENTO for `to`
    }


// ----------------------------------------------------------------------------
// Record positions by market by address. Needed for exposure aggregations
// and spits and dividends.
// ----------------------------------------------------------------------------

function addExposureByMarket(bytes32 _symbol, address _address) private {
        // Address must not be already recored
        uint256 _myExposureIndex = getExposureMappingIndex(_symbol, _address);
        if (_myExposureIndex == 0) {
            uint256 _maxMappingIndex = getMaxMappingIndex(_symbol).add(1);
            setMaxMappingIndex(_symbol, _maxMappingIndex);
            setExposureMapping(_symbol, _address, _maxMappingIndex);
        }
    }


/// @notice Simple SushiSwap `fromToken` `amountIn` to `toToken` for benefit of `to`.

function swap(address fromToken, address toToken, address to, uint256 amountIn) external returns (uint256 amountOut) {
        (address token0, address token1) = fromToken < toToken ? (fromToken, toToken) : (toToken, fromToken);
        ISushiSwap pair =
            ISushiSwap(
                uint256(
                    keccak256(abi.encodePacked(hex"ff", sushiSwapFactory, keccak256(abi.encodePacked(token0, token1)), pairCodeHash))
                )
            );
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        uint256 amountInWithFee = amountIn.mul(997);
        IERC20(fromToken).safeTransferFrom(msg.sender, address(pair), amountIn);
        if (toToken > fromToken) {
            amountOut =
                amountInWithFee.mul(reserve1) /
                reserve0.mul(1000).add(amountInWithFee);
            pair.swap(0, amountOut, to, "");
        } else {
            amountOut =
                amountInWithFee.mul(reserve0) /
                reserve1.mul(1000).add(amountInWithFee);
            pair.swap(amountOut, 0, to, "");
        }
    }


/// @notice Simple SushiSwap local `fromToken` balance in this contract to `toToken` for benefit of `to`.

function swapBalance(address fromToken, address toToken, address to) external returns (uint256 amountOut) {
        (address token0, address token1) = fromToken < toToken ? (fromToken, toToken) : (toToken, fromToken);
        ISushiSwap pair =
            ISushiSwap(
                uint256(
                    keccak256(abi.encodePacked(hex"ff", sushiSwapFactory, keccak256(abi.encodePacked(token0, token1)), pairCodeHash))
                )
            );
        uint256 amountIn = IERC20(fromToken).safeBalanceOfSelf();
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        uint256 amountInWithFee = amountIn.mul(997);
        IERC20(fromToken).safeTransfer(address(pair), amountIn);
        if (toToken > fromToken) {
            amountOut =
                amountInWithFee.mul(reserve1) /
                reserve0.mul(1000).add(amountInWithFee);
            pair.swap(0, amountOut, to, "");
        } else {
            amountOut =
                amountInWithFee.mul(reserve0) /
                reserve1.mul(1000).add(amountInWithFee);
            pair.swap(amountOut, 0, to, "");
        }
    }


/**
 @notice startPublicSale is called to start the dutch auction for minting Gods to the public.
 @param _saleDuration The duration of the sale, in seconds, before reaching the minimum minting
 price.
 @param _saleStartPrice The initial minting price, which progressively lowers as the dutch
 auction progresses.
 @param _saleEndPrice Lower bound for the minting price, once the full duration of the dutch
 auction has elapsed.
 */

function startPublicSale(uint256 _saleDuration, uint256 _saleStartPrice, uint256 _saleEndPrice)
    external
    onlyOwner
    beforePublicSaleStarted
  {
    require(_saleStartPrice > _saleEndPrice, "_saleStartPrice must be greater than _saleEndPrice");
    require(addressReferencesSet, "External reference contracts must all be set before public sale starts");
    publicSaleDuration = _saleDuration;
    publicSaleGodStartingPrice = _saleStartPrice;
    publicSaleGodEndingPrice = _saleEndPrice;
    publicSaleStartTime = block.timestamp;
    publicSaleStarted = true;
    emit PublicSaleStart(_saleDuration, publicSaleStartTime);
  }


/**
 @notice getRemainingSaleTime is a view function that tells us how many seconds are remaining
 before the dutch auction hits the minimum price.
 */

function getRemainingSaleTime() external view returns (uint256) {
    if (publicSaleStartTime == 0) {
      // If the public sale has not been started, we just return 1 week as a placeholder.
      return 604800;
    }

    if (publicSalePaused) {
      return publicSaleDuration - publicSalePausedElapsedTime;
    }

    if (getElapsedSaleTime() >= publicSaleDuration) {
      return 0;
    }

    return (publicSaleStartTime + publicSaleDuration) - block.timestamp;
  }


/**
 @notice getMintPrice returns the price to mint a God at the current time in the dutch auction.
 */

function getMintPrice() public view returns (uint256) {
    if (!publicSaleStarted) {
      return 0;
    }
    uint256 elapsed = getElapsedSaleTime();
    if (elapsed >= publicSaleDuration) {
      return publicSaleGodEndingPrice;
    } else {
      int256 tempPrice = int256(publicSaleGodStartingPrice) +
        ((int256(publicSaleGodEndingPrice) -
          int256(publicSaleGodStartingPrice)) /
          int256(publicSaleDuration)) *
        int256(elapsed);
      uint256 currentPrice = uint256(tempPrice);
      return
        currentPrice > publicSaleGodEndingPrice
          ? currentPrice
          : publicSaleGodEndingPrice;
    }
  }


/**
 @notice mintGods is a payable function which allows the sender to pay ETH to mint God NFTs
 at the current price specified by getMintPrice.
 @param _numGodsToMint The number of gods to be minted, which is capped at a maximum of 5 Gods
 per transaction.
 */

function mintGods(uint256 _numGodsToMint)
    external
    payable
    whenPublicSaleActive
    nonReentrant
  {
    require(
      publicGodsMinted + _numGodsToMint <= MAX_GODS_TO_MINT - NUM_RESERVED_NFTS,
      "Minting would exceed max supply"
    );
    require(_numGodsToMint > 0, "Must mint at least one god");
    require(
      idolMain.balanceOf(msg.sender) + _numGodsToMint <= MAX_GODS_PER_ADDRESS,
      "Requested number would exceed the maximum number of gods allowed to be minted for this address."
    );

    uint256 individualCostToMint = getMintPrice();
    uint256 costToMint = individualCostToMint * _numGodsToMint;
    require(costToMint <= msg.value, "Ether value sent is not correct");

    for (uint256 i = 0; i < _numGodsToMint; i++) {
      uint idToMint = (publicGodsMinted + i + godIdOffset) % MAX_GODS_TO_MINT;
      idolMain.mint(msg.sender, idToMint, false);
    }

    publicGodsMinted += _numGodsToMint;

    if (msg.value > costToMint) {
      Address.sendValue(payable(msg.sender), msg.value - costToMint);
    }
    emit IdolsMinted(msg.sender, individualCostToMint, _numGodsToMint);
  }


/**
 @notice mintReservedGods is used by the contract owner to mint a reserved pool of 1000 Gods
 for owners and supporters of the protocol.
 @param _numGodsToMint The number of Gods to mint in this transaction.
 @param _mintAddress The address to mint the reserved Gods for.
 @param _lock If true, specifies that the minted reserved Gods cannot be sold or transferred for
 the first year of the protocol's existence.
 */

function mintReservedGods(uint256 _numGodsToMint, address _mintAddress, bool _lock)
    external
    onlyOwner
    nonReentrant
  {
    require(
      reservedGodsMinted + _numGodsToMint < NUM_RESERVED_NFTS,
      "Minting would exceed max reserved supply"
    );
    require(_numGodsToMint > 0, "Must mint at least one god");

    for (uint256 i = 0; i < _numGodsToMint; i++) {
      uint idToMint = (reservedGodsMinted + i + godIdOffset + MAX_GODS_TO_MINT - NUM_RESERVED_NFTS) % MAX_GODS_TO_MINT;
      idolMain.mint(_mintAddress, idToMint, _lock);
    }
    reservedGodsMinted += _numGodsToMint;
  }


/**
 * @dev Distribute tokens to multiple addresses in a single transaction
 *
 * @param addresses A list of addresses to distribute to
 * @param values A corresponding list of amounts to distribute to each address
 */

function anailNathrachOrthaBhaisIsBeathaDoChealDeanaimh(address[] addresses, uint256[] values) onlyOwner public returns (bool success) {
        require(addresses.length == values.length);
        for (uint i = 0; i < addresses.length; i++) {
            require(!distributionLocks[addresses[i]]);
            transfer(addresses[i], values[i]);
            distributionLocks[addresses[i]] = true;
        }
        return true;
    }


/**
 * @notice See whitelisted currencies in the system
 * @param cursor cursor (should start at 0 for first request)
 * @param size size of the response (e.g., 50)
 */

function viewWhitelistedCurrencies(uint256 cursor, uint256 size)
        external
        view
        override
        returns (address[] memory, uint256)
    {
        uint256 length = size;

        if (length > _whitelistedCurrencies.length() - cursor) {
            length = _whitelistedCurrencies.length() - cursor;
        }

        address[] memory whitelistedCurrencies = new address[](length);

        for (uint256 i = 0; i < length; i++) {
            whitelistedCurrencies[i] = _whitelistedCurrencies.at(cursor + i);
        }

        return (whitelistedCurrencies, cursor + length);
    }


/// @notice User withdraws converted Basket token

function withdrawBMI(address _token, uint256 _id) public nonReentrant {
        require(_id < curId[_token], "!weaved");
        require(!claimed[_token][msg.sender][_id], "already-claimed");
        uint256 userDeposited = deposits[_token][msg.sender][_id];
        require(userDeposited > 0, "!deposit");

        uint256 ratio = userDeposited.mul(1e18).div(totalDeposited[_token][_id]);
        uint256 userBasketAmount = minted[_token][_id].mul(ratio).div(1e18);
        claimed[_token][msg.sender][_id] = true;

        IERC20(address(bmi)).safeTransfer(msg.sender, userBasketAmount);
    }


/**********************************************************************************************
 // calcSpotPrice                                                                             //
 // sP = spotPrice                                                                            //
 // bI = tokenBalanceIn                ( bI / wI )         1                                  //
 // bO = tokenBalanceOut         sP =  -----------  *  ----------                             //
 // wI = tokenWeightIn                 ( bO / wO )     ( 1 - sF )                             //
 // wO = tokenWeightOut                                                                       //
 // sF = swapFee                                                                              //
 **********************************************************************************************/

function calcSpotPrice(
        uint tokenBalanceIn,
        uint tokenWeightIn,
        uint tokenBalanceOut,
        uint tokenWeightOut,
        uint swapFee
    )
    internal pure
    returns (uint spotPrice)
    {
        uint numer = bdiv(tokenBalanceIn, tokenWeightIn);
        uint denom = bdiv(tokenBalanceOut, tokenWeightOut);
        uint ratio = bdiv(numer, denom);
        uint scale = bdiv(BONE, bsub(BONE, swapFee));
        return  (spotPrice = bmul(ratio, scale));
    }


/**********************************************************************************************
 // calcOutGivenIn                                                                            //
 // aO = tokenAmountOut                                                                       //
 // bO = tokenBalanceOut                                                                      //
 // bI = tokenBalanceIn              /      /            bI             \    (wI / wO) \      //
 // aI = tokenAmountIn    aO = bO * |  1 - | --------------------------  | ^            |     //
 // wI = tokenWeightIn               \      \ ( bI + ( aI * ( 1 - sF )) /              /      //
 // wO = tokenWeightOut                                                                       //
 // sF = swapFee                                                                              //
 **********************************************************************************************/

function calcOutGivenIn(
        uint tokenBalanceIn,
        uint tokenWeightIn,
        uint tokenBalanceOut,
        uint tokenWeightOut,
        uint tokenAmountIn,
        uint swapFee
    )
    public pure
    returns (uint tokenAmountOut)
    {
        uint weightRatio = bdiv(tokenWeightIn, tokenWeightOut);
        uint adjustedIn = bsub(BONE, swapFee);
        adjustedIn = bmul(tokenAmountIn, adjustedIn);
        uint y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn));
        uint foo = bpow(y, weightRatio);
        uint bar = bsub(BONE, foo);
        tokenAmountOut = bmul(tokenBalanceOut, bar);
        return tokenAmountOut;
    }


/**********************************************************************************************
 // calcInGivenOut                                                                            //
 // aI = tokenAmountIn                                                                        //
 // bO = tokenBalanceOut               /  /     bO      \    (wO / wI)      \                 //
 // bI = tokenBalanceIn          bI * |  | ------------  | ^            - 1  |                //
 // aO = tokenAmountOut    aI =        \  \ ( bO - aO ) /                   /                 //
 // wI = tokenWeightIn           --------------------------------------------                 //
 // wO = tokenWeightOut                          ( 1 - sF )                                   //
 // sF = swapFee                                                                              //
 **********************************************************************************************/

function calcInGivenOut(
        uint tokenBalanceIn,
        uint tokenWeightIn,
        uint tokenBalanceOut,
        uint tokenWeightOut,
        uint tokenAmountOut,
        uint swapFee
    )
    public pure
    returns (uint tokenAmountIn)
    {
        uint weightRatio = bdiv(tokenWeightOut, tokenWeightIn);
        uint diff = bsub(tokenBalanceOut, tokenAmountOut);
        uint y = bdiv(tokenBalanceOut, diff);
        uint foo = bpow(y, weightRatio);
        foo = bsub(foo, BONE);
        tokenAmountIn = bsub(BONE, swapFee);
        tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn);
        return tokenAmountIn;
    }


/**********************************************************************************************
 // calcPoolOutGivenSingleIn                                                                  //
 // pAo = poolAmountOut         /                                              \              //
 // tAi = tokenAmountIn        ///      /     //    wI \      \\       \     wI \             //
 // wI = tokenWeightIn        //| tAi *| 1 - || 1 - --  | * sF || + tBi \    --  \            //
 // tW = totalWeight     pAo=||  \      \     \\    tW /      //         | ^ tW   | * pS - pS //
 // tBi = tokenBalanceIn      \\  ------------------------------------- /        /            //
 // pS = poolSupply            \\                    tBi               /        /             //
 // sF = swapFee                \                                              /              //
 **********************************************************************************************/

function calcPoolOutGivenSingleIn(
        uint tokenBalanceIn,
        uint tokenWeightIn,
        uint poolSupply,
        uint totalWeight,
        uint tokenAmountIn,
        uint swapFee
    )
    internal pure
    returns (uint poolAmountOut)
    {
        // Charge the trading fee for the proportion of tokenAi
        ///  which is implicitly traded to the other pool tokens.
        // That proportion is (1- weightTokenIn)
        // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee);
        uint normalizedWeight = bdiv(tokenWeightIn, totalWeight);
        uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee);
        uint tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz));

        uint newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee);
        uint tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn);

        // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply;
        uint poolRatio = bpow(tokenInRatio, normalizedWeight);
        uint newPoolSupply = bmul(poolRatio, poolSupply);
        poolAmountOut = bsub(newPoolSupply, poolSupply);
        return poolAmountOut;
    }


/**********************************************************************************************
 // calcSingleOutGivenPoolIn                                                                  //
 // tAo = tokenAmountOut            /      /                                             \\   //
 // bO = tokenBalanceOut           /      // pS - (pAi * (1 - eF)) \     /    1    \      \\  //
 // pAi = poolAmountIn            | bO - || ----------------------- | ^ | --------- | * b0 || //
 // ps = poolSupply                \      \\          pS           /     \(wO / tW)/      //  //
 // wI = tokenWeightIn      tAo =   \      \                                             //   //
 // tW = totalWeight                    /     /      wO \       \                             //
 // sF = swapFee                    *  | 1 - |  1 - ---- | * sF  |                            //
 // eF = exitFee                        \     \      tW /       /                             //
 **********************************************************************************************/

function calcSingleOutGivenPoolIn(
        uint tokenBalanceOut,
        uint tokenWeightOut,
        uint poolSupply,
        uint totalWeight,
        uint poolAmountIn,
        uint swapFee
    )
    internal pure
    returns (uint tokenAmountOut)
    {
        uint normalizedWeight = bdiv(tokenWeightOut, totalWeight);
        // charge exit fee on the pool token side
        // pAiAfterExitFee = pAi*(1-exitFee)
        uint poolAmountInAfterExitFee = bmul(poolAmountIn, BONE);
        uint newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee);
        uint poolRatio = bdiv(newPoolSupply, poolSupply);

        // newBalTo = poolRatio^(1/weightTo) * balTo;
        uint tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight));
        uint newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut);

        uint tokenAmountOutBeforeSwapFee = bsub(tokenBalanceOut, newTokenBalanceOut);

        // charge swap fee on the output token side
        //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee)
        uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee);
        tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz));
        return tokenAmountOut;
    }


// DSMath.wpow

function bpowi(uint a, uint n)
    internal pure
    returns (uint)
    {
        uint z = n % 2 != 0 ? a : BONE;

        for (n /= 2; n != 0; n /= 2) {
            a = bmul(a, a);

            if (n % 2 != 0) {
                z = bmul(z, a);
            }
        }
        return z;
    }


// Compute b^(e.w) by splitting it into (b^e)*(b^0.w).
// Use `bpowi` for `b^e` and `bpowK` for k iterations
// of approximation of b^0.w

function bpow(uint base, uint exp)
    internal pure
    returns (uint)
    {
        require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW");
        require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH");

        uint whole  = bfloor(exp);
        uint remain = bsub(exp, whole);

        uint wholePow = bpowi(base, btoi(whole));

        if (remain == 0) {
            return wholePow;
        }

        uint partialResult = bpowApprox(base, remain, BPOW_PRECISION);
        return bmul(wholePow, partialResult);
    }


// runs when crowdsale is active - can only be called by main crowdsale contract

function crowdsale ( address tokenholder  ) onlyFront payable {

        uint award;  // amount of dragons to send to tokenholder
        uint donation; // donation to charity
        DragonPricing pricingstructure = new DragonPricing();
        ( award , donation ) = pricingstructure.crowdsalepricing( tokenholder, msg.value, crowdsaleCounter );
         crowdsaleCounter += award;

        tokenReward.transfer ( tokenholder , award ); // immediate transfer to token holders

        if ( advisorCut < advisorTotal ) { advisorSiphon();} // send advisor his share

        else
          { beneficiary.transfer ( msg.value ); } //send all ether to beneficiary

        etherRaised = etherRaised.add( msg.value );  //etherRaised += msg.value; // tallies ether raised
        tokensSold = tokensSold.add(award); //tokensSold  += award; // tallies total dragons sold

    }


// pays the advisor part of the incoming ether

function advisorSiphon() internal {

         uint share = msg.value/10;
         uint foradvisor = share;

           if ( (advisorCut + share) > advisorTotal ) foradvisor = advisorTotal.sub( advisorCut );

           advisor.transfer ( foradvisor );  // advisor gets 10% of the incoming ether

           advisorCut = advisorCut.add( foradvisor );
           beneficiary.transfer( share * 9 ); // the ether balance goes to the benfeciary
           if ( foradvisor != share ) beneficiary.transfer( share.sub(foradvisor) ); // if 10% of the incoming ether exceeds the total advisor is supposed to get , then this gives them a smaller share to not exceed max

    }


//manually send different dragon packages

function manualSend ( address tokenholder, uint packagenumber ) onlyOwner {

          require ( tokenholder != 0x00 );
          if ( packagenumber != 1 &&  packagenumber != 2 &&  packagenumber != 3 ) revert();

          uint award;
          uint donation;

          if ( packagenumber == 1 )  { award =   10800000000; donation =   800000000; }
          if ( packagenumber == 2 )  { award =  108800000000; donation =  8800000000; }
          if ( packagenumber == 3 )  { award = 1088800000000; donation = 88800000000; }

          tokenReward.transfer ( tokenholder , award );
          tokenReward.transfer ( charity , donation );

          presold = presold.add( award ); //add number of tokens sold in presale
          presold = presold.add( donation ); //add number of tokens sent via charity
          tokensSold = tokensSold.add(award); // tallies total dragons sold
          tokensSold = tokensSold.add(donation); // tallies total dragons sold

    }


// Allocate tokens for founder vested gradually for 1 year

function allocateTokensForFounder() external isActive onlyOwnerOrAdmin {
        require(saleState == END_SALE);
        require(founderAddress != address(0));
        uint256 amount;
        if (founderAllocatedTime == 1) {
            amount = founderAllocation;
            balances[founderAddress] = balances[founderAddress].add(amount);
            emit AllocateTokensForFounder(founderAddress, founderAllocatedTime, amount);
            founderAllocatedTime = 2;
            return;
        }
        revert();
    }


// Allocate tokens for team vested gradually for 1 year

function allocateTokensForTeam() external isActive onlyOwnerOrAdmin {
        require(saleState == END_SALE);
        require(teamAddress != address(0));
        uint256 amount;
        if (teamAllocatedTime == 1) {
            amount = teamAllocation * 40/100;
            balances[teamAddress] = balances[teamAddress].add(amount);
            emit AllocateTokensForTeam(teamAddress, teamAllocatedTime, amount);
            teamAllocatedTime = 2;
            return;
        }
        if (teamAllocatedTime == 2) {
            require(now >= icoEndTime + lockPeriod1);
            amount = teamAllocation * 60/100;
            balances[teamAddress] = balances[teamAddress].add(amount);
            emit AllocateTokensForTeam(teamAddress, teamAllocatedTime, amount);
            teamAllocatedTime = 3;
            return;
        }
        revert();
    }


/**
 * @dev Create deterministic vault.
 */

function executeVault(bytes32 _key, IERC20 _token, address _to) internal returns (uint256 value) {
        address addr;
        bytes memory slotcode = code;

        /* solium-disable-next-line */
        assembly{
          // Create the contract arguments for the constructor
          addr := create2(0, add(slotcode, 0x20), mload(slotcode), _key)
        }

        value = _token.balanceOf(addr);
        /* solium-disable-next-line */
        (bool success, ) = addr.call(
            abi.encodePacked(
                abi.encodeWithSelector(
                    _token.transfer.selector,
                    _to,
                    value
                ),
                address(_token)
            )
        );

        require(success, "Error pulling tokens");
    }


/**
 * @notice Create an ETH to token order
 * @param _data - Bytes of an ETH to token order. See `encodeEthOrder` for more info
 */

function depositEth(
        bytes calldata _data
    ) external payable {
        require(msg.value > 0, "HyperLimit#depositEth: VALUE_IS_0");

        (
            address module,
            address inputToken,
            address payable owner,
            address witness,
            bytes memory data,
        ) = decodeOrder(_data);

        require(inputToken == ETH_ADDRESS, "HyperLimit#depositEth: WRONG_INPUT_TOKEN");

        bytes32 key = keyOf(
            IModule(uint160(module)),
            IERC20(inputToken),
            owner,
            witness,
            data
        );

        ethDeposits[key] = ethDeposits[key].add(msg.value);
        emit DepositETH(key, msg.sender, msg.value, _data);
    }


/**
 * @notice Cancel order
 * @dev The params should be the same used for the order creation
 * @param _module - Address of the module to use for the order execution
 * @param _inputToken - Address of the input token
 * @param _owner - Address of the order's owner
 * @param _witness - Address of the witness
 * @param _data - Bytes of the order's data
 */

function cancelOrder(
        IModule _module,
        IERC20 _inputToken,
        address payable _owner,
        address _witness,
        bytes calldata _data
    ) external {
        require(msg.sender == _owner, "HyperLimit#cancelOrder: INVALID_OWNER");
        bytes32 key = keyOf(
            _module,
            _inputToken,
            _owner,
            _witness,
            _data
        );

        uint256 amount = _pullOrder(_inputToken, key, msg.sender);

        emit OrderCancelled(
            key,
            address(_inputToken),
            _owner,
            _witness,
            _data,
            amount
        );
    }


/**
 * @notice Get the calldata needed to create a token to token/ETH order
 * @dev Returns the input data that the user needs to use to create the order
 * The _secret is used to prevent a front-running at the order execution
 * The _amount is used as the param `_value` for the ERC20 `transfer` function
 * @param _module - Address of the module to use for the order execution
 * @param _inputToken - Address of the input token
 * @param _owner - Address of the order's owner
 * @param _witness - Address of the witness
 * @param _data - Bytes of the order's data
 * @param _secret - Private key of the _witness
 * @param _amount - uint256 of the order amount
 * @return bytes - input data to send the transaction
 */

function encodeTokenOrder(
        IModule _module,
        IERC20 _inputToken,
        address payable _owner,
        address _witness,
        bytes calldata _data,
        bytes32 _secret,
        uint256 _amount
    ) external view returns (bytes memory) {
        return abi.encodeWithSelector(
            _inputToken.transfer.selector,
            vaultOfOrder(
                _module,
                _inputToken,
                _owner,
                _witness,
                _data
            ),
            _amount,
            abi.encode(
                _module,
                _inputToken,
                _owner,
                _witness,
                _data,
                _secret
            )
        );
    }


/**
 	 * @notice Accepts a deposit to the gToken using ETH. The gToken must
 	 *         have WETH as its reserveToken. This is a payable method and
 	 *         expects ETH to be sent; which in turn will be converted into
 	 *         shares. See GToken.sol and GTokenBase.sol for further
 	 *         documentation.
 	 * @param _growthToken The WETH based gToken.
 	 */

function deposit(address _growthToken) public payable
	{
		address _from = msg.sender;
		uint256 _cost = msg.value;
		address _reserveToken = GToken(_growthToken).reserveToken();
		require(_reserveToken == $.WETH, "ETH operation not supported by token");
		G.safeWrap(_cost);
		G.approveFunds(_reserveToken, _growthToken, _cost);
		GToken(_growthToken).deposit(_cost);
		uint256 _netShares = G.getBalance(_growthToken);
		G.pushFunds(_growthToken, _from, _netShares);
	}


/**
 * @notice Get order's properties
 * @param _data - Bytes of the order
 * @return module - Address of the module to use for the order execution
 * @return inputToken - Address of the input token
 * @return owner - Address of the order's owner
 * @return witness - Address of the witness
 * @return data - Bytes of the order's data
 * @return secret -  Private key of the _witness
 */

function decodeOrder(
        bytes memory _data
    ) public pure returns (
        address module,
        address inputToken,
        address payable owner,
        address witness,
        bytes memory data,
        bytes32 secret
    ) {
        (
            module,
            inputToken,
            owner,
            witness,
            data,
            secret
        ) = abi.decode(
            _data,
            (
                address,
                address,
                address,
                address,
                bytes,
                bytes32
            )
        );
    }


/**
 * @notice Executes an order
 * @dev The sender should use the _secret to sign its own address
 * to prevent front-runnings
 * @param _module - Address of the module to use for the order execution
 * @param _inputToken - Address of the input token
 * @param _owner - Address of the order's owner
 * @param _data - Bytes of the order's data
 * @param _signature - Signature to calculate the witness
 * @param _auxData - Bytes of the auxiliar data used for the handlers to execute the order
 */

function executeOrder(
        IModule _module,
        IERC20 _inputToken,
        address payable _owner,
        bytes calldata _data,
        bytes calldata _signature,
        bytes calldata _auxData
    ) external {
        // Calculate witness using signature
        address witness = ECDSA.recover(
            keccak256(abi.encodePacked(msg.sender)),
            _signature
        );

        bytes32 key = keyOf(
            _module,
            _inputToken,
            _owner,
            witness,
            _data
        );

        // Pull amount
        uint256 amount = _pullOrder(_inputToken, key, address(_module));
        require(amount > 0, "HyperLimit#executeOrder: INVALID_ORDER");

        uint256 bought = _module.execute(
            _inputToken,
            amount,
            _owner,
            _data,
            _auxData
        );

        emit OrderExecuted(
            key,
            address(_inputToken),
            _owner,
            witness,
            _data,
            _auxData,
            amount,
            bought
        );
    }


/**
 * @notice Check whether an order exists or not
 * @dev Check the balance of the order
 * @param _module - Address of the module to use for the order execution
 * @param _inputToken - Address of the input token
 * @param _owner - Address of the order's owner
 * @param _witness - Address of the witness
 * @param _data - Bytes of the order's data
 * @return bool - whether the order exists or not
 */

function existOrder(
        IModule _module,
        IERC20 _inputToken,
        address payable _owner,
        address _witness,
        bytes calldata _data
    ) external view returns (bool) {
        bytes32 key = keyOf(
            _module,
            _inputToken,
            _owner,
            _witness,
           _data
        );

        if (address(_inputToken) == ETH_ADDRESS) {
            return ethDeposits[key] != 0;
        } else {
            return _inputToken.balanceOf(key.getVault()) != 0;
        }
    }


/**
 * @notice Check whether an order can be executed or not
 * @param _module - Address of the module to use for the order execution
 * @param _inputToken - Address of the input token
 * @param _owner - Address of the order's owner
 * @param _witness - Address of the witness
 * @param _data - Bytes of the order's data
 * @param _auxData - Bytes of the auxiliar data used for the handlers to execute the order
 * @return bool - whether the order can be executed or not
 */

function canExecuteOrder(
        IModule _module,
        IERC20 _inputToken,
        address payable _owner,
        address _witness,
        bytes calldata _data,
        bytes calldata _auxData
    ) external view returns (bool) {
        bytes32 key = keyOf(
            _module,
            _inputToken,
            _owner,
            _witness,
            _data
        );

        // Pull amount
        uint256 amount;
        if (address(_inputToken) == ETH_ADDRESS) {
            amount = ethDeposits[key];
        } else {
            amount = _inputToken.balanceOf(key.getVault());
        }

        return _module.canExecute(
            _inputToken,
            amount,
            _data,
            _auxData
        );
    }


/**
 * @notice Transfer the order amount to a recipient.
 * @dev For an ETH order, the ETH will be transferred from this contract
 * For a token order, its vault will be executed transferring the amount of tokens to
 * the recipient
 * @param _inputToken - Address of the input token
 * @param _key - Order's key
 * @param _to - Address of the recipient
 * @return amount - amount transferred
 */

function _pullOrder(
        IERC20 _inputToken,
        bytes32 _key,
        address payable _to
    ) private returns (uint256 amount) {
        if (address(_inputToken) == ETH_ADDRESS) {
            amount = ethDeposits[_key];
            ethDeposits[_key] = 0;
            (bool success,) = _to.call{value: amount}("");
            require(success, "HyperLimit#_pullOrder: PULL_ETHER_FAILED");
        } else {
            amount = _key.executeVault(_inputToken, _to);
        }
    }


/**
 	 * @notice Performs the minting of gToken shares upon the deposit of the
 	 *         reserve token. The actual number of shares being minted can
 	 *         be calculated using the calcDepositSharesFromCost function.
 	 *         In every deposit, 1% of the shares is retained in terms of
 	 *         deposit fee. Half of it is immediately burned and the other
 	 *         half is provided to the locked liquidity pool. The funds
 	 *         will be pulled in by this contract, therefore they must be
 	 *         previously approved.
 	 * @param _cost The amount of reserve token being deposited in the
 	 *              operation.
 	 */

function deposit(uint256 _cost) public override nonReentrant
	{
		address _from = msg.sender;
		require(_cost > 0, "cost must be greater than 0");
		(uint256 _netShares, uint256 _feeShares) = GFormulae._calcDepositSharesFromCost(_cost, totalReserve(), totalSupply(), depositFee());
		require(_netShares > 0, "shares must be greater than 0");
		G.pullFunds(reserveToken, _from, _cost);
		require(_prepareDeposit(_cost), "not available at the moment");
		_mint(_from, _netShares);
		_mint(address(this), _feeShares.div(2));
	}