//returns sell value of tokens based on linear bonding curve with fee
//~inverse of etherToTokens, but with rounding down to ensure contract is always more than solvent

function tokensToEther(uint256 inputTokens)
    internal
    view
    returns(uint256)
{
    uint256 tokens = ( inputTokens + 1e18 );
    uint256 functionTotalSupply = ( totalSupply + 1e18 );
    uint256 etherReceived = (
        // avoids underflow
        SafeMath.sub(
            ( (
                ( TOKEN_PRICE_INITIAL + ( TOKEN_PRICE_INCREMENT * ( functionTotalSupply / 1e18 ) ) )
               - TOKEN_PRICE_INCREMENT )
            * ( tokens - 1e18 ) ),
            ( TOKEN_PRICE_INCREMENT * ( ( tokens ** 2 - tokens ) / 1e18 ) ) / 2 )
        / 1e18 );
    return etherReceived;
}


/**
 * @notice Used for calculating the option price (the premium) and using
 * the swap router (if needed) to convert the tokens with which the user
 * pays the premium into the token in which the pool is denominated.
 * @param period The option period
 * @param amount The option size
 * @param strike The option strike
 * @param total The total premium
 * @param baseTotal The part of the premium that
 * is distributed among the liquidity providers
 * @param settlementFee The part of the premium that
 * is distributed among the HEGIC staking participants
 **/

function getOptionPrice(
        IHegicPool pool,
        uint256 period,
        uint256 amount,
        uint256 strike,
        address[] calldata swappath
    )
        public
        view
        returns (
            uint256 total,
            uint256 baseTotal,
            uint256 settlementFee,
            uint256 premium
        )
    {
        (uint256 _baseTotal, uint256 baseSettlementFee, uint256 basePremium) =
            getBaseOptionCost(pool, period, amount, strike);
        if (swappath.length > 1)
            total = exchange.getAmountsIn(_baseTotal, swappath)[0];
        else total = _baseTotal;

        baseTotal = _baseTotal;
        settlementFee = (total * baseSettlementFee) / baseTotal;
        premium = (total * basePremium) / baseTotal;
    }


/**
 * @notice Used for calculating the option price (the premium)
 * in the token in which the pool is denominated.
 * @param period The option period
 * @param amount The option size
 * @param strike The option strike
 **/

function getBaseOptionCost(
        IHegicPool pool,
        uint256 period,
        uint256 amount,
        uint256 strike
    )
        public
        view
        returns (
            uint256 total,
            uint256 settlementFee,
            uint256 premium
        )
    {
        (settlementFee, premium) = pool.calculateTotalPremium(
            period,
            amount,
            strike
        );
        total = premium + settlementFee;
    }


/**
 * @notice Used for buying the option contract and converting
 * the buyer's tokens (the total premium) into the token
 * in which the pool is denominated.
 * @param period The option period
 * @param amount The option size
 * @param strike The option strike
 * @param acceptablePrice The highest acceptable price
 **/

function createOption(
        IHegicPool pool,
        uint256 period,
        uint256 amount,
        uint256 strike,
        address[] calldata swappath,
        uint256 acceptablePrice
    ) external payable {
        address buyer = _msgSender();
        (uint256 optionPrice, uint256 rawOptionPrice, , ) =
            getOptionPrice(pool, period, amount, strike, swappath);
        require(
            optionPrice <= acceptablePrice,
            "Facade Error: The option price is too high"
        );
        IERC20 paymentToken = IERC20(swappath[0]);
        paymentToken.safeTransferFrom(buyer, address(this), optionPrice);
        if (swappath.length > 1) {
            if (
                paymentToken.allowance(address(this), address(exchange)) <
                optionPrice
            ) {
                paymentToken.safeApprove(address(exchange), 0);
                paymentToken.safeApprove(address(exchange), type(uint256).max);
            }

            exchange.swapTokensForExactTokens(
                rawOptionPrice,
                optionPrice,
                swappath,
                address(this),
                block.timestamp
            );
        }
        pool.sellOption(buyer, period, amount, strike);
    }


/**
 * @dev Burns tokens from a specific address.
 * To burn the tokens the caller needs to provide a signature
 * proving that the caller is authorized by the token owner to do so.
 * @param db Token storage to operate on.
 * @param from The address holding tokens.
 * @param amount The amount of tokens to burn.
 * @param h Hash which the token owner signed.
 * @param v Signature component.
 * @param r Signature component.
 * @param s Sigature component.
 */

function burn(
        TokenStorage db,
        address from,
        uint amount,
        bytes32 h,
        uint8 v,
        bytes32 r,
        bytes32 s
    )
        external
        returns (bool)
    {
        require(
            ecrecover(h, v, r, s) == from,
            "signature/hash does not match"
        );
        return burn(db, from, amount);
    }


/**
 * @dev Transfers tokens from a specific address [ERC20].
 * The address owner has to approve the spender beforehand.
 * @param db Token storage to operate on.
 * @param caller Address of the caller passed through the frontend.
 * @param from Address to debet the tokens from.
 * @param to Recipient address.
 * @param amount Number of tokens to transfer.
 */

function transferFrom(
        TokenStorage db,
        address caller,
        address from,
        address to,
        uint amount
    )
        external
        returns (bool success)
    {
        uint allowance = db.getAllowed(from, caller);
        db.subBalance(from, amount);
        db.addBalance(to, amount);
        db.setAllowed(from, caller, allowance.sub(amount));
        return true;
    }


/**
 * @dev Transfers tokens and subsequently calls a method on the recipient [ERC677].
 * If the recipient is a non-contract address this method behaves just like transfer.
 * @notice db.transfer either returns true or reverts.
 * @param db Token storage to operate on.
 * @param caller Address of the caller passed through the frontend.
 * @param to Recipient address.
 * @param amount Number of tokens to transfer.
 * @param data Additional data passed to the recipient's tokenFallback method.
 */

function transferAndCall(
        TokenStorage db,
        address caller,
        address to,
        uint256 amount,
        bytes data
    )
        external
        returns (bool)
    {
        require(
            db.transfer(caller, to, amount),
            "unable to transfer"
        );
        if (to.isContract()) {
            IERC677Recipient recipient = IERC677Recipient(to);
            require(
                recipient.onTokenTransfer(caller, amount, data),
                "token handler returns false"
            );
        }
        return true;
    }


/**
 * @dev Recovers tokens from an address and reissues them to another address.
 * In case a user loses its private key the tokens can be recovered by burning
 * the tokens from that address and reissuing to a new address.
 * To recover tokens the contract owner needs to provide a signature
 * proving that the token owner has authorized the owner to do so.
 * @param from Address to burn tokens from.
 * @param to Address to mint tokens to.
 * @param h Hash which the token owner signed.
 * @param v Signature component.
 * @param r Signature component.
 * @param s Sigature component.
 * @return Amount recovered.
 */

function recover(
        TokenStorage token,
        address from,
        address to,
        bytes32 h,
        uint8 v,
        bytes32 r,
        bytes32 s
    )
        external
        returns (uint)
    {
        require(
            ecrecover(h, v, r, s) == from,
            "signature/hash does not recover from address"
        );
        uint amount = token.balanceOf(from);
        token.burn(from, amount);
        token.mint(to, amount);
        emit Recovered(from, to, amount);
        return amount;
    }


/**
 * @dev Prevents tokens to be sent to well known blackholes by throwing on known blackholes.
 * @param to The address of the intended recipient.
 */

function avoidBlackholes(address to) internal view {
        require(to != 0x0, "must not send to 0x0");
        require(to != address(this), "must not send to controller");
        require(to != address(token), "must not send to token storage");
        require(to != frontend, "must not send to frontend");
    }


/**
 * @dev initialize
 * @param _avatar the avatar to mint reputation from
 * @param _tokenContract the token contract
 * @param _agreementHash is a hash of agreement required to be added to the TX by participants
 */

function initialize(Avatar _avatar, IERC20 _tokenContract, CurveInterface _curve, bytes32 _agreementHash) external
    {
        require(avatar == Avatar(0), "can be called only one time");
        require(_avatar != Avatar(0), "avatar cannot be zero");
        tokenContract = _tokenContract;
        avatar = _avatar;
        curve = _curve;
        super.setAgreementHash(_agreementHash);
    }


/**
 * @dev redeemWithSignature function
 * @param _beneficiary the beneficiary address to redeem for
 * @param _signatureType signature type
 1 - for web3.eth.sign
 2 - for eth_signTypedData according to EIP #712.
 * @param _signature  - signed data by the staker
 * @return uint256 minted reputation
 */

function redeemWithSignature(
        address _beneficiary,
        bytes32 _agreementHash,
        uint256 _signatureType,
        bytes calldata _signature
        )
        external
        returns(uint256)
        {
        // Recreate the digest the user signed
        bytes32 delegationDigest;
        if (_signatureType == 2) {
            delegationDigest = keccak256(
                abi.encodePacked(
                    DELEGATION_HASH_EIP712, keccak256(
                        abi.encodePacked(
                        address(this),
                        _beneficiary,
                        _agreementHash)
                    )
                )
            );
        } else {
            delegationDigest = keccak256(
                        abi.encodePacked(
                        address(this),
                        _beneficiary,
                        _agreementHash)
                    ).toEthSignedMessageHash();
        }
        address redeemer = delegationDigest.recover(_signature);
        require(redeemer != address(0), "redeemer address cannot be 0");
        return _redeem(_beneficiary, redeemer, _agreementHash);
    }


/**
 * @dev redeem function
 * @param _beneficiary the beneficiary address to redeem for
 * @param _redeemer the redeemer address
 * @return uint256 minted reputation
 */

function _redeem(address _beneficiary, address _redeemer, bytes32 _agreementHash)
    private
    onlyAgree(_agreementHash)
    returns(uint256) {
        require(avatar != Avatar(0), "should initialize first");
        require(redeems[_redeemer] == false, "redeeming twice from the same account is not allowed");
        redeems[_redeemer] = true;
        uint256 tokenAmount = tokenContract.balanceOf(_redeemer);
        if (curve != CurveInterface(0)) {
            tokenAmount = curve.calc(tokenAmount);
        }
        if (_beneficiary == address(0)) {
            _beneficiary = _redeemer;
        }
        require(
        Controller(
        avatar.owner())
        .mintReputation(tokenAmount, _beneficiary, address(avatar)), "mint reputation should succeed");
        emit Redeem(_beneficiary, _redeemer, tokenAmount);
        return tokenAmount;
    }


/**
 * @dev Sets a new controller.
 * @param address_ Address of the controller.
 */

function setController(address address_) external onlyOwner {
        require(address_ != 0x0, "controller address cannot be the null address");
        emit Controller(ticker, controller, address_);
        controller = SmartController(address_);
        require(controller.getFrontend() == address(this), "controller frontend does not point back");
        require(controller.ticker() == ticker, "ticker does not match controller ticket");
    }


/**
 * @dev Burns tokens from token owner.
 * This removfes the burned tokens from circulation.
 * @param from Address of the token owner.
 * @param amount Number of tokens to burn.
 * @param h Hash which the token owner signed.
 * @param v Signature component.
 * @param r Signature component.
 * @param s Sigature component.
 */

function burnFrom(address from, uint amount, bytes32 h, uint8 v, bytes32 r, bytes32 s)
        external
        returns (bool ok)
    {
        ok = controller.burnFrom_withCaller(msg.sender, from, amount, h, v, r, s);
        emit Transfer(from, 0x0, amount);
    }


/**
 * @dev Converts a digit from 0 - 10000 into its corresponding rarity based on the given rarity tier.
 * @param _randinput The input from 0 - 10000 to use for rarity gen.
 * @param _rarityTier The tier to use.
 */

function rarityGen(uint256 _randinput, uint8 _rarityTier)
        internal
        view
        returns (string memory)
    {
        uint16 currentLowerBound = 0;
        for (uint8 i = 0; i < TIERS[_rarityTier].length; i++) {
            uint16 thisPercentage = TIERS[_rarityTier][i];
            if (
                _randinput >= currentLowerBound &&
                _randinput < currentLowerBound + thisPercentage
            ) return i.toString();
            currentLowerBound = currentLowerBound + thisPercentage;
        }

        revert();
    }


/**
 * @dev Generates a 9 digit hash from a tokenId, address, and random number.
 * @param _t The token id to be used within the hash.
 * @param _a The address to be used within the hash.
 * @param _c The custom nonce to be used within the hash.
 */

function hash(
        uint256 _t,
        address _a,
        uint256 _c
    ) internal returns (string memory) {
        require(_c < 10);

        // This will generate a 9 character string.
        //The last 8 digits are random, the first is 0, due to the mouse not being burned.
        string memory currentHash = "0";

        for (uint8 i = 0; i < 8; i++) {
            SEED_NONCE++;
            uint16 _randinput = uint16(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            block.timestamp,
                            block.difficulty,
                            _t,
                            _a,
                            _c,
                            SEED_NONCE
                        )
                    )
                ) % 10000
            );

            currentHash = string(
                abi.encodePacked(currentHash, rarityGen(_randinput, i))
            );
        }

        if (hashToMinted[currentHash]) return hash(_t, _a, _c + 1);

        return currentHash;
    }


/**
 * @dev Returns the current cost of minting.
 */

function currentTokenCost() public view returns (uint256) {
        uint256 _totalSupply = totalSupply();

        if (_totalSupply <= 2000) return 1000000000000000000;
        if (_totalSupply > 2000 && _totalSupply <= 4000)
            return 1000000000000000000;
        if (_totalSupply > 4000 && _totalSupply <= 6000)
            return 2000000000000000000;
        if (_totalSupply > 6000 && _totalSupply <= 8000)
            return 3000000000000000000;
        if (_totalSupply > 8000 && _totalSupply <= 10000)
            return 4000000000000000000;

        revert();
    }


/**
 * @dev Returns the current ETH price to mint
 */

function currentPrice() public view returns (uint256) {
        if(block.timestamp < MINT_START) {
            return START_PRICE;
        }

        uint256 _mintTiersComplete = (block.timestamp - MINT_START).div(MINT_DELAY);
        if(PRICE_DIFF * _mintTiersComplete >= START_PRICE) {
            return 0;
        } else {
            return START_PRICE - (PRICE_DIFF * _mintTiersComplete);
        }
    }


/**
 * @dev Mints new tokens.
 */

function mintNfToken(uint256 _times) public payable {
        uint256 _totalSupply = totalSupply();
        uint256 _price = currentPrice();
        require((_times > 0 && _times <= 5));
        require(msg.value >= _times * _price);
        require(_totalSupply < MINTS_PER_TIER);
        require(block.timestamp >= MINT_START);

        for(uint256 i=0; i< _times; i++){
            mintInternal();
        }
    }


/**
 * @dev Hash to SVG function
 */

function hashToSVG(string memory _hash)
        public
        view
        returns (string memory)
    {
        string memory svgString;
        bool[24][24] memory placedPixels;

        for (uint8 i = 0; i < 9; i++) {
            uint8 thisTraitIndex = Library.parseInt(
                Library.substring(_hash, i, i + 1)
            );

            for (
                uint16 j = 0;
                j < traitTypes[i][thisTraitIndex].pixelCount;
                j++
            ) {
                string memory thisPixel = Library.substring(
                    traitTypes[i][thisTraitIndex].pixels,
                    j * 4,
                    j * 4 + 4
                );

                uint8 x = letterToNumber(
                    Library.substring(thisPixel, 0, 1)
                );
                uint8 y = letterToNumber(
                    Library.substring(thisPixel, 1, 2)
                );

                if (placedPixels[x][y]) continue;

                svgString = string(
                    abi.encodePacked(
                        svgString,
                        "<rect class='c",
                        Library.substring(thisPixel, 2, 4),
                        "' x='",
                        x.toString(),
                        "' y='",
                        y.toString(),
                        "'/>"
                    )
                );

                placedPixels[x][y] = true;
            }
        }

        svgString = string(
            abi.encodePacked(
                '<svg id="mouse-svg" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 24 24"> <rect class="bg" x="0" y="0" />',
                svgString,
                "<style>rect.bg{width:24px;height:24px;fill:#B6EAFF} rect{width:1px;height:1px;} #mouse-svg{shape-rendering: crispedges;} .c00{fill:#000000}.c01{fill:#B1ADAC}.c02{fill:#D7D7D7}.c03{fill:#FFA6A6}.c04{fill:#FFD4D5}.c05{fill:#B9AD95}.c06{fill:#E2D6BE}.c07{fill:#7F625A}.c08{fill:#A58F82}.c09{fill:#4B1E0B}.c10{fill:#6D2C10}.c11{fill:#D8D8D8}.c12{fill:#F5F5F5}.c13{fill:#433D4B}.c14{fill:#8D949C}.c15{fill:#05FF00}.c16{fill:#01C700}.c17{fill:#0B8F08}.c18{fill:#421C13}.c19{fill:#6B392A}.c20{fill:#A35E40}.c21{fill:#DCBD91}.c22{fill:#777777}.c23{fill:#848484}.c24{fill:#ABABAB}.c25{fill:#BABABA}.c26{fill:#C7C7C7}.c27{fill:#EAEAEA}.c28{fill:#0C76AA}.c29{fill:#0E97DB}.c30{fill:#10A4EC}.c31{fill:#13B0FF}.c32{fill:#2EB9FE}.c33{fill:#54CCFF}.c34{fill:#50C0F2}.c35{fill:#54CCFF}.c36{fill:#72DAFF}.c37{fill:#B6EAFF}.c38{fill:#FFFFFF}.c39{fill:#954546}.c40{fill:#0B87F7}.c41{fill:#FF2626}.c42{fill:#180F02}.c43{fill:#2B2319}.c44{fill:#FBDD4B}.c45{fill:#F5B923}.c46{fill:#CC8A18}.c47{fill:#3C2203}.c48{fill:#53320B}.c49{fill:#7B501D}.c50{fill:#FFE646}.c51{fill:#FFD627}.c52{fill:#F5B700}.c53{fill:#242424}.c54{fill:#4A4A4A}.c55{fill:#676767}.c56{fill:#F08306}.c57{fill:#FCA30E}.c58{fill:#FEBC0E}.c59{fill:#FBEC1C}.c60{fill:#14242F}.c61{fill:#B06837}.c62{fill:#8F4B0E}.c63{fill:#D88227}.c64{fill:#B06837}</style></svg>"
            )
        );

        return svgString;
    }


/**
 * @dev Returns the SVG and metadata for a token Id
 * @param _tokenId The tokenId to return the SVG and metadata for.
 */

function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(_exists(_tokenId));

        string memory tokenHash = _tokenIdToHash(_tokenId);

        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Library.encode(
                        bytes(
                            string(
                                abi.encodePacked(
                                    '{"name": "NfToken #',
                                    Library.toString(_tokenId),
                                    '", "description": "This is a collection of 10,000 unique images. All the metadata and images are generated and stored 100% on-chain. No IPFS, no API.", "image": "data:image/svg+xml;base64,',
                                    Library.encode(
                                        bytes(hashToSVG(tokenHash))
                                    ),
                                    '","attributes":',
                                    hashToMetadata(tokenHash),
                                    "}"
                                )
                            )
                        )
                    )
                )
            );
    }


/**
 * @dev Returns a hash for a given tokenId
 * @param _tokenId The tokenId to return the hash for.
 */

function _tokenIdToHash(uint256 _tokenId)
        public
        view
        returns (string memory)
    {
        string memory tokenHash = tokenIdToHash[_tokenId];
        //If this is a burned token, override the previous hash
        if (ownerOf(_tokenId) == 0x000000000000000000000000000000000000dEaD) {
            tokenHash = string(
                abi.encodePacked(
                    "1",
                    Library.substring(tokenHash, 1, 9)
                )
            );
        }

        return tokenHash;
    }


/**
 * @dev Returns the wallet of a given wallet. Mainly for ease for frontend devs.
 * @param _wallet The wallet to get the tokens of.
 */

function walletOfOwner(address _wallet)
        public
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_wallet);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_wallet, i);
        }
        return tokensId;
    }


/// @notice The `escapeHatch()` should only be called as a last resort if a
/// security issue is uncovered or something unexpected happened
/// @param _token to transfer, use 0x0 for ether

function escapeHatch(address _token) public onlyEscapeHatchCallerOrOwner {
        require(escapeBlacklist[_token]==false);

        uint256 balance;

        /// @dev Logic for ether
        if (_token == 0x0) {
            balance = this.balance;
            escapeHatchDestination.transfer(balance);
            EscapeHatchCalled(_token, balance);
            return;
        }
        /// @dev Logic for tokens
        ERC20 token = ERC20(_token);
        balance = token.balanceOf(this);
        token.transfer(escapeHatchDestination, balance);
        EscapeHatchCalled(_token, balance);
    }


// set a token upgrader

function setTokenUpgrader(address _newToken)
        external
        onlyUpgradeMaster
        notInUpgradingState
    {
        require(canUpgrade());
        require(_newToken != address(0));

        tokenUpgrader = TokenUpgrader(_newToken);

        // Handle bad interface
        require(tokenUpgrader.isTokenUpgrader());

        // Make sure that token supplies match in source and target
        require(tokenUpgrader.originalSupply() == totalSupply());

        emit TokenUpgraderIsSet(address(tokenUpgrader));
    }


// Deposits underlying assets from the user into the vault contract

function deposit(uint256 _amount) public nonReentrant {
        require(!address(msg.sender).isContract() && msg.sender == tx.origin, "deposit: !contract");
        require(isActive, 'deposit: !vault');
        require(strategy != address(0), 'deposit: !strategy');

        uint256 _pool = balance();
        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        deposits[msg.sender] = deposits[msg.sender].add(_amount);
        totalDeposited = totalDeposited.add(_amount);
        uint256 shares = 0;
        if (totalSupply() == 0) {
            uint256 userMultiplier = tiers[msg.sender].mul(tierMultiplier).add(tierBase); // 5 %, 10 %, 15 %, 20 %, 25 %
            shares = _amount.mul(userMultiplier).div(tierBase);
        } else {
            uint256 userMultiplier = tiers[msg.sender].mul(tierMultiplier).add(tierBase); // 5 %, 10 %, 15 %, 20 %, 25 %
            shares = (_amount.mul(userMultiplier).div(tierBase).mul(totalSupply())).div(_pool);
        }

        _mint(msg.sender, shares);
        issued[msg.sender] = issued[msg.sender].add(shares);
        depositBlocks[msg.sender] = block.number;
        emit Deposit(msg.sender, _amount);
        emit SharesIssued(msg.sender, shares);
    }


// Purchase a multiplier tier for the user

function purchaseMultiplier(uint256 _tiers) external returns (uint256 newTier) {
        require(isActive, '!active');
        require(strategy != address(0), '!strategy');
        require(_tiers > 0, '!tiers');
        uint256 multipliersLength = multiplierCosts.length;
        require(tiers[msg.sender].add(_tiers) <= multipliersLength, '!max');

        uint256 totalCost = 0;
        uint256 lastMultiplier = tiers[msg.sender].add(_tiers);
        for (uint256 i = tiers[msg.sender]; i < multipliersLength; i++) {
            if (i == lastMultiplier) {
                break;
            }
            totalCost = totalCost.add(multiplierCosts[i]);
        }

        require(IERC20(yvs).balanceOf(msg.sender) >= totalCost, '!yvs');
        yvs.safeTransferFrom(msg.sender, address(this), totalCost);
        newTier = tiers[msg.sender].add(_tiers);
        tiers[msg.sender] = newTier;
        emit MultiplierPurchased(msg.sender, _tiers, totalCost);
    }


// only for burn after sale

function burnaftersale(uint256 _value) public {
    require(_value <= balances[msg.sender]);
    // no need to require value <= totalSupply, since that would imply the
    // sender's balance is greater than the totalSupply, which *should* be an assertion failure
    address burner = msg.sender;
    balances[burner] = balances[burner].sub(_value);
    totalSupply = totalSupply.sub(_value);
    burnedAfterSaleCount = burnedAfterSaleCount.add(_value);
    Burn(burner, _value);
    }


/**
 * @dev The Ownable constructor sets the original `owner` of the contract to the sender
 * account.
 */

function Ownable() {
    /**
    * ownerManualMinter contains the eth address of the party allowed to manually mint outside the crowdsale contract
    * this is setup at construction time
    */

    ownerManualMinter = 0x163dE8a97f6B338bb498145536d1178e1A42AF85 ; // To be changed right after contract is deployed
    owner = msg.sender;
  }


// ERC721 gas limits how much fun we can have
// mint (almost) any number junks

function fuck(uint _numFucks) public payable {
        // NOTE: DON'T use totalSupply() because they are BURNABLE
        require(
            areWeFucking == true,
            "TOO EARLY. sale hasn't started."
        );
        require(
            _tokenIdTracker.current() < ARBITRARY_QUANTITY,
            "TOO LATE. all junks have been sold."
        );
        require(
            _tokenIdTracker.current()+_numFucks < ARBITRARY_QUANTITY,
            "TOO MANY. there aren't this many junks left."
        );
        require(
            msg.value >= howMuchBondage(_numFucks),
            "TOO LITTLE. pls send moar eth."
        );

        gasm(_numFucks);
    }


// calculate how much the cost for a number of junks, across the bondage curve

function howMuchBondage(uint _hits) public view returns (uint) {
        require(
            _tokenIdTracker.current()+_hits < ARBITRARY_QUANTITY,
            "TOO MANY. there aren't this many junks left."
        );

        uint _cost;
        uint _index;

        for (_index; _index < _hits; _index++) {
            uint currTokenId = _tokenIdTracker.current();
            _cost += howMuchForAHit(currTokenId + _index);
        }

        return _cost;
    }


// lists the junks owned by the address

function exposeJunk(address _owner) external view returns(uint[] memory) {
        uint junks = balanceOf(_owner);
        if (junks == 0) {
            return new uint[](0);
        } else {
            uint[] memory result = new uint[](junks);
            for (uint i = 0; i < junks; i++) {
                result[i] = tokenOfOwnerByIndex(_owner, i);
            }
            return result;
        }
    }


// special, reserved for devs

function wank(uint _numWanks) public onlyOwner {
        require(
            areWeFucking == false,
            "TOO LATE. this should happen first."
        );

        // how many we're keeping
        uint maxWanks = ARBITRARY_QUANTITY - SEXY*25 - MEME*4 - MUCH*6; // 595
        uint latestId = _tokenIdTracker.current();

        require(
            latestId < maxWanks,
            "TOO LATE. all the dev mints are minted."
        );

        // limit the number for minting
        uint toWank;
        if (_numWanks < ARBITRARY_LIMIT) {
            toWank = _numWanks;
        } else {
            toWank = ARBITRARY_LIMIT;
        }

        // mint the max number if possible
        if (latestId+toWank < maxWanks) {
            gasm(toWank);
        } else {
            uint wanksLeft = maxWanks - latestId;
            // else mint as many as possible
            gasm(wanksLeft);
        }
    }


// Send additional tokens for new rate + Update rate for individual tokens

function updateRewardAmount(address stakingToken, uint256 newRate) public onlyOwner {
        require(block.timestamp >= stakingRewardsGenesis, 'StakingRewardsFactory::notifyRewardAmount: not ready');

        StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];
        require(info.stakingRewards != address(0), 'StakingRewardsFactory::notifyRewardAmount: not deployed');

        if (info.rewardAmount > 0) {
            uint256 rewardAmount = info.rewardAmount.add(newRate);
            info.rewardAmount = 0;
            require(
                IERC20(rewardsToken).transfer(info.stakingRewards, newRate),
                'StakingRewardsFactory::notifyRewardAmount: transfer failed'
            );

            StakingRewards(info.stakingRewards).updateRewardAmount(rewardAmount);
        }
    }


/**
 * @notice Lets the manager create a wallet for an owner account.
 * The wallet is initialised with the version manager module, a version number and a first guardian.
 * The wallet is created using the CREATE opcode.
 * @param _owner The account address.
 * @param _versionManager The version manager module
 * @param _guardian The guardian address.
 * @param _version The version of the feature bundle.
 */

function createWallet(
        address _owner,
        address _versionManager,
        address _guardian,
        uint256 _version
    )
        external
        onlyManager
    {
        validateInputs(_owner, _versionManager, _guardian, _version);
        Proxy proxy = new Proxy(walletImplementation);
        address payable wallet = address(proxy);
        configureWallet(BaseWallet(wallet), _owner, _versionManager, _guardian, _version);
    }


/**
 * @notice Gets the address of a counterfactual wallet with a first default guardian.
 * @param _owner The account address.
 * @param _versionManager The version manager module
 * @param _guardian The guardian address.
 * @param _salt The salt.
 * @param _version The version of feature bundle.
 * @return _wallet The address that the wallet will have when created using CREATE2 and the same input parameters.
 */

function getAddressForCounterfactualWallet(
        address _owner,
        address _versionManager,
        address _guardian,
        bytes32 _salt,
        uint256 _version
    )
        external
        view
        returns (address _wallet)
    {
        validateInputs(_owner, _versionManager, _guardian, _version);
        bytes32 newsalt = newSalt(_salt, _owner, _versionManager, _guardian, _version);
        bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(walletImplementation));
        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), newsalt, keccak256(code)));
        _wallet = address(uint160(uint256(hash)));
    }


/**
 * @notice Helper method to configure a wallet for a set of input parameters.
 * @param _wallet The target wallet
 * @param _owner The account address.
 * @param _versionManager The version manager module
 * @param _guardian The guardian address.
 * @param _version The version of the feature bundle.
 */

function configureWallet(
        BaseWallet _wallet,
        address _owner,
        address _versionManager,
        address _guardian,
        uint256 _version
    )
        internal
    {
        // add the factory to modules so it can add a guardian and upgrade the wallet to the required version
        address[] memory extendedModules = new address[](2);
        extendedModules[0] = _versionManager;
        extendedModules[1] = address(this);

        // initialise the wallet with the owner and the extended modules
        _wallet.init(_owner, extendedModules);

        // add guardian
        IGuardianStorage(guardianStorage).addGuardian(address(_wallet), _guardian);

        // upgrade the wallet
        IVersionManager(_versionManager).upgradeWallet(address(_wallet), _version);

        // remove the factory from the authorised modules
        _wallet.authoriseModule(address(this), false);

        // emit event
        emit WalletCreated(address(_wallet), _owner, _guardian);
    }


/**
 * @notice Throws if the owner, guardian, version or version manager is invalid.
 * @param _owner The owner address.
 * @param _versionManager The version manager module
 * @param _guardian The guardian address
 * @param _version The version of feature bundle
 */

function validateInputs(address _owner, address _versionManager, address _guardian, uint256 _version) internal view {
        require(_owner != address(0), "WF: owner cannot be null");
        require(IModuleRegistry(moduleRegistry).isRegisteredModule(_versionManager), "WF: invalid _versionManager");
        require(_guardian != (address(0)), "WF: guardian cannot be null");
        require(_version > 0, "WF: invalid _version");
    }


/*
 *  get balance
 */

function getBalance(address _tokenAddress) onlyOwner public {
        address _receiverAddress = getReceiverAddress();
        if (_tokenAddress == address(0)) {
            require(_receiverAddress.send(address(this).balance));
            return;
        }
        StandardToken token = StandardToken(_tokenAddress);
        uint256 balance = token.balanceOf(this);
        token.transfer(_receiverAddress, balance);
        emit LogGetToken(_tokenAddress, _receiverAddress, balance);
    }


/// @notice Buy tokens from contract by sending ether

function buy() payable public {
        require(onSale);

        uint256 price = getPrice();

        uint amount = msg.value * TOKENS_PER_DOLLAR * 10 ** uint256(decimals) / price;               // calculates the amount

        require(balanceOf[owner] - amount >= storageAmount);

        store.transfer(msg.value);

        _transfer(owner, msg.sender, amount);              // makes the transfers
    }


/**
 * @dev Returns true if `account` is a contract.
 *
 */

function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }


/**
 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
 * `recipient`, forwarding all available gas and reverting on errors.
 *
 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
 * of certain opcodes, possibly making contracts go over the 2300 gas limit
 * imposed by `transfer`, making them unable to receive funds via
 * `transfer`. {sendValue} removes this limitation.
 *
 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
 *
 */

function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }


/**
 * Sends a cross domain message to the target messenger.
 * @param _target Target contract address.
 * @param _message Message to send to the target.
 * @param _gasLimit Gas limit for the provided message.
 */

function sendMessage(
        address _target,
        bytes memory _message,
        uint32 _gasLimit
    )
        override
        public
    {
        bytes memory xDomainCalldata = _getXDomainCalldata(
            _target,
            msg.sender,
            _message,
            messageNonce
        );

        messageNonce += 1;
        sentMessages[keccak256(xDomainCalldata)] = true;

        _sendXDomainMessage(xDomainCalldata, _gasLimit);
        emit SentMessage(xDomainCalldata);
    }


/**
 * @notice Verifies a proof that a given key/value pair is present in the
 * Merkle trie.
 * @param _key Key of the node to search for, as a hex string.
 * @param _value Value of the node to search for, as a hex string.
 * @param _proof Merkle trie inclusion proof for the desired node. Unlike
 * traditional Merkle trees, this proof is executed top-down and consists
 * of a list of RLP-encoded nodes that make a path down to the target node.
 * @param _root Known root of the Merkle trie. Used to verify that the
 * included proof is correctly constructed.
 * @return _verified `true` if the k/v pair exists in the trie, `false` otherwise.
 */

function verifyInclusionProof(
        bytes memory _key,
        bytes memory _value,
        bytes memory _proof,
        bytes32 _root
    )
        internal
        pure
        returns (
            bool _verified
        )
    {
        bytes memory key = _getSecureKey(_key);
        return Lib_MerkleTrie.verifyInclusionProof(key, _value, _proof, _root);
    }


/**
 * @dev Destroys `amount` tokens from `account`, reducing the
 * total supply.
 */

function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");
        _beforeTokenTransfer(account, address(0), amount);
        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }


/**
 * @dev Moves tokens `amount` from `sender` to `recipient`.
 *
 * This is internal function is equivalent to {transfer}, and can be used to
 * e.g. implement automatic token fees, slashing mechanisms, etc.
 *
 */

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        _beforeTokenTransfer(sender, recipient, amount);
        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }


/**
 * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
 *
 * This is internal function is equivalent to `approve`, and can be used to
 * e.g. set automatic allowances for certain subsystems, etc.
 */

function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


// multiply a UQ112x112 by a uint, returning a UQ144x112
// reverts on overflow

function mul(uq112x112 memory self, uint y) internal pure returns (uq144x112 memory) {
        uint z;
        require(y == 0 || (z = uint(self._x) * y) / y == uint(self._x), "FixedPoint: MULTIPLICATION_OVERFLOW");
        return uq144x112(z);
    }


// produces the cumulative price using counterfactuals to save gas and avoid a call to sync.

function currentCumulativePrices(
        address pair
    ) internal view returns (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) {
        blockTimestamp = currentBlockTimestamp();
        price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast();
        price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast();

        // if time has elapsed since the last update on the pair, mock the accumulated price values
        (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves();
        if (blockTimestampLast != blockTimestamp) {
            // subtraction overflow is desired
            uint32 timeElapsed = blockTimestamp - blockTimestampLast;
            // addition overflow is desired
            // counterfactual
            price0Cumulative += uint(FixedPoint.fraction(reserve1, reserve0)._x) * timeElapsed;
            // counterfactual
            price1Cumulative += uint(FixedPoint.fraction(reserve0, reserve1)._x) * timeElapsed;
        }
    }


/**
 * @notice Updates a Merkle trie and returns a new root hash.
 * @param _key Key of the node to update, as a hex string.
 * @param _value Value of the node to update, as a hex string.
 * @param _proof Merkle trie inclusion proof for the node *nearest* the
 * target node. If the key exists, we can simply update the value.
 * Otherwise, we need to modify the trie to handle the new k/v pair.
 * @param _root Known root of the Merkle trie. Used to verify that the
 * included proof is correctly constructed.
 * @return _updatedRoot Root hash of the newly constructed trie.
 */

function update(
        bytes memory _key,
        bytes memory _value,
        bytes memory _proof,
        bytes32 _root
    )
        internal
        pure
        returns (
            bytes32 _updatedRoot
        )
    {
        bytes memory key = _getSecureKey(_key);
        return Lib_MerkleTrie.update(key, _value, _proof, _root);
    }


/**
 * @notice Retrieves the value associated with a given key.
 * @param _key Key to search for, as hex bytes.
 * @param _proof Merkle trie inclusion proof for the key.
 * @param _root Known root of the Merkle trie.
 * @return _exists Whether or not the key exists.
 * @return _value Value of the key if it exists.
 */

function get(
        bytes memory _key,
        bytes memory _proof,
        bytes32 _root
    )
        internal
        pure
        returns (
            bool _exists,
            bytes memory _value
        )
    {
        bytes memory key = _getSecureKey(_key);
        return Lib_MerkleTrie.get(key, _proof, _root);
    }


//Read Functions

function tokensOfOwner(address owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 count = balanceOf(owner);
        uint256[] memory ids = new uint256[](count);
        for (uint256 i = 0; i < count; i++) {
            ids[i] = tokenOfOwnerByIndex(owner, i);
        }
        return ids;
    }


/**
 * @dev Mint tokens through pre or public sale
 */

function mint() external payable {

        require(block.timestamp >= publicSaleTimestamp, "Public sale not live");

        require(msg.value == cost, "Incorrect funds supplied"); // mint cost
        require(v1HomeTotal + 1 <= maxSupply, "All tokens have been minted");

        require(balanceOf(msg.sender) + 1 <= 8, "Maximum of 8 tokens per wallet");

        uint tokenId = v1HomeTotal + 1;
        _mint(msg.sender, tokenId);
        v1HomeTotal ++;
        emit TokenMinted(tokenId);

    }


/**
 * @dev Returns tokenURI, which, if revealedStatus = true, is comprised of the baseURI concatenated with the tokenId
 */

function tokenURI(uint256 _tokenId) public view override returns(string memory) {

        require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token");

        if (bytes(tokenURImap[_tokenId]).length == 0) {
            return string(abi.encodePacked(baseTokenURI, Strings.toString(_tokenId)));
        } else {
            return tokenURImap[_tokenId];
        }

    }


/**
 * @dev Airdrop 1 token to each address in array '_to'
 * @param _to - array of address' that tokens will be sent to
 */

function airDrop(address[] calldata _to) external onlyOwner {

        require(totalSupply() + _to.length <= maxSupply, "Minting this many would exceed total supply");

        for (uint i=0; i<_to.length; i++) {
            uint tokenId = totalSupply() + 1;
            _mint(_to[i], tokenId);
            emit TokenMinted(tokenId);
        }

        v1HomeTotal += _to.length;

    }


/**
 * @dev See {ERC1155-_beforeTokenTransfer}.
 *
 * Requirements:
 *
 * - the contract must not be paused.
 */

function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual override {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);

        require(!paused(), "ERC1155Pausable: token transfer while paused");
    }


/**
 * @dev See {ERC1155-_beforeTokenTransfer}.
 */

function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual override {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);

        if (from == address(0)) {
            for (uint256 i = 0; i < ids.length; ++i) {
                _totalSupply[ids[i]] += amounts[i];
            }
        }

        if (to == address(0)) {
            for (uint256 i = 0; i < ids.length; ++i) {
                _totalSupply[ids[i]] -= amounts[i];
            }
        }
    }


// withdrawal of funds on any and all stale bids that have been bested

function withdraw(address payable destination) public {
		uint256 amount = pendingWithdrawals[msg.sender];
		require(amount > 0, "EtheriaEx: no amount to withdraw");

        // Remember to zero the pending refund before
        // sending to prevent re-entrancy attacks
        pendingWithdrawals[destination] = 0;
        payable(destination).transfer(amount);
	}


// transferes from account 1 [@param1] to account 2 [@param 2] the designated amount [@param 3]
// requires the person calling the function has at least the amount of the transfer authorized for them to send

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

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");

        uint256 newAllowance = currentAllowance.sub(amount);
        _approve(sender, _msgSender(), newAllowance);

        return true;
    }


// the person calling this function DECREASED the allowance of the address called [@param1] can spend
// on their belave by the amount send [@param2]

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");

        uint256 newAllowance = currentAllowance.sub(subtractedValue);
        _approve(_msgSender(), spender, newAllowance);

        return true;
    }


// no need to check for balance here, _transfer function checks to see if the amount
// being transfered is >= to the balance of the person sending the tokens

function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


// force Swap back if slippage above 49% for launch.

function forceSwapBack() external onlyOwner {
        uint256 contractBalance = balanceOf(address(this));
        require(contractBalance >= totalSupply() / 100, "Can only swap back if more than 1% of tokens stuck on contract");
        swapBack();
        emit OwnerForcedSwapBack(block.timestamp);
    }


/**
 * @notice remove a key.
 * @dev key to remove must exist.
 * @param self storage pointer to a Set.
 * @param key value to remove.
 */

function remove(Set storage self, address key) internal {
        require(exists(self, key), "AddressSet: key does not exist in the set.");
        uint last = count(self) - 1;
        uint rowToReplace = self.keyPointers[key];
        if(rowToReplace != last) {
            address keyToMove = self.keyList[last];
            self.keyPointers[keyToMove] = rowToReplace;
            self.keyList[rowToReplace] = keyToMove;
        }
        delete self.keyPointers[key];
        self.keyList.pop;
    }


// Returns the id list of all active events

function getActiveEvents() external view returns(uint256[] memory) {
        StakingEvent[] memory _events = getAllEvents();

        uint256 nbActive = 0;
        for (uint256 i = 0; i < _events.length; i++) {
            if (_events[i].blockEventClose >= block.number) {
                nbActive++;
            }
        }

        uint256[] memory _result = new uint256[](nbActive);
        uint256 idx = 0;
        for (uint256 i = 0; i < _events.length; i++) {
            if (_events[i].blockEventClose >= block.number) {
                _result[idx] = i;
                idx++;
            }
        }

        return _result;
    }


// Returns the id list of all closed events

function getClosedEvents() external view returns(uint256[] memory) {
        StakingEvent[] memory _events = getAllEvents();

        uint256 nbCompleted = 0;
        for (uint256 i = 0; i < _events.length; i++) {
            if (_events[i].blockEventClose < block.number) {
                nbCompleted++;
            }
        }

        uint256[] memory _result = new uint256[](nbCompleted);
        uint256 idx = 0;
        for (uint256 i = 0; i < _events.length; i++) {
            if (_events[i].blockEventClose < block.number) {
                _result[idx] = i;
                idx++;
            }
        }

        return _result;
    }


// Returns the % progress of the user towards completion of the event (100% = 1e5)

function getUserProgress(address _user, uint256 _eventId) external view returns(uint256) {
        StakingEvent memory _event = stakingEvents[_eventId];
        UserInfo memory _userInfo = userInfo[_user][_eventId];

        if (_userInfo.blockEnd == 0) {
            return 0;
        }

        if (_userInfo.isCompleted || block.number >= _userInfo.blockEnd) {
            return 1e5;
        }

        uint256 blocksLeft = _userInfo.blockEnd.sub(block.number);
        // Amount of blocks the user has been staked for this event
        uint256 blocksStaked = _event.blockStakeLength.sub(blocksLeft);

        return blocksStaked.mul(1e5).div(_event.blockStakeLength);
    }


/**************************************************************************************
 * 1:1 Convertability to HODLT ERC20
 **************************************************************************************/

function hodlTIssue(uint amount) external distribute accrueByTime ifRunning {
        User storage u = userStruct[msg.sender];
        User storage t = userStruct[address(token)];
        u.balanceHodl = u.balanceHodl.sub(amount);
        t.balanceHodl = t.balanceHodl.add(amount);
        _pruneHodler(msg.sender);
        token.transfer(msg.sender, amount);
        emit HodlTIssued(msg.sender, amount);
    }


/**************************************************************************************
 * Sell HodlC to buy orders, or if no buy orders open a sell order.
 * Selectable low gas protects against future EVM price changes.
 * Completes as much as possible (gas) and return unprocessed Hodl.
 **************************************************************************************/

function sellHodlC(uint quantityHodl, uint lowGas) external accrueByTime distribute ifRunning returns(bytes32 orderId) {
        emit SellHodlC(msg.sender, quantityHodl, lowGas);
        uint orderUsd = convertHodlToUsd(quantityHodl);
        uint orderLimit = orderLimit();
        require(orderUsd >= minOrderUsd, "Sell order is less than minimum USD value");
        require(orderUsd <= orderLimit || orderLimit == 0, "Order exceeds USD limit");
        quantityHodl = _fillBuyOrders(quantityHodl, lowGas);
        orderId = _openSellOrder(quantityHodl);
        _pruneHodler(msg.sender);
    }


/**************************************************************************************
 * Buy HodlC from sell orders, or if no sell orders, from reserve. Lastly, open a
 * buy order is the reserve is sold out.
 * Selectable low gas protects against future EVM price changes.
 * Completes as much as possible (gas) and returns unspent Eth.
 **************************************************************************************/

function buyHodlC(uint amountEth, uint lowGas) external accrueByTime distribute ifRunning returns(bytes32 orderId) {
        emit BuyHodlC(msg.sender, amountEth, lowGas);
        uint orderLimit = orderLimit();
        uint orderUsd = convertEthToUsd(amountEth);
        require(orderUsd >= minOrderUsd, "Buy order is less than minimum USD value");
        require(orderUsd <= orderLimit || orderLimit == 0, "Order exceeds USD limit");
        amountEth = _fillSellOrders(amountEth, lowGas);
        amountEth = _buyFromReserve(amountEth);
        orderId = _openBuyOrder(amountEth);
        _makeHodler(msg.sender);
    }


/**************************************************************************************
 * Cancel orders
 **************************************************************************************/

function cancelSell(bytes32 orderId) external accrueByTime distribute ifRunning {
        SellOrder storage o = sellOrder[orderId];
        User storage u = userStruct[o.seller];
        require(o.seller == msg.sender, "Sender is not the seller.");
        u.balanceHodl = u.balanceHodl.add(o.volumeHodl);
        u.sellOrderIdFifo.remove(orderId);
        sellOrderIdFifo.remove(orderId);
        emit SellOrderCancelled(msg.sender, orderId);
    }


// Moves forward in 1-day steps to prevent overflow

function rates() public view returns(uint hodlUsd, uint dailyAccrualRate) {
        hodlUsd = HODL_USD;
        dailyAccrualRate = DAILY_ACCRUAL_RATE;
        uint startTime = BIRTHDAY.add(SLEEP_TIME);
        if(now > startTime) {
            uint daysFromStart = (now.sub(startTime)) / 1 days;
            uint daysUnprocessed = daysFromStart.sub(accrualDaysProcessed);
            if(daysUnprocessed > 0) {
                hodlUsd = HODL_USD.mul(DAILY_ACCRUAL_RATE).div(USD_PRECISION);
                dailyAccrualRate = DAILY_ACCRUAL_RATE.mul(DAILY_ACCRUAL_RATE_DECAY).div(USD_PRECISION);
            }
        }
    }


/**************************************************************************************
 * Initialization functions that support migration cannot be used after trading starts
 **************************************************************************************/

function initUser(address userAddr, uint hodl) external onlyOwner payable {
        User storage u = userStruct[userAddr];
        User storage r = userStruct[address(this)];
        u.balanceEth  = u.balanceEth.add(msg.value);
        u.balanceHodl = u.balanceHodl.add(hodl);
        r.balanceHodl = r.balanceHodl.sub(hodl);
        _makeHodler(userAddr);
        emit UserInitialized(msg.sender, userAddr, hodl, msg.value);
    }


/**
 * @dev Delegates execution to an implementation contract.
 * This is a low level function that doesn't return to its internal call site.
 * It will return to the external caller whatever the implementation returns.
 * @param implementation Address to delegate.
 */

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

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

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

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


// Create a new staking event

function createStakingEvent(uint256[] memory _cardIdList, uint256 _cardAmountAny, uint256[] memory _cardAmountList, uint256 _cardRewardId,
        uint256 _blockStakeLength, uint256 _blockEventClose, uint256[] memory _toBurnIdList, uint256[] memory _toBurnAmountList) public onlyOwner {

        require(_cardIdList.length > 0, "Accepted card list is empty");
        require(_cardAmountAny > 0 || _cardAmountList.length > 0, "Card amount required not specified");
        require(_blockEventClose > block.number, "blockEventClose < current block");
        require(_toBurnIdList.length == _toBurnAmountList.length, "ToBurn arrays have different length");
        require(_cardAmountAny == 0 || _toBurnIdList.length == 0, "ToBurn not supported with anyEvent");

        stakingEvents.push(StakingEvent({
        cardIdList: _cardIdList,
        cardAmountAny: _cardAmountAny,
        cardAmountList: _cardAmountList,
        cardRewardId: _cardRewardId,
        blockStakeLength: _blockStakeLength,
        blockEventClose: _blockEventClose,
        toBurnIdList: _toBurnIdList,
        toBurnAmountList: _toBurnAmountList
        }));

        emit StakingEventCreated(stakingEvents.length - 1);
    }


// Stake cards into a staking event

function stakeAny(uint256 _eventId, uint256[] memory _cardIdList, uint256[] memory _cardAmountList) public {
        require(_cardIdList.length == _cardAmountList.length, "Arrays have different length");

        StakingEvent storage _event = stakingEvents[_eventId];
        UserInfo storage _userInfo = userInfo[msg.sender][_eventId];

        require(block.number <= _event.blockEventClose, "Event is closed");
        require(_userInfo.isCompleted == false, "Address already completed event");
        require(_userInfo.blockEnd == 0, "Address already staked for this event");
        require(_event.cardAmountAny > 0, "Not a stakeAny event");

        for (uint256 i = 0; i < _cardIdList.length; i++) {
            require(_isInArray(_cardIdList[i], _event.cardIdList), "Card not accepted");
        }

        uint256 total = 0;
        for (uint256 i = 0; i < _cardAmountList.length; i++) {
            total = total.add(_cardAmountList[i]);
        }

        require(total == _event.cardAmountAny, "Wrong card total");

        pepemonFactory.safeBatchTransferFrom(msg.sender, address(this), _cardIdList, _cardAmountList, "");

        // Save list cards staked in storage
        for (uint256 i = 0; i < _cardIdList.length; i++) {
            uint256 cardId = _cardIdList[i];
            uint256 amount = _cardAmountList[i];

            cardsStaked[msg.sender][_eventId][cardId] = amount;
        }

        _userInfo.blockEnd = block.number.add(_event.blockStakeLength);

        emit StakingEventEntered(msg.sender, _eventId);
    }


// Add a new token to the pool. Can only be called by the owner.

function add(
        uint256 _allocPoint,
        IERC20 _token,
        bool _withUpdate
    ) external onlyOwner {
        require(!isTokenAdded(_token), "add: token already added");

        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock =
            block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);

        uint256 pid = poolInfo.length;
        poolInfo.push(
            PoolInfo({
                token: _token,
                allocPoint: _allocPoint,
                lastRewardBlock: lastRewardBlock,
                accRewardPerShare: 0
            })
        );
        poolPidByAddress[address(_token)] = pid;

        emit TokenAdded(address(_token), pid, _allocPoint);
    }


// claim rewards

function claim() external{
        uint256 i;
        for (i = 0; i < poolInfo.length; ++i) {
            updatePool(i);
            accrueReward(i);
            UserPoolInfo storage userPool = userPoolInfo[i][msg.sender];
            userPool.accruedReward = calcReward(poolInfo[i], userPool);
        }
        uint256 unlocked = calcUnlocked(rewards[msg.sender]).sub(claimedRewards[msg.sender]);
        if (unlocked > 0) {
            _safeRewardTransfer(msg.sender, unlocked);
        }
        claimedRewards[msg.sender] = claimedRewards[msg.sender].add(unlocked);
        emit Claimed(msg.sender, unlocked);
    }


// Deposit tokens to liquidity mining for reward token allocation.

function deposit(uint256 _pid, uint256 _amount) external {
        require(block.number <= endBlock, "LP mining has ended.");
        updatePool(_pid);
        accrueReward(_pid);

        UserPoolInfo storage userPool = userPoolInfo[_pid][msg.sender];

        if (_amount > 0) {
            poolInfo[_pid].token.safeTransferFrom(
                address(msg.sender),
                address(this),
                _amount
            );

            userPool.amount = userPool.amount.add(_amount);
        }

        userPool.accruedReward = calcReward(poolInfo[_pid], userPoolInfo[_pid][msg.sender]);
        emit Deposited(msg.sender, _pid, _amount);
    }


// Set unlocks infos - block number and quota.

function setUnlocks(uint256[] calldata _blocks, uint256[] calldata _quotas) external onlyOwner {
        require(_blocks.length == _quotas.length, "Should be same length");
        for (uint256 i = 0; i < _blocks.length; ++i) {
            unlocks.push(UnlockInfo(_blocks[i], _quotas[i]));
            unlocksTotalQuotation = unlocksTotalQuotation.add(_quotas[i]);
        }
    }


// Withdraw tokens from rewardToken liquidity mining.

function withdraw(uint256 _pid, uint256 _amount) external {
        require(userPoolInfo[_pid][msg.sender].amount >= _amount, "withdraw: not enough amount");
        updatePool(_pid);
        accrueReward(_pid);
        UserPoolInfo storage userPool = userPoolInfo[_pid][msg.sender];
        if (_amount > 0) {
            userPool.amount = userPool.amount.sub(_amount);
            poolInfo[_pid].token.safeTransfer(address(msg.sender), _amount);
        }

        userPool.accruedReward = calcReward(poolInfo[_pid], userPoolInfo[_pid][msg.sender]);
        emit Withdrawn(msg.sender, _pid, _amount);
    }


// Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)

function withdrawPartial(uint256 _amount)
    public
    onlyOwner
    {
        require(twoStage);
        // Check that amount is not more than total contract balance
        require(
        _amount > 0 && _amount <= address(this).balance,
        "Withdraw amount must be positive and not exceed total contract balance"
        );
        payable(msg.sender).transfer(_amount);
        twoStage = false;
    }


// Migrate token to another lp contract. Can be called by anyone.

function migrate(uint256 _pid) external {
        require(address(migrator) != address(0), "migrate: no migrator");
        PoolInfo storage pool = poolInfo[_pid];
        IERC20 token = pool.token;
        uint256 bal = token.balanceOf(address(this));
        token.safeApprove(address(migrator), bal);
        IERC20 newToken = migrator.migrate(token);
        require(bal == newToken.balanceOf(address(this)), "migrate: bad");
        pool.token = newToken;

        delete poolPidByAddress[address(token)];
        poolPidByAddress[address(newToken)] = _pid;

        emit TokenMigrated(address(token), address(newToken), _pid);
    }


/**
 * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
 * total shares and their previous withdrawals.
 */

function release(address payable account) public virtual {
        require(_shares[account] > 0, "PaymentSplitter: account has no shares");

        uint256 totalReceived = address(this).balance + totalReleased();
        uint256 payment = _pendingPayment(account, totalReceived, released(account));

        require(payment != 0, "PaymentSplitter: account is not due payment");

        _released[account] += payment;
        _totalReleased += payment;

        AddressUpgradeable.sendValue(account, payment);
        emit PaymentReleased(account, payment);
    }


// function to get claimable amount for any user

function getClaimableAmount(address _user) external view returns(uint256) {
        LockInfo[] memory lockInfoArrayForUser = lockInfoByUser[_user];
        uint256 totalTransferableAmount = 0;
        uint i;
        for (i=latestCounterByUser[_user]; i<lockInfoArrayForUser.length; i++){
            uint256 lockingPeriodHere = lockingPeriod;
            if(lockInfoArrayForUser[i]._isDev){
                lockingPeriodHere = devLockingPeriod;
            }
            if(now >= (lockInfoArrayForUser[i]._timestamp.add(lockingPeriodHere))){
                totalTransferableAmount = totalTransferableAmount.add(lockInfoArrayForUser[i]._amount);
            } else {
                break;
            }
        }
        return totalTransferableAmount;
    }


/**
 * @dev Add a new payee to the contract.
 * @param account The address of the payee to add.
 * @param shares_ The number of shares owned by the payee.
 */

function _addPayee(address account, uint256 shares_) private {
        require(account != address(0), "PaymentSplitter: account is the zero address");
        require(shares_ > 0, "PaymentSplitter: shares are 0");
        require(_shares[account] == 0, "PaymentSplitter: account already has shares");

        _payees.push(account);
        _shares[account] = shares_;
        _totalShares = _totalShares + shares_;
        emit PayeeAdded(account, shares_);
    }


/**
 * Add new star
 */

function addStar(address owner, uint8 gid, uint8 zIndex, uint16 box, uint8 inbox, uint8 stype, uint8 color, uint256 price) internal returns(uint256) {
        Star memory _star = Star({
            owner: owner,
            gid: gid, zIndex: zIndex, box: box, inbox: inbox,
            stype: stype, color: color,
            price: price, sell: 0, deleted: false, name: "", message: ""
        });
        uint256 starId = stars.push(_star) - 1;
        placeStar(gid, zIndex, box, starId);
        return starId;
    }


/**
 * Get star by id
 */

function getStar(uint256 starId) external view returns(address owner, uint8 gid, uint8 zIndex, uint16 box, uint8 inbox,
                                                           uint8 stype, uint8 color,
                                                           uint256 price, uint256 sell, bool deleted,
                                                           string name, string message) {
        Star storage _star = stars[starId];
        owner      = _star.owner;
        gid        = _star.gid;
        zIndex     = _star.zIndex;
        box        = _star.box;
        inbox      = _star.inbox;
        stype      = _star.stype;
        color      = _star.color;
        price      = _star.price;
        sell       = _star.sell;
        deleted    = _star.deleted;
        name       = _star.name;
        message    = _star.message;
    }


/**
 * Set validation data
 */

function setValidationData(uint16 _zMin, uint16 _zMax, uint8 _lName, uint8 _lMessage, uint8 _maxCT, uint8 _maxIR, uint16 _boxSize) external onlyOwner {
        zMin       = _zMin;
        zMax       = _zMax;
        lName      = _lName;
        lMessage   = _lMessage;
        maxCT      = _maxCT;
        maxIRandom = _maxIR;
        boxSize    = _boxSize;
        inboxXY    = uint8((boxSize * boxSize) / 4);
    }


/**
 * Get random star position
 */

function getRandomPosition(uint8 gid, uint8 zIndex) internal returns(uint16 box, uint8 inbox) {
        uint16 boxCount = getBoxCountZIndex(zIndex);
        uint16 randBox  = 0;
        if (boxCount == 0) revert();

        uint8 ii   = maxIRandom;
        bool valid = false;
        while (!valid && ii > 0) {
            randBox = getRandom16(0, boxCount);
            valid   = isValidBox(gid, zIndex, randBox);
            ii--;
        }

        if (!valid) revert();
        return(randBox, getRandom8(0, inboxXY));
    }


/**
 * Create star
 */

function createStar(uint8 gid, uint16 z, string name, string message) external payable {
        // Check basic requires
        require(isValidGid(gid));
        require(isValidZ(z));
        require(isValidNameLength(name));
        require(isValidMessageLength(message));

        // Get zIndex
        uint8   zIndex    = getZIndex(z);
        uint256 starPrice = getCreatePrice(z, getZCount(gid, zIndex));
        require(isValidMsgValue(starPrice));

        // Create star (need to split method into two because solidity got error - to deep stack)
        uint256 starId = newStar(gid, zIndex, starPrice);
        setStarNameMessage(starId, name, message);

        // Event and returns data
        emit StarCreated(starId);
    }


/**
 * Update start method
 */

function updateStar(uint256 starId, string name, string message) external payable {
        // Exists and owned star
        require(starExists(starId));
        require(isStarOwner(starId, msg.sender));

        // Check basic requires
        require(isValidNameLength(name));
        require(isValidMessageLength(message));

        // Get star update price
        uint256 commission = getCommission(stars[starId].price);
        require(isValidMsgValue(commission));

        // Update star
        setStarNameMessage(starId, name, message);
        emit StarUpdated(starId, 1);
    }


/**
 * Delete star
 */

function deleteStar(uint256 starId) external payable {
        // Exists and owned star
        require(starExists(starId));
        require(isStarOwner(starId, msg.sender));

        // Get star update price
        uint256 commission = getCommission(stars[starId].price);
        require(isValidMsgValue(commission));

        // Update star data
        setStarDeleted(starId);
        emit StarDeleted(starId, msg.sender);
    }


/**
 * Gift star
 */

function giftStar(uint256 starId, address recipient) external payable {
        // Check star exists owned
        require(starExists(starId));
        require(recipient != address(0));
        require(isStarOwner(starId, msg.sender));
        require(!isStarOwner(starId, recipient));

        // Get gift commission
        uint256 commission = getCommission(stars[starId].price);
        require(isValidMsgValue(commission));

        // Update star
        setStarNewOwner(starId, recipient);
        setStarSellPrice(starId, 0);
        emit StarGifted(starId, msg.sender, recipient);
        emit StarUpdated(starId, 3);
    }


/**
 * Buy star
 */

function buyStar(uint256 starId, string name, string message) external payable {
        // Exists and NOT owner
        require(starExists(starId));
        require(!isStarOwner(starId, msg.sender));
        require(stars[starId].sell > 0);

        // Get sell commission and check value
        uint256 commission = getCommission(stars[starId].price);
        uint256 starPrice  = stars[starId].sell;
        uint256 totalPrice = starPrice + commission;
        require(isValidMsgValue(totalPrice));

        // Transfer money to seller
        address seller = stars[starId].owner;
        seller.transfer(starPrice);

        // Update star data
        setStarNewOwner(starId, msg.sender);
        setStarSellPrice(starId, 0);
        setStarNameMessage(starId, name, message);
        emit StarSold(starId, seller, msg.sender, starPrice);
        emit StarUpdated(starId, 4);
    }


// Forward ERC20 methods to upgraded contract after the upgrade timestamp has been reached

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        if (now > upgradeTimestamp) {
            return UpgradedStandardToken(upgradeAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
        } else {
            return super.transferFrom(_from, _to, _value);
        }
    }


// Deposit LP tokens to TopDog for BONE allocation.

function deposit(uint256 _pid, uint256 _amount) public nonReentrant {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accBonePerShare).div(1e12).sub(user.rewardDebt);
            if(pending > 0) {
                // safeBoneTransfer(msg.sender, pending);
                uint256 sendAmount = pending.mul(rewardMintPercent).div(100);
                safeBoneTransfer(msg.sender, sendAmount);
                if(rewardMintPercent != 100) {
                    safeBoneTransfer(address(boneLocker), pending.sub(sendAmount)); // Rest amount sent to Bone token contract
                    boneLocker.lock(msg.sender, pending.sub(sendAmount), false); //function called for token time-lock
                }
            }
        }
        if(_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accBonePerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


/**
 * @notice Initiates a new rebase operation, provided the minimum time period has elapsed.
 *
 * @dev The supply adjustment equals (_totalSupply * DeviationFromTargetRate) / rebaseLag
 *      Where DeviationFromTargetRate is (MarketOracleRate - targetRate) / targetRate
 *      and targetRate is 1
 */

function rebase() external onlyOrchestrator {
        require(inRebaseWindow());

        // This comparison also ensures there is no reentrancy.
        require(lastRebaseTimestampSec.add(minRebaseTimeIntervalSec) < now);

        // Snap the rebase time to the start of this window.
        lastRebaseTimestampSec = now.sub(
            now.mod(minRebaseTimeIntervalSec)).add(rebaseWindowOffsetSec);

        epoch = epoch.add(1);

        uint256 targetRate = TARGET_RATE;

        uint256 exchangeRate;
        bool rateValid;
        (exchangeRate, rateValid) = marketOracle.getData();
        require(rateValid);

        if (exchangeRate > MAX_RATE) {
            exchangeRate = MAX_RATE;
        }

        int256 supplyDelta = computeSupplyDelta(exchangeRate, targetRate);

        // Apply the Dampening factor.
        supplyDelta = supplyDelta.div(rebaseLag.toInt256Safe());

        if (supplyDelta > 0 && uFrags.totalSupply().add(uint256(supplyDelta)) > MAX_SUPPLY) {
            supplyDelta = (MAX_SUPPLY.sub(uFrags.totalSupply())).toInt256Safe();
        }

        uint256 supplyAfterRebase = uFrags.rebase(epoch, supplyDelta);
        assert(supplyAfterRebase <= MAX_SUPPLY);
        emit LogRebase(epoch, exchangeRate, supplyDelta, now);
    }


// Method which is used to mint during the standard minting window

function mint(uint256 _amountMinted, address _recipient)
        public
        payable
        nonReentrant
    {
        if (owner() != _msgSender()) {
            require(
                saleTime != 0 && saleTime <= block.timestamp,
                "ERC721: Sale is paused..."
            );
        }
        require(totalSupply() < supplyCap, "ERC721: Sale is finished.");
        require(
            _amountMinted > 0 && _amountMinted <= perTxLimit,
            "ERC721: You may only mint up to 20 Empty Buns."
        );
        require(
            totalSupply() + _amountMinted < supplyCap,
            "ERC721: Transaction would exceed supply cap."
        );
        require(
            msg.value >= getTokenPrice(true) * _amountMinted,
            "ERC721: Incorrect value sent."
        );

        // Prevent nefarious contracts from bypassing limits
        require(
            tx.origin == _msgSender(),
            "ERC721: The caller is another contract."
        );

        for (uint256 i = 0; i < _amountMinted; i++) {
            _safeMint(_recipient, tokenCounter);
            tokenCounter++;
        }
    }


// Public method which retrieves holders inventory of tokens

function getHoldersTokens(address _holder)
        public
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_holder);

        uint256[] memory tokensId = new uint256[](tokenCount);

        for (uint256 i; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_holder, i);
        }

        return tokensId;
    }


// BID
// Lets player pick number
// Locks his potential winnings

function Bid(uint256 _number) payable public {
        require(now < timerEnd, "game is over!");
        require(msg.value > bid, "not enough to beat current leader");
        require(_number >= numberMin, "number too low");
        require(_number <= numberMax, "number too high");

        bid = msg.value;
        pot = pot.add(msg.value);
        shareToWinner = ComputeShare();
        uint256 _share = 100;
        shareToThrone = _share.sub(shareToWinner);
        leader = msg.sender;
        number = _number;

        emit GameBid(msg.sender, msg.value, number, pot, shareToWinner);
    }


// END
// Can be called manually after the game ends
// Sends pot to winner and snailthrone

function End() public {
        require(now > timerEnd, "game is still running!");

        uint256 _throneReward = pot.mul(shareToThrone).div(100);
        pot = pot.sub(_throneReward);
        (bool success, bytes memory data) = SNAILTHRONE.call.value(_throneReward)("");
        require(success);

        uint256 _winnerReward = pot;
        pot = 0;
        leader.transfer(_winnerReward);

        emit GameEnd(leader, _winnerReward, _throneReward, number);
    }


// ref: https://blogs.sas.com/content/iml/2016/05/16/babylonian-square-roots.html

function sqrt(uint256 x) internal pure returns (uint256 y) {
        uint256 z = (x + 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }


/**
 * @notice Hash an order into bytes32
 * @dev EIP-191 header and domain separator included
 * @param order Order The order to be hashed
 * @param domainSeparator bytes32
 * @return bytes32 A keccak256 abi.encodePacked value
 */

function hashOrder(
    Order calldata order,
    bytes32 domainSeparator
  ) external pure returns (bytes32) {
    return keccak256(abi.encodePacked(
      EIP191_HEADER,
      domainSeparator,
      keccak256(abi.encode(
        ORDER_TYPEHASH,
        order.nonce,
        order.expiry,
        keccak256(abi.encode(
          PARTY_TYPEHASH,
          order.signer.kind,
          order.signer.wallet,
          order.signer.token,
          order.signer.amount,
          order.signer.id
        )),
        keccak256(abi.encode(
          PARTY_TYPEHASH,
          order.sender.kind,
          order.sender.wallet,
          order.sender.token,
          order.sender.amount,
          order.sender.id
        )),
        keccak256(abi.encode(
          PARTY_TYPEHASH,
          order.affiliate.kind,
          order.affiliate.wallet,
          order.affiliate.token,
          order.affiliate.amount,
          order.affiliate.id
        ))
      ))
    ));
  }


/**
 * @notice Send an Order to be forwarded to Swap
 * @dev Sender must authorize this contract on the swapContract
 * @dev Sender must approve this contract on the wethContract
 * @param order Types.Order The Order
 */

function swap(
    Types.Order calldata order
  ) external payable {
    // Ensure msg.sender is sender wallet.
    require(order.sender.wallet == msg.sender,
      "MSG_SENDER_MUST_BE_ORDER_SENDER");
    // Ensure that the signature is present.
    // The signature will be explicitly checked in Swap.
    require(order.signature.v != 0,
      "SIGNATURE_MUST_BE_SENT");
    // Wraps ETH to WETH when the sender provides ETH and the order is WETH
    _wrapEther(order.sender);
    // Perform the swap.
    swapContract.swap(order);
    // Unwraps WETH to ETH when the sender receives WETH
    _unwrapEther(order.sender.wallet, order.signer.token, order.signer.amount);
  }


// Withdraw without caring about rewards. EMERGENCY ONLY.

function emergencyWithdraw(uint amount) public shouldStarted {
        require(amount <= _stakerTokenBalance[msg.sender] && _stakerTokenBalance[msg.sender] > 0, "Bad withdraw.");

        if(_stakerStakingPlan[msg.sender] == 4 || _stakerStakingPlan[msg.sender] == 5){
            require(block.timestamp >= _stakerStakingTime[msg.sender] + 30 days, "Early withdrawal available after 30 days");
        }

        updateRewards(msg.sender);

        _stakerTokenBalance[msg.sender] = _stakerTokenBalance[msg.sender].sub(amount);
        _stakerTokenRewardsClaimed[msg.sender] = 0;
        _stakerStakingPlan[msg.sender] = 0;

        if(_stakerWithdrawFeeRate[msg.sender] > 0){
            uint256 _burnedAmount = amount.mul(_stakerWithdrawFeeRate[msg.sender]).div(100);
            amount = amount.sub(_burnedAmount);
            token.burn(_burnedAmount);
        }

        token.safeTransfer(msg.sender, amount);

        emit Withdraw(msg.sender, amount);
    }


/**
 * @notice Send an Order to be forwarded to a Delegate
 * @dev Sender must authorize the Delegate contract on the swapContract
 * @dev Sender must approve this contract on the wethContract
 * @dev Delegate's tradeWallet must be order.sender - checked in Delegate
 * @param order Types.Order The Order
 * @param delegate IDelegate The Delegate to provide the order to
 */

function provideDelegateOrder(
    Types.Order calldata order,
    IDelegate delegate
  ) external payable {
    // Ensure that the signature is present.
    // The signature will be explicitly checked in Swap.
    require(order.signature.v != 0,
      "SIGNATURE_MUST_BE_SENT");
    // Wraps ETH to WETH when the signer provides ETH and the order is WETH
    _wrapEther(order.signer);
    // Provide the order to the Delegate.
    delegate.provideOrder(order);
    // Unwraps WETH to ETH when the signer receives WETH
    _unwrapEther(order.signer.wallet, order.sender.token, order.sender.amount);
  }


// Return accumulate rewards over the given _fromTime to _toTime.

function getGeneratedReward(uint256 _fromTime, uint256 _toTime) public view returns (uint256) {
        for (uint8 epochId = 2; epochId >= 1; --epochId) {
            if (_toTime >= epochEndTimes[epochId - 1]) {
                if (_fromTime >= epochEndTimes[epochId - 1]) {
                    return _toTime.sub(_fromTime).mul(epochApePerSecond[epochId]);
                }

                uint256 _generatedReward = _toTime.sub(epochEndTimes[epochId - 1]).mul(epochApePerSecond[epochId]);
                if (epochId == 1) {
                    return _generatedReward.add(epochEndTimes[0].sub(_fromTime).mul(epochApePerSecond[0]));
                }
                for (epochId = epochId - 1; epochId >= 1; --epochId) {
                    if (_fromTime >= epochEndTimes[epochId - 1]) {
                        return _generatedReward.add(epochEndTimes[epochId].sub(_fromTime).mul(epochApePerSecond[epochId]));
                    }
                    _generatedReward = _generatedReward.add(epochEndTimes[epochId].sub(epochEndTimes[epochId - 1]).mul(epochApePerSecond[epochId]));
                }
                return _generatedReward.add(epochEndTimes[0].sub(_fromTime).mul(epochApePerSecond[0]));
            }
        }
        return _toTime.sub(_fromTime).mul(epochApePerSecond[0]);
    }


/**
 * @notice Wraps ETH to WETH when a trade requires it
 * @param party Types.Party The side of the trade that may need wrapping
 */

function _wrapEther(Types.Party memory party) internal {
    // Check whether ether needs wrapping
    if (party.token == address(wethContract)) {
      // Ensure message value is param.
      require(party.amount == msg.value,
        "VALUE_MUST_BE_SENT");
      // Wrap (deposit) the ether.
      wethContract.deposit.value(msg.value)();
      // Transfer the WETH from the wrapper to party.
      // Return value not checked - WETH throws on error and does not return false
      wethContract.transfer(party.wallet, party.amount);
    } else {
      // Ensure no unexpected ether is sent.
      require(msg.value == 0,
        "VALUE_MUST_BE_ZERO");
    }
  }


/**
 * @notice Unwraps WETH to ETH when a trade requires it
 * @dev The unwrapping only succeeds if recipientWallet has approved transferFrom
 * @param recipientWallet address The trade recipient, who may have received WETH
 * @param receivingToken address The token address the recipient received
 * @param amount uint256 The amount of token the recipient received
 */

function _unwrapEther(address recipientWallet, address receivingToken, uint256 amount) internal {
    // Check whether ether needs unwrapping
    if (receivingToken == address(wethContract)) {
      // Transfer weth from the recipient to the wrapper.
      wethContract.transferFrom(recipientWallet, address(this), amount);
      // Unwrap (withdraw) the ether.
      wethContract.withdraw(amount);
      // Transfer ether to the recipient.
      // solium-disable-next-line security/no-call-value
      (bool success, ) = recipientWallet.call.value(amount)("");
      require(success, "ETH_RETURN_FAILED");
    }
  }


// Deposit LP tokens.

function deposit(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 _pending = user.amount.mul(pool.accApePerShare).div(1e18).sub(user.rewardDebt);
            if (_pending > 0) {
                safeApeTransfer(_sender, _pending);
                emit RewardPaid(_sender, _pending);
            }
        }
        if (_amount > 0) {
            pool.token.safeTransferFrom(_sender, address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accApePerShare).div(1e18);
        emit Deposit(_sender, _pid, _amount);
    }


// Withdraw LP tokens.

function withdraw(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 _pending = user.amount.mul(pool.accApePerShare).div(1e18).sub(user.rewardDebt);
        if (_pending > 0) {
            safeApeTransfer(_sender, _pending);
            emit RewardPaid(_sender, _pending);
        }
        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.token.safeTransfer(_sender, _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accApePerShare).div(1e18);
        emit Withdraw(_sender, _pid, _amount);
    }


// Safe ape transfer function, just in case if rounding error causes pool to not have enough Apes.

function safeApeTransfer(address _to, uint256 _amount) internal {
        uint256 _apeBal = ape.balanceOf(address(this));
        if (_apeBal > 0) {
            if (_amount > _apeBal) {
                ape.safeTransfer(_to, _apeBal);
            } else {
                ape.safeTransfer(_to, _amount);
            }
        }
    }


// Transfer amount of tokens from sender account to recipient.
// Only callable after the crowd fund is completed

function transfer(address _to, uint _value)
  {
    if (_to == msg.sender) return; // no-op, allow even during crowdsale, in order to work around using grantVestedTokens() while in crowdsale
   // if (!isCrowdfundCompleted()) throw;
    super.transfer(_to, _value);
  }


//constant function returns the current XFM price.

function getPriceRate()
      constant
      returns (uint o_rate)
  {
      uint delta = now;
      if (delta < PRESALE_START_WEEK1) return PRICE_PRESALE_START;
      if (delta < PRESALE_START_WEEK2) return PRICE_PRESALE_WEEK1;
      if (delta < PRESALE_START_WEEK3) return PRICE_PRESALE_WEEK2;
      if (delta < CROWDSALE_START) return PRICE_PRESALE_WEEK3;
      return (PRICE_CROWDSALE);
  }


// Returns `amount` in scope as the number of XFM tokens that it will purchase.

function processPurchase(uint _rate, uint _remaining)
    internal
    returns (uint o_amount)
  {
    o_amount = calcAmount(msg.value, _rate);

    if (o_amount > _remaining) throw;
    if (!multisigAddress.send(msg.value)) throw;

    balances[ownerAddress] = balances[ownerAddress].sub(o_amount);
    balances[msg.sender] = balances[msg.sender].add(o_amount);

    XFMSold += o_amount;
    etherRaised += msg.value;
  }


// To be called at the end of crowdfund period
// WARNING: transfer(), which is called by grantVestedTokens(), wants a minimum message length

function grantVested(address _XferMoneyTeamAddress, address _XferMoneyFundAddress)
    is_crowdfund_completed
    only_owner
    is_not_halted
  {
    // Grant tokens pre-allocated for the team
    grantVestedTokens(
      _XferMoneyTeamAddress, ALLOC_TEAM,
      uint64(now), uint64(now) + 91 days , uint64(now) + 365 days,
      false, false
    );

    // Grant tokens that remain after crowdsale to the XferMoney fund, vested for 2 years
    grantVestedTokens(
      _XferMoneyFundAddress, balances[ownerAddress],
      uint64(now), uint64(now) + 182 days , uint64(now) + 730 days,
      false, false
    );
  }


// List every tokens an owner owns

function tokensOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }


// Before any transfer, add the new owner to the holders list of this token

function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        // Save the holder only once per token
        if (!_alreadyHoldToken[tokenId][to]) {
            cardInfo[tokenId].holders.push(to);
            _alreadyHoldToken[tokenId][to] = true;
        }
    }


//Safe Maths

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


/* Buy Token 1 token for x ether */

function buyTokens() public whenCrowdsaleNotPaused payable {
        require(!accounts[msg.sender].blacklisted);
        require(msg.value > 0);
        require(msg.value >= _tokenPrice);
        require(msg.value % _tokenPrice == 0);
        var numTokens = msg.value / _tokenPrice;
        require(numTokens >= _minimumTokens);
        balanceOf[msg.sender] += numTokens;
        Transfer(0, msg.sender, numTokens);
        wallet.transfer(msg.value);
        accounts[msg.sender].balance = accounts[msg.sender].balance.add(numTokens);
        insertShareholder(msg.sender);
        if (msg.sender != owner) {
            totalSupply += numTokens;
        }
    }


/**
 * @dev Function to mint tokens: uri must be is unique and msg.value sufficient
 * @param to The address that will receive the minted tokens.
 * @param tokenURI The token URI of the minted token.
 * @return A boolean that indicates if the operation was successful.
 */

function mintWithTokenURI(address to, string memory tokenURI) public payable returns (bool) {
        require(_enabled == true, "Minting has been disabled");

        // Confirm payment
        require(msg.value == mintPrice, "");

        uint256 tokenId = ++_totalMinted;

        require(bytes(tokenURI).length > 0, "Invalid tokenURI");

        // Set and validate tokenURI unique
        _mapTokenURIToId(tokenURI, tokenId);

        _mint(to, tokenId);
        _setTokenURI(tokenId, tokenURI);
        return true;
    }


//An identifier: eg SBX

function scearu(
        uint256 _initialAmount,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol
    ) public {
        balances[msg.sender] = _initialAmount;               // Give the creator all initial tokens
        totalSupply = _initialAmount;                        // Update total supply
        name = _tokenName;                                   // Set the name for display purposes
        decimals = _decimalUnits;                            // Amount of decimals for display purposes
        symbol = _tokenSymbol;                               // Set the symbol for display purposes
    }


/// @notice Harvest profits while preventing a sandwich attack exploit.
/// @param maxBalance The maximum balance of the underlying token that is allowed to be in BentoBox.
/// @param rebalance Whether BentoBox should rebalance the strategy assets to acheive it's target allocation.
/// @param maxChangeAmount When rebalancing - the maximum amount that will be deposited to or withdrawn from a strategy to BentoBox.
/// @param harvestRewards If we want to claim any accrued reward tokens
/// @dev maxBalance can be set to 0 to keep the previous value.
/// @dev maxChangeAmount can be set to 0 to allow for full rebalancing.

function safeHarvest(
        uint256 maxBalance,
        bool rebalance,
        uint256 maxChangeAmount,
        bool harvestRewards
    ) external onlyExecutor {
        if (harvestRewards) {
            _harvestRewards();
        }

        if (maxBalance > 0) {
            maxBentoBoxBalance = maxBalance;
        }

        IBentoBoxMinimal(bentoBox).harvest(strategyToken, rebalance, maxChangeAmount);
    }


// Deposit LP tokens to TravelAgency for CITY allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accCityPerShare).div(1e12).sub(user.rewardDebt);
            safeCityTransfer(msg.sender, pending);
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accCityPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


/// @inheritdoc IStrategy

function withdraw(uint256 amount) external override isActive onlyBentoBox returns (uint256 actualAmount) {
        _withdraw(amount);
        /// @dev Make sure we send and report the exact same amount of tokens by using balanceOf.
        actualAmount = IERC20(strategyToken).balanceOf(address(this));
        IERC20(strategyToken).safeTransfer(bentoBox, actualAmount);
    }


/// @inheritdoc IStrategy
/// @dev do not use isActive modifier here; allow bentobox to call strategy.exit() multiple times

function exit(uint256 balance) external override onlyBentoBox returns (int256 amountAdded) {
        _exit();
        /// @dev Check balance of token on the contract.
        uint256 actualBalance = IERC20(strategyToken).balanceOf(address(this));
        /// @dev Calculate tokens added (or lost).
        amountAdded = int256(actualBalance) - int256(balance);
        /// @dev Transfer all tokens to bentoBox.
        IERC20(strategyToken).safeTransfer(bentoBox, actualBalance);
        /// @dev Flag as exited, allowing the owner to manually deal with any amounts available later.
        exited = true;
    }


//***************************** TRANSFER TOKENS FROM YOUR ACCOUNT **************

function transfer(address _to, uint256 _val)
public returns (bool) {
require(holders[msg.sender] >= _val);
require(msg.sender != _to);
assert(_val <= holders[msg.sender]);
holders[msg.sender] = holders[msg.sender] - _val;
holders[_to] = holders[_to] + _val;
assert(holders[_to] >= _val);
emit Transfer(msg.sender, _to, _val);
return true;
}


//**************************** TRANSFER TOKENS FROM ANOTHER ACCOUNT ************

function transferFrom(address _from, address _to, uint256 _val)
public returns (bool) {
require(holders[_from] >= _val);
require(approach[_from][msg.sender] >= _val);
assert(_val <= holders[_from]);
holders[_from] = holders[_from] - _val;
assert(_val <= approach[_from][msg.sender]);
approach[_from][msg.sender] = approach[_from][msg.sender] - _val;
holders[_to] = holders[_to] + _val;
assert(holders[_to] >= _val);
emit Transfer(_from, _to, _val);
return true;
}