/// @dev Assigns ownership of a specific deed to an address.
/// @param _from The address to transfer the deed from.
/// @param _to The address to transfer the deed to.
/// @param _deedId The identifier of the deed to transfer.

function _transfer(address _from, address _to, uint256 _deedId) internal {
        // The number of plots is capped at 2^16 * 2^16, so this cannot
        // be overflowed.
        ownershipDeedCount[_to]++;

        // Transfer ownership.
        identifierToOwner[_deedId] = _to;

        // When a new deed is minted, the _from address is 0x0, but we
        // do not track deed ownership of 0x0.
        if (_from != address(0)) {
            ownershipDeedCount[_from]--;

            // Clear taking ownership approval.
            delete identifierToApproved[_deedId];
        }

        // Emit the transfer event.
        Transfer(_from, _to, _deedId);
    }


/// @notice Approve a given address to take ownership of multiple deeds.
/// @param _to The address to approve taking ownership.
/// @param _deedIds The identifiers of the deeds to give approval for.

function approveMultiple(address _to, uint256[] _deedIds) public whenNotPaused {
        // Ensure the sender is not approving themselves.
        require(msg.sender != _to);

        for (uint256 i = 0; i < _deedIds.length; i++) {
            uint256 _deedId = _deedIds[i];

            // Require the sender is the owner of the deed.
            require(_owns(msg.sender, _deedId));

            // Perform the approval.
            _approve(msg.sender, _to, _deedId);
        }
    }


/// @notice Transfers multiple deeds to another address. If transferring to
/// a smart contract be VERY CAREFUL to ensure that it is aware of ERC-721,
/// or your deeds may be lost forever.
/// @param _to The address of the recipient, can be a user or contract.
/// @param _deedIds The identifiers of the deeds to transfer.

function transferMultiple(address _to, uint256[] _deedIds) public whenNotPaused {
        // Safety check to prevent against an unexpected 0x0 default.
        require(_to != address(0));

        // Disallow transfers to this contract to prevent accidental misuse.
        require(_to != address(this));

        for (uint256 i = 0; i < _deedIds.length; i++) {
            uint256 _deedId = _deedIds[i];

            // One can only transfer their own plots.
            require(_owns(msg.sender, _deedId));

            // Transfer ownership
            _transfer(msg.sender, _to, _deedId);
        }
    }


/// @notice Transfer multiple deeds owned by another address, for which the
/// calling address has previously been granted transfer approval by the owner.
/// @param _deedIds The identifier of the deed to be transferred.

function takeOwnershipMultiple(uint256[] _deedIds) public whenNotPaused {
        for (uint256 i = 0; i < _deedIds.length; i++) {
            uint256 _deedId = _deedIds[i];
            address _from = identifierToOwner[_deedId];

            // Check for transfer approval
            require(_approvedFor(msg.sender, _deedId));

            // Reassign ownership (also clears pending approvals and emits Transfer event).
            _transfer(_from, msg.sender, _deedId);
        }
    }


/// @notice Returns a list of all deed identifiers assigned to an address.
/// @param _owner The owner whose deeds we are interested in.
/// @dev This method MUST NEVER be called by smart contract code. It's very
/// expensive and is not supported in contract-to-contract calls as it returns
/// a dynamic array (only supported for web3 calls).

function deedsOfOwner(address _owner) external view returns(uint256[]) {
        uint256 deedCount = countOfDeedsByOwner(_owner);

        if (deedCount == 0) {
            // Return an empty array.
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](deedCount);
            uint256 totalDeeds = countOfDeeds();
            uint256 resultIndex = 0;

            for (uint256 deedNumber = 0; deedNumber < totalDeeds; deedNumber++) {
                uint256 identifier = plots[deedNumber];
                if (identifierToOwner[identifier] == _owner) {
                    result[resultIndex] = identifier;
                    resultIndex++;
                }
            }

            return result;
        }
    }


/// @notice Returns a deed identifier of the owner at the given index.
/// @param _owner The address of the owner we want to get a deed for.
/// @param _index The index of the deed we want.

function deedOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) {
        // The index should be valid.
        require(_index < countOfDeedsByOwner(_owner));

        // Loop through all plots, accounting the number of plots of the owner we've seen.
        uint256 seen = 0;
        uint256 totalDeeds = countOfDeeds();

        for (uint256 deedNumber = 0; deedNumber < totalDeeds; deedNumber++) {
            uint256 identifier = plots[deedNumber];
            if (identifierToOwner[identifier] == _owner) {
                if (seen == _index) {
                    return identifier;
                }

                seen++;
            }
        }
    }


/// @notice Returns an (off-chain) metadata url for the given deed.
/// @param _deedId The identifier of the deed to get the metadata
/// url for.
/// @dev Implementation of optional ERC-721 functionality.

function deedUri(uint256 _deedId) external pure returns (string uri) {
        require(validIdentifier(_deedId));

        var (x, y) = identifierToCoordinate(_deedId);

        // Maximum coordinate length in decimals is 5 (65535)
        uri = "https://dworld.io/plot/xxxxx/xxxxx";
        bytes memory _uri = bytes(uri);

        for (uint256 i = 0; i < 5; i++) {
            _uri[27 - i] = byte(48 + (x / 10 ** i) % 10);
            _uri[33 - i] = byte(48 + (y / 10 ** i) % 10);
        }
    }


/// @dev Rent out a deed to an address.
/// @param _to The address to rent the deed out to.
/// @param _rentPeriod The rent period in seconds.
/// @param _deedId The identifier of the deed to rent out.

function _rentOut(address _to, uint256 _rentPeriod, uint256 _deedId) internal {
        // Set the renter and rent period end timestamp
        uint256 rentPeriodEndTimestamp = now.add(_rentPeriod);
        identifierToRenter[_deedId] = _to;
        identifierToRentPeriodEndTimestamp[_deedId] = rentPeriodEndTimestamp;

        Rent(_to, _deedId, rentPeriodEndTimestamp, _rentPeriod);
    }


/// @notice Rents multiple plots out to another address.
/// @param _to The address of the renter, can be a user or contract.
/// @param _rentPeriod The rent time period in seconds.
/// @param _deedIds The identifiers of the plots to rent out.

function rentOutMultiple(address _to, uint256 _rentPeriod, uint256[] _deedIds) public whenNotPaused {
        // Safety check to prevent against an unexpected 0x0 default.
        require(_to != address(0));

        // Disallow transfers to this contract to prevent accidental misuse.
        require(_to != address(this));

        for (uint256 i = 0; i < _deedIds.length; i++) {
            uint256 _deedId = _deedIds[i];

            require(validIdentifier(_deedId));

            // There should not be an active renter.
            require(identifierToRentPeriodEndTimestamp[_deedId] < now);

            // One can only rent out their own plots.
            require(_owns(msg.sender, _deedId));

            _rentOut(_to, _rentPeriod, _deedId);
        }
    }


/// @notice Returns the address of the currently assigned renter and
/// end time of the rent period of a given plot.
/// @param _deedId The identifier of the deed to get the renter and
/// rent period for.

function renterOf(uint256 _deedId) external view returns (address _renter, uint256 _rentPeriodEndTimestamp) {
        require(validIdentifier(_deedId));

        if (identifierToRentPeriodEndTimestamp[_deedId] < now) {
            // There is no active renter
            _renter = address(0);
            _rentPeriodEndTimestamp = 0;
        } else {
            _renter = identifierToRenter[_deedId];
            _rentPeriodEndTimestamp = identifierToRentPeriodEndTimestamp[_deedId];
        }
    }


/// @dev Calculate the current price of an auction.

function _currentPrice(Auction storage _auction) internal view returns (uint256) {
        require(now >= _auction.startedAt);

        uint256 secondsPassed = now - _auction.startedAt;

        if (secondsPassed >= _auction.duration) {
            return _auction.endPrice;
        } else {
            // Negative if the end price is higher than the start price!
            int256 totalPriceChange = int256(_auction.endPrice) - int256(_auction.startPrice);

            // Calculate the current price based on the total change over the entire
            // auction duration, and the amount of time passed since the start of the
            // auction.
            int256 currentPriceChange = totalPriceChange * int256(secondsPassed) / int256(_auction.duration);

            // Calculate the final price. Note this once again
            // is representable by a uint256, as the price can
            // never be negative.
            int256 price = int256(_auction.startPrice) + currentPriceChange;

            // This never throws.
            assert(price >= 0);

            return uint256(price);
        }
    }


/// @dev Calculate the fee for a given price.
/// @param _price The price to calculate the fee for.

function _calculateFee(uint256 _price) internal view returns (uint256) {
        // _price is guaranteed to fit in a uint128 due to the createAuction entry
        // modifiers, so this cannot overflow.
        return _price * fee / 100000;
    }


/// @notice Get the auction for the given deed.
/// @param _deedId The identifier of the deed to get the auction for.
/// @dev Throws if there is no auction for the given deed.

function getAuction(uint256 _deedId) external view returns (
            address seller,
            uint256 startPrice,
            uint256 endPrice,
            uint256 duration,
            uint256 startedAt
        )
    {
        Auction storage auction = identifierToAuction[_deedId];

        // The auction must be active
        require(_activeAuction(auction));

        return (
            auction.seller,
            auction.startPrice,
            auction.endPrice,
            auction.duration,
            auction.startedAt
        );
    }


/// @notice Create an auction for a given deed.
/// Must previously have been given approval to take ownership of the deed.
/// @param _deedId The identifier of the deed to create an auction for.
/// @param _startPrice The starting price of the auction.
/// @param _endPrice The ending price of the auction.
/// @param _duration The duration in seconds of the dynamic pricing part of the auction.

function createAuction(uint256 _deedId, uint256 _startPrice, uint256 _endPrice, uint256 _duration)
        public
        fitsIn128Bits(_startPrice)
        fitsIn128Bits(_endPrice)
        fitsIn64Bits(_duration)
        whenNotPaused
    {
        // Get the owner of the deed to be auctioned
        address deedOwner = deedContract.ownerOf(_deedId);

        // Caller must either be the deed contract or the owner of the deed
        // to prevent abuse.
        require(
            msg.sender == address(deedContract) ||
            msg.sender == deedOwner
        );

        // The duration of the auction must be at least 60 seconds.
        require(_duration >= 60);

        // Throws if placing the deed in escrow fails (the contract requires
        // transfer approval prior to creating the auction).
        _escrow(_deedId);

        // Auction struct
        Auction memory auction = Auction(
            deedOwner,
            uint128(_startPrice),
            uint128(_endPrice),
            uint64(_duration),
            uint64(now)
        );

        _createAuction(_deedId, auction);
    }


/// @notice Cancel an auction
/// @param _deedId The identifier of the deed to cancel the auction for.

function cancelAuction(uint256 _deedId) external whenNotPaused {
        Auction storage auction = identifierToAuction[_deedId];

        // The auction must be active.
        require(_activeAuction(auction));

        // The auction can only be cancelled by the seller
        require(msg.sender == auction.seller);

        _cancelAuction(_deedId, auction);
    }


/// @notice Withdraw ether owed to a beneficiary.
/// @param beneficiary The address to withdraw the auction balance for.

function withdrawAuctionBalance(address beneficiary) external {
        // The sender must either be the beneficiary or the core deed contract.
        require(
            msg.sender == beneficiary ||
            msg.sender == address(deedContract)
        );

        uint256 etherOwed = addressToEtherOwed[beneficiary];

        // Ensure ether is owed to the beneficiary.
        require(etherOwed > 0);

        // Set ether owed to 0
        delete addressToEtherOwed[beneficiary];

        // Subtract from total outstanding balance. etherOwed is guaranteed
        // to be less than or equal to outstandingEther, so this cannot
        // underflow.
        outstandingEther -= etherOwed;

        // Transfer ether owed to the beneficiary (not susceptible to re-entry
        // attack, as the ether owed is set to 0 before the transfer takes place).
        beneficiary.transfer(etherOwed);
    }


/// @notice Withdraw (unowed) contract balance.

function withdrawFreeBalance() external {
        // Calculate the free (unowed) balance. This never underflows, as
        // outstandingEther is guaranteed to be less than or equal to the
        // contract balance.
        uint256 freeBalance = this.balance - outstandingEther;

        address deedContractAddress = address(deedContract);

        require(
            msg.sender == owner ||
            msg.sender == deedContractAddress
        );

        deedContractAddress.transfer(freeBalance);
    }


/// @notice Create an auction for a given deed. Be careful when calling
/// createAuction for a RentAuction, that this overloaded function (including
/// the _rentPeriod parameter) is used. Otherwise the rent period defaults to
/// a week.
/// Must previously have been given approval to take ownership of the deed.
/// @param _deedId The identifier of the deed to create an auction for.
/// @param _startPrice The starting price of the auction.
/// @param _endPrice The ending price of the auction.
/// @param _duration The duration in seconds of the dynamic pricing part of the auction.
/// @param _rentPeriod The rent period in seconds being auctioned.

function createAuction(
        uint256 _deedId,
        uint256 _startPrice,
        uint256 _endPrice,
        uint256 _duration,
        uint256 _rentPeriod
    )
        external
    {
        // Require the rent period to be at least one hour.
        require(_rentPeriod >= 3600);

        // Require there to be no active renter.
        DWorldRenting dWorldRentingContract = DWorldRenting(deedContract);
        var (renter,) = dWorldRentingContract.renterOf(_deedId);
        require(renter == address(0));

        // Set the rent period.
        identifierToRentPeriod[_deedId] = _rentPeriod;

        // Throws (reverts) if creating the auction fails.
        createAuction(_deedId, _startPrice, _endPrice, _duration);
    }


/// @dev Perform the bid win logic (in this case: give renter status to the winner).
/// @param _seller The address of the seller.
/// @param _winner The address of the winner.
/// @param _deedId The identifier of the deed.
/// @param _price The price the auction was bought at.

function _winBid(address _seller, address _winner, uint256 _deedId, uint256 _price) internal {
        DWorldRenting dWorldRentingContract = DWorldRenting(deedContract);

        uint256 rentPeriod = identifierToRentPeriod[_deedId];
        if (rentPeriod == 0) {
            rentPeriod = 604800; // 1 week by default
        }

        // Rent the deed out to the winner.
        dWorldRentingContract.rentOut(_winner, identifierToRentPeriod[_deedId], _deedId);

        // Transfer the deed back to the seller.
        _transfer(_seller, _deedId);
    }


/// @notice Create a sale auction.
/// @param _deedId The identifier of the deed to create a sale auction for.
/// @param _startPrice The starting price of the sale auction.
/// @param _endPrice The ending price of the sale auction.
/// @param _duration The duration in seconds of the dynamic pricing part of the sale auction.

function createSaleAuction(uint256 _deedId, uint256 _startPrice, uint256 _endPrice, uint256 _duration)
        external
        whenNotPaused
    {
        require(_owns(msg.sender, _deedId));

        // Prevent creating a sale auction if no sale auction contract is configured.
        require(address(saleAuctionContract) != address(0));

        // Approve the deed for transferring to the sale auction.
        _approve(msg.sender, address(saleAuctionContract), _deedId);

        // Auction contract checks input values (throws if invalid) and places the deed into escrow.
        saleAuctionContract.createAuction(_deedId, _startPrice, _endPrice, _duration);
    }


/// @notice Allow withdrawing balances from the auction contracts
/// in a single step.

function withdrawAuctionBalances() external {
        // Withdraw from the sale contract if the sender is owed Ether.
        if (saleAuctionContract.addressToEtherOwed(msg.sender) > 0) {
            saleAuctionContract.withdrawAuctionBalance(msg.sender);
        }

        // Withdraw from the rent contract if the sender is owed Ether.
        if (rentAuctionContract.addressToEtherOwed(msg.sender) > 0) {
            rentAuctionContract.withdrawAuctionBalance(msg.sender);
        }
    }


/// @notice Set the data associated with a plot.

function setPlotData(uint256 _deedId, string name, string description, string imageUrl, string infoUrl)
        public
        whenNotPaused
    {
        // The sender requesting the data update should be
        // the owner (without an active renter) or should
        // be the active renter.
        require(_owns(msg.sender, _deedId) && identifierToRentPeriodEndTimestamp[_deedId] < now || _rents(msg.sender, _deedId));

        // Set the data
        _setPlotData(_deedId, name, description, imageUrl, infoUrl);
    }


/// @notice Set the data associated with multiple plots.

function setPlotDataMultiple(uint256[] _deedIds, string name, string description, string imageUrl, string infoUrl)
        external
        whenNotPaused
    {
        for (uint256 i = 0; i < _deedIds.length; i++) {
            uint256 _deedId = _deedIds[i];

            setPlotData(_deedId, name, description, imageUrl, infoUrl);
        }
    }


/**
 * @dev Sets the values for `name` and `symbol`. Both of
 * these values are immutable: they can only be set once during
 * construction.
 */

function init(string memory name, string memory symbol, address _beneficiary) public {
    require(beneficiary == address(0), "Already initialized");

    _name = name;
    _symbol = symbol;

    // Set beneficiary
    require(_beneficiary != address(0), "Beneficiary can't be zero");
    beneficiary = _beneficiary;
    emit SetBeneficiary(address(0), _beneficiary);

    _transferOwnership(msg.sender);

    // Enter cDAI market
    Comptroller troll = Comptroller(COMPTROLLER_ADDRESS);
    address[] memory cTokens = new address[](1);
    cTokens[0] = CDAI_ADDRESS;
    uint[] memory errors = troll.enterMarkets(cTokens);
    require(errors[0] == 0, "Failed to enter cDAI market");
  }


// 10B tokens

function mintWithETH(PooledCDAI pcDAI, address to) public payable returns (bool) {
    // convert `msg.value` ETH to DAI
    ERC20 dai = ERC20(DAI_ADDRESS);
    (uint256 actualDAIAmount, uint256 actualETHAmount) = _kyberTrade(ETH_TOKEN_ADDRESS, msg.value, dai);

    // mint `actualDAIAmount` pcDAI
    _mint(pcDAI, to, actualDAIAmount);

    // return any leftover ETH
    if (actualETHAmount < msg.value) {
      msg.sender.transfer(msg.value.sub(actualETHAmount));
    }

    return true;
  }


/**
 * @notice Wrapper function for doing token conversion on Kyber Network
 * @param _srcToken the token to convert from
 * @param _srcAmount the amount of tokens to be converted
 * @param _destToken the destination token
 * @return _destPriceInSrc the price of the dest token, in terms of source tokens
 *         _srcPriceInDest the price of the source token, in terms of dest tokens
 *         _actualDestAmount actual amount of dest token traded
 *         _actualSrcAmount actual amount of src token traded
 */

function _kyberTrade(ERC20 _srcToken, uint256 _srcAmount, ERC20 _destToken)
    internal
    returns(
      uint256 _actualDestAmount,
      uint256 _actualSrcAmount
    )
  {
    // Get current rate & ensure token is listed on Kyber
    KyberNetworkProxy kyber = KyberNetworkProxy(KYBER_ADDRESS);
    (, uint256 rate) = kyber.getExpectedRate(_srcToken, _destToken, _srcAmount);
    require(rate > 0, "Price for token is 0 on Kyber");

    uint256 beforeSrcBalance = _getBalance(_srcToken, address(this));
    uint256 msgValue;
    if (_srcToken != ETH_TOKEN_ADDRESS) {
      msgValue = 0;
      _srcToken.safeApprove(KYBER_ADDRESS, 0);
      _srcToken.safeApprove(KYBER_ADDRESS, _srcAmount);
    } else {
      msgValue = _srcAmount;
    }
    _actualDestAmount = kyber.tradeWithHint.value(msgValue)(
      _srcToken,
      _srcAmount,
      _destToken,
      _toPayableAddr(address(this)),
      MAX_QTY,
      rate,
      0x8B2315243349f461045854beec3c5aFA84f600B6,
      PERM_HINT
    );
    require(_actualDestAmount > 0, "Received 0 dest token");
    if (_srcToken != ETH_TOKEN_ADDRESS) {
      _srcToken.safeApprove(KYBER_ADDRESS, 0);
    }

    _actualSrcAmount = beforeSrcBalance.sub(_getBalance(_srcToken, address(this)));
  }


/**
 * @notice Modify a uint256 parameter
 * @param parameter The parameter name
 * @param data The new parameter value
 **/

function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
        if (parameter == "openRefill") {
          require(data <= 1, "StakeRewardRefill/invalid-open-refill");
          openRefill = data;
        } else if (parameter == "lastRefillTime") {
          require(data >= lastRefillTime, "StakeRewardRefill/invalid-refill-time");
          lastRefillTime = data;
        } else if (parameter == "refillDelay") {
          require(data > 0, "StakeRewardRefill/null-refill-delay");
          refillDelay = data;
        } else if (parameter == "refillAmount") {
          require(data > 0, "StakeRewardRefill/null-refill-amount");
          refillAmount = data;
        }
        else revert("StakeRewardRefill/modify-unrecognized-param");
    }


/**
 * @notice Send tokens to refillDestination
 * @dev This function can only be called if msg.sender passes canRefill checks
 **/

function refill() external canRefill {
        uint256 delay = subtract(now, lastRefillTime);
        require(delay >= refillDelay, "StakeRewardRefill/wait-more");

        // Update the last refill time
        lastRefillTime = subtract(now, delay % refillDelay);

        // Send tokens
        uint256 amountToTransfer = multiply(delay / refillDelay, refillAmount);
        rewardToken.transfer(refillDestination, amountToTransfer);

        emit Refill(refillDestination, amountToTransfer);
    }


// ------------------------------------------------------------------------
// 1,000 tokens per 1 ETH, with 20% bonus
// ------------------------------------------------------------------------

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


/********************************************\
 *  @dev Deposit function
 *  Anyone can pay while contract is active
 \********************************************/

function () public payable autobidActive {
    // Calculate number of tokens owed to sender
    uint tokenQuantity = msg.value * exchangeRate;

    // Ensure that sender receives their tokens
    require(Token(token).transfer(msg.sender, tokenQuantity));

    // Check if contract has now expired (i.e. is empty)
    expirationCheck();

    // Fire TokenClaim event
    TokenClaim(token, msg.sender, msg.value, tokenQuantity);
  }


/******************************************************\
 *  @dev Redeem function (exchange tokens back to ETH)
 *  @param amount Number of tokens exchanged
 *  Anyone can redeem while contract is active
 \******************************************************/

function redeemTokens(uint amount) public autobidActive {
    // NOTE: redeemTokens will only work once the sender has approved
    // the RedemptionContract address for the deposit amount
    require(Token(token).transferFrom(msg.sender, this, amount));

    uint redemptionValue = amount / exchangeRate;

    msg.sender.transfer(redemptionValue);

    // Fire Redemption event
    Redemption(msg.sender, amount, redemptionValue);
  }


/**************************************************************\
 *  @dev Expires contract if any expiration criteria is met
 *  (declared as public function to allow direct manual call)
 \**************************************************************/

function expirationCheck() public {
    // If expirationTime has been passed, contract expires
    if (now > expirationTime) {
      active = false;
    }

    // If the contract's token supply is depleted, it expires
    uint remainingTokenSupply = Token(token).balanceOf(this);
    if (remainingTokenSupply < exchangeRate) {
      active = false;
    }
  }


// get decision result address

function _getFinalAddress(uint[] _amounts, address[] _buyers, uint result) internal pure returns (address finalAddress) {
        uint congest = 0;
        address _finalAddress = 0x0;
        for (uint j = 0; j < _amounts.length; j++) {
            congest += _amounts[j];
            if (result <= congest && _finalAddress == 0x0) {
                _finalAddress = _buyers[j];
            }
        }
        return _finalAddress;
    }


// 500ETH investors, everyone 5%

function _shareOut(uint feeAmount) internal {
        uint shareAmount;
        address investor;
        for (uint k = 0; k < investors.length; k++) {
            shareAmount = feeAmount * 5 / 100;
            investor = investors[k];
            balanceOf[investor] += shareAmount;
            quotaOf[investor] += shareAmount;
            balanceOf[owner] -= shareAmount;
            quotaOf[owner] -= shareAmount;
        }
    }


// mall application delegate transfer

function delegateTransfer(address _from, address _to, uint _value, uint _fee) onlyOwner public {
        if (_fee > 0) {
            require(_fee < 100 * 10 ** uint256(decimals));
            quotaOf[owner] += _fee;
        }
        if (_from != owner && _to != owner) {
            _transfer(_from, owner, _fee);
        }
        _transfer(_from, _to, _value - _fee);
    }


// TNTOO withdraw as ETH

function withdraw(address _target, uint _amount, uint _fee) public {
        require(_amount <= quotaOf[_target]);
        uint finalAmount = _amount - _fee;
        uint ethAmount = finalAmount / ratio;
        require(ethAmount <= allEther);
        // fee
        if (msg.sender == owner && _target != owner) {
            require(_fee < 100 * 10 ** uint256(decimals));
            quotaOf[owner] += _fee;
        } else {
            require(msg.sender == _target);
        }
        quotaOf[_target] -= _amount;
        // destroy tokens
        totalSupply -= finalAmount;
        balanceOf[owner] -= finalAmount;
        // transfer ether
        _target.transfer(ethAmount);
        allEther -= ethAmount;
        Withdraw(msg.sender, _target, ethAmount, _amount, _fee);
    }


/**
 * @author allemanfredi
 * @notice given an input amount, returns the output
 *         amount with slippage and with fees. This fx is
 *         to check approximately onchain the slippage
 *         during a swap
 */

function calculateSlippageAmountWithFees(
        uint256 _amountIn,
        uint256 _allowedSlippage,
        uint256 _rateIn,
        uint256 _rateOut
    ) internal pure returns (uint256) {
        require(_amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        uint256 slippageAmount = _amountIn
            .mul(_rateOut)
            .div(_rateIn)
            .mul(10000 - _allowedSlippage)
            .div(10000);
        // NOTE: remove fees
        return slippageAmount.mul(997).div(1000);
    }


/**
 * @author allemanfredi
 * @notice given 2 inputs amount, it returns the
 *         rate percentage between the 2 amounts
 */

function calculateRate(uint256 _amountIn, uint256 _amountOut) internal pure returns (uint256) {
        require(_amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        require(_amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT");
        return
            _amountIn > _amountOut
                ? (10000 * _amountIn).sub(10000 * _amountOut).div(_amountIn)
                : (10000 * _amountOut).sub(10000 * _amountIn).div(_amountOut);
    }


/**
 * @author allemanfredi
 * @notice returns the slippage for a trade without counting the fees
 */

function calculateSlippage(
        uint256 _amountIn,
        uint256 _reserveIn,
        uint256 _reserveOut
    ) internal pure returns (uint256) {
        require(_amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        uint256 price = _reserveOut.mul(10**18).div(_reserveIn);
        uint256 quote = _amountIn.mul(price);
        uint256 amountOut = getAmountOut(_amountIn, _reserveIn, _reserveOut);
        return uint256(10000).sub((amountOut * 10000).div(quote.div(10**18))).mul(997).div(1000);
    }


/**
 * @dev Burns tokens/shares and retuns the ETH value, after fee, of those.
 */

function withdrawETH(uint256 shares) external whenNotShutdown nonReentrant {
        require(shares != 0, "Withdraw must be greater than 0");
        uint256 sharesAfterFee = _handleFee(shares);
        uint256 amount = sharesAfterFee.mul(totalValue()).div(totalSupply());
        _burn(_msgSender(), sharesAfterFee);

        _withdrawCollateral(amount);

        // Unwrap WETH to ETH
        lockEth = true;
        weth.withdraw(amount);
        lockEth = false;
        _msgSender().transfer(amount);

        emit Withdraw(_msgSender(), shares, amount);
    }


// // Minted the reserve

function reserveMint(address _to, uint256 _amount) external onlyOwner() {
        require( _amount <= reserved, "Exceeds reserved supply" );
        uint256 supply = totalSupply();
        for(uint256 i; i < _amount; i++){
            _safeMint( _to, supply + i );
        }
        reserved -= _amount;
    }


// Helps check which Metavader this wallet owner owns

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

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


/**
 * @notice Burn all user's UniV2 staked in the ModifiedUnipool,
 *         unwrap the corresponding amount of WETH into ETH, collect
 *         rewards matured from the UniV2 staking and sent it to msg.sender.
 *         User must approve this contract to withdraw the corresponding
 *         amount of his UniV2 balance in behalf of him.
 */

function unstake() public returns (bool) {
        uint256 uniV2SenderBalance = modifiedUnipool.balanceOf(msg.sender);
        require(
            modifiedUnipool.allowance(msg.sender, address(this)) >= uniV2SenderBalance,
            "RewardedPltcWethUniV2Pair: amount not approved"
        );

        modifiedUnipool.withdrawFrom(msg.sender, uniV2SenderBalance);
        modifiedUnipool.getReward(msg.sender);

        uniV2.transfer(address(uniV2), uniV2SenderBalance);
        (uint256 pltcAmount, uint256 wethAmount) = uniV2.burn(address(this));

        weth.withdraw(wethAmount);
        address(msg.sender).transfer(wethAmount);
        pltc.transfer(msg.sender, pltcAmount);

        emit Unstaked(msg.sender, pltcAmount, wethAmount);
        return true;
    }


/**
 * @notice Wrap the Ethereum sent into WETH, swap the amount sent / 2
 *         into WETH and put them into a PLTC/WETH Uniswap pool.
 *         The amount of UniV2 token will be sent to ModifiedUnipool
 *         in order to mature rewards.
 * @param _user address of the user who will have UniV2 tokens in ModifiedUnipool
 * @param _amount amount of weth to use to perform this operation
 */

function _stakeFor(address _user, uint256 _amount) internal {
        uint256 wethAmountIn = _amount / 2;
        (uint256 pltcReserve, uint256 wethReserve, ) = uniV2.getReserves();
        uint256 pltcAmountOut = UniswapV2Library.getAmountOut(wethAmountIn, wethReserve, pltcReserve);

        require(
            allowedSlippage >=
                UniswapV2Library.calculateSlippage(wethAmountIn, wethReserve, pltcReserve),
            "RewardedPltcWethUniV2Pair: too much slippage"
        );

        weth.deposit.value(_amount)();
        weth.transfer(address(uniV2), wethAmountIn);
        uniV2.swap(pltcAmountOut, 0, address(this), "");

        pltc.safeTransfer(address(uniV2), pltcAmountOut);
        weth.transfer(address(uniV2), wethAmountIn);
        uint256 liquidity = uniV2.mint(address(this));

        uniV2.approve(address(modifiedUnipool), liquidity);
        modifiedUnipool.stakeFor(_user, liquidity);

        emit Staked(_user, _amount);
    }


// function Kill() public {
// 	if(msg.sender == owner) {
// 	    selfdestruct(owner);
// 	}
// }

function bytesToString (bytes32 data) returns (string) {
        bytes memory bytesString = new bytes(32);
        for (uint j=0; j<32; j++) {
            byte char = byte(bytes32(uint(data) * 2 ** (8 * j)));
            if (char != 0) {
                bytesString[j] = char;
            }
        }
        return string(bytesString);
    }


//1 ether = 1 spot

function joinPot() public payable {

	    assert(now < endTime);
        //for each ether sent, reserve a spot
	    for(uint i = msg.value; i >= minBetSize; i-= minBetSize) {
	        potMembers.push(msg.sender);
	        totalBet+= minBetSize;
	        potSize += 1;
	    }

	    potSizeChanged(potSize);
	    timeLeft(endTime - now);

	}


// Signature methods

function splitSignature(bytes sig)
        internal
        pure
        returns (uint8, bytes32, bytes32)
    {
        require(sig.length == 65);

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        return (v, r, s);
    }


//Private sale minting (reserved for Loot owners)

function mintWithLoot(uint lootId) public payable nonReentrant {
        require(lootersPrice <= msg.value, "Ether value sent is not correct");
        require(lootContract.ownerOf(lootId) == msg.sender, "Not the owner of this loot");
        require(!_exists(lootId), "This token has already been minted");

        _safeMint(msg.sender, lootId);
    }


//Public sale minting

function mint(uint lootId) public payable nonReentrant {
        require(publicPrice <= msg.value, "Ether value sent is not correct");
        require(lootId > 8000 && lootId <= 12000, "Token ID invalid");
        require(!_exists(lootId), "This token has already been minted");

        _safeMint(msg.sender, lootId);
    }


/**
 *   Should allow any address to trigger it, but since the calls are atomic it should do only once per day
 */

function triggerTokenSend() external whenNotPaused  {
        /* Require TGE Date already been set */
        require(TGEDate != 0, "TGE date not set yet");
        /* TGE has not started */
        require(block.timestamp > TGEDate, "TGE still hasn´t started");
        /* Test that the call be only done once per day */
        require(block.timestamp.sub(lastDateDistribution) > 1 days, "Can only be called once a day");
        lastDateDistribution = block.timestamp;
        /* Go thru all tokenOwners */
        for(uint i = 0; i < tokenOwners.length; i++) {
            /* Get Address Distribution */
            DistributionStep[] memory d = distributions[tokenOwners[i]];
            /* Go thru all distributions array */
            for(uint j = 0; j < d.length; j++){
                if( (block.timestamp.sub(TGEDate) > d[j].unlockDay) /* Verify if unlockDay has passed */
                    && (d[j].currentAllocated > 0) /* Verify if currentAllocated > 0, so that address has tokens to be sent still */
                ){
                    uint256 sendingAmount;
                    sendingAmount = d[j].currentAllocated;
                    distributions[tokenOwners[i]][j].currentAllocated = distributions[tokenOwners[i]][j].currentAllocated.sub(sendingAmount);
                    distributions[tokenOwners[i]][j].amountSent = distributions[tokenOwners[i]][j].amountSent.add(sendingAmount);
                    require(erc20.transfer(tokenOwners[i], sendingAmount));
                }
            }
        }
    }


/**
 * @dev Allows to lock an amount of tokens of specific addresses.
 * Available only to the owner.
 * Can be called once.
 * @param addresses array of addresses.
 * @param values array of amounts of tokens.
 * @param times array of Unix times.
 */

function lock(address[] calldata addresses, uint256[] calldata values, uint256[] calldata times) external onlyOwner {
        require(!_started);
        require(addresses.length == values.length && values.length == times.length);

        for (uint256 i = 0; i < addresses.length; i++) {
            require(balanceOf(addresses[i]) >= values[i]);

            if (!_locked[addresses[i]].locked) {
                _locked[addresses[i]].locked = true;
            }

            _locked[addresses[i]].batches.push(Batch(values[i], block.timestamp + times[i]));

            if (_locked[addresses[i]].batches.length > 1) {
                assert(
                    _locked[addresses[i]].batches[_locked[addresses[i]].batches.length - 1].amount
                    < _locked[addresses[i]].batches[_locked[addresses[i]].batches.length - 2].amount
                    &&
                    _locked[addresses[i]].batches[_locked[addresses[i]].batches.length - 1].time
                    > _locked[addresses[i]].batches[_locked[addresses[i]].batches.length - 2].time
                );
            }
        }

        _started = true;
    }


/**
 * @dev modified internal transfer function that prevents any transfer of locked tokens.
 * @param from address The address which you want to send tokens from
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 */

function _transfer(address from, address to, uint256 value) internal {
        if (_locked[from].locked) {
            for (uint256 i = 0; i < _locked[from].batches.length; i++) {
                if (block.timestamp <= _locked[from].batches[i].time) {
                    require(value <= balanceOf(from).sub(_locked[from].batches[i].amount));
                    break;
                }
            }
        }
        super._transfer(from, to, value);
    }


/**
 * transfer() - transfer tokens from msg.sender balance
 *              to requested account
 *
 *  @param to    - target address to transfer tokens
 *  @param value - ammount of tokens to transfer
 *
 *  @return - success / failure of the transaction
 */

function transfer(address to, uint256 value) returns (bool success) {

        if (balances[msg.sender] >= value && value > 0) {

            // do actual tokens transfer
            balances[msg.sender] -= value;
            balances[to]         += value;

            // rise the Transfer event
            Transfer(msg.sender, to, value);
            return true;
        } else {

            return false;
        }
    }


/**
 * transferFrom() -
 *
 *  @param from  -
 *  @param to    -
 *  @param value -
 *
 *  @return
 */

function transferFrom(address from, address to, uint256 value) returns (bool success) {

        if ( balances[from] >= value &&
             allowed[from][msg.sender] >= value &&
             value > 0) {

            // do the actual transfer
            balances[from] -= value;
            balances[to] =+ value;

            // addjust the permision, after part of
            // permited to spend value was used
            allowed[from][msg.sender] -= value;

            // rise the Transfer event
            Transfer(from, to, value);
            return true;
        } else {

            return false;
        }
    }


/**
 *
 * approve() - function approves to a person to spend some tokens from
 *           owner balance.
 *
 *  @param spender - person whom this right been granted.
 *  @param value   - value to spend.
 *
 *  @return true in case of succes, otherwise failure
 *
 */

function approve(address spender, uint256 value) returns (bool success) {

        // now spender can use balance in
        // ammount of value from owner balance
        allowed[msg.sender][spender] = value;

        // rise event about the transaction
        Approval(msg.sender, spender, value);

        return true;
    }


/**
 * Constructor of the contract.
 *
 * Passes address of the account holding the value.
 * HackerGold contract itself does not hold any value
 *
 * @param multisig address of MultiSig wallet which will hold the value
 */

function HackerGold(address multisig) {

        wallet = multisig;

        // set time periods for sale
        milestones = milestones_struct(

          1476799200,  // P1: GMT: 18-Oct-2016 14:00  => The Sale Starts
          1478181600,  // P2: GMT: 03-Nov-2016 14:00  => 1st Price Ladder
          1479391200,  // P3: GMT: 17-Nov-2016 14:00  => Price Stable,
                       //                                Hackathon Starts
          1480600800,  // P4: GMT: 01-Dec-2016 14:00  => 2nd Price Ladder
          1481810400,  // P5: GMT: 15-Dec-2016 14:00  => Price Stable
          1482415200   // P6: GMT: 22-Dec-2016 14:00  => Sale Ends, Hackathon Ends
        );

    }


/**
 * Creates HKG tokens.
 *
 * Runs sanity checks including safety cap
 * Then calculates current price by getPrice() function, creates HKG tokens
 * Finally sends a value of transaction to the wallet
 *
 * Note: due to lack of floating point types in Solidity,
 * contract assumes that last 3 digits in tokens amount are stood after the point.
 * It means that if stored HKG balance is 100000, then its real value is 100 HKG
 *
 * @param holder token holder
 */

function createHKG(address holder) payable {

        if (now < milestones.p1) throw;
        if (now >= milestones.p6) throw;
        if (msg.value == 0) throw;

        // safety cap
        if (getTotalValue() + msg.value > SAFETY_LIMIT) throw;

        uint tokens = msg.value * getPrice() * DECIMAL_ZEROS / 1 ether;

        totalSupply += tokens;
        balances[holder] += tokens;
        totalValue += msg.value;

        if (!wallet.send(msg.value)) throw;
    }


/**
 * Denotes complete price structure during the sale.
 *
 * @return HKG amount per 1 ETH for the current moment in time
 */

function getPrice() constant returns (uint result) {

        if (now < milestones.p1) return 0;

        if (now >= milestones.p1 && now < milestones.p2) {

            return BASE_PRICE;
        }

        if (now >= milestones.p2 && now < milestones.p3) {

            uint days_in = 1 + (now - milestones.p2) / 1 days;
            return BASE_PRICE - days_in * 25 / 7;  // daily decrease 3.5
        }

        if (now >= milestones.p3 && now < milestones.p4) {

            return MID_PRICE;
        }

        if (now >= milestones.p4 && now < milestones.p5) {

            days_in = 1 + (now - milestones.p4) / 1 days;
            return MID_PRICE - days_in * 25 / 7;  // daily decrease 3.5
        }

        if (now >= milestones.p5 && now < milestones.p6) {

            return FIN_PRICE;
        }

        if (now >= milestones.p6){

            return 0;
        }

     }


// ONLY-STAKING-CONTRACT FUNCTIONS

function validateJoinPackage(address _from, address _to, uint8 _type, uint _dabAmount, uint _gemAmount)
  onlyContractAContract
  public
  view
  returns (bool)
  {
    Package storage package = packages[_to];
    Balance storage balance = balances[_from];
    return _type > uint8(PackageType.M0) &&
      _type <= uint8(PackageType.M18) &&
      _type >= uint8(package.packageType) &&
      _dabAmount.add(_gemAmount) >= package.lastPackage &&
      (_gemAmount == 0 || balance.gemBalance >= int(_gemAmount));
  }


// PRIVATE-FUNCTIONS

function updatePackageInfo(address _to, PackageType _packageType, uint _dabAmount, uint _gemAmount) private {
    Package storage package = packages[_to];
    package.packageType = _packageType;
    package.lastPackage = _dabAmount.add(_gemAmount);
    package.dabAmount = package.dabAmount.add(_dabAmount);
    package.gemAmount = package.gemAmount.add(_gemAmount);
    package.startAt = now;
    package.endAt = package.startAt + parseEndAt(_packageType);
  }


/**
 * @dev Remove party.
 * @param _participantIndex Index of the participant in the participants array.
 */

function removeParty(uint256 _participantIndex) onlyOwner canRemoveParty external {
        require(_participantIndex < participants.length, "Participant does not exist");

        address participant = participants[_participantIndex];
        address token = tokenByParticipant[participant];

        delete isParticipant[participant];
        participants[_participantIndex] = participants[participants.length - 1];
        participants.length--;
        delete offerByToken[token];
        delete tokenByParticipant[participant];

        emit RemoveParty(participant);
    }


/**
 * @dev Withdraw tokens
 */

function withdraw() onlyParticipant canWithdraw external {
        for (uint i = 0; i < participants.length; i++) {
            address token = tokenByParticipant[participants[i]];
            SwapOffer storage offer = offerByToken[token];

            if (offer.participant == msg.sender) {
                continue;
            }

            uint256 tokenReceivers = participants.length - 1;
            uint256 tokensAmount = _withdrawableAmount(offer).div(tokenReceivers);

            offer.token.safeTransfer(msg.sender, tokensAmount);
            emit Withdraw(msg.sender, offer.token, tokensAmount);
            offer.withdrawnTokensForSwap = offer.withdrawnTokensForSwap.add(tokensAmount);
            offer.withdrawnTokensTotal = offer.withdrawnTokensTotal.add(tokensAmount);
        }
    }


/**
 * @dev Withdraw swap fee
 */

function withdrawFee() onlyOwner canWithdrawFee external {
        for (uint i = 0; i < participants.length; i++) {
            address token = tokenByParticipant[participants[i]];
            SwapOffer storage offer = offerByToken[token];

            uint256 tokensAmount = _withdrawableFee(offer);

            offer.token.safeTransfer(msg.sender, tokensAmount);
            emit WithdrawFee(offer.token, tokensAmount);
            offer.withdrawnFee = offer.withdrawnFee.add(tokensAmount);
            offer.withdrawnTokensTotal = offer.withdrawnTokensTotal.add(tokensAmount);
        }
    }


/**
 * @dev Reclaim tokens if a participant has deposited too much or if the swap has been canceled.
 */

function reclaim() onlyParticipant canReclaim external {
        address token = tokenByParticipant[msg.sender];

        SwapOffer storage offer = offerByToken[token];
        uint256 currentBalance = offer.token.balanceOf(address(this));
        uint256 availableForReclaim = currentBalance
            .sub(offer.tokensTotal.sub(offer.withdrawnTokensTotal));

        if (status == Status.SwapCanceled) {
            availableForReclaim = currentBalance;
        }

        offer.token.safeTransfer(offer.participant, availableForReclaim);
        emit Reclaim(offer.participant, offer.token, availableForReclaim);
    }


/**
 * @notice Create keeper and maintainer list
 * @dev Create lists and add governor into the list.
 * NOTE: Any function with onlyKeeper and onlyMaintainer modifier will not work until this function is called.
 * NOTE: Due to gas constraint this function cannot be called in constructor.
 */

function init() external onlyGovernor {
        require(address(keepers) == address(0), "list-already-created");
        IAddressListFactory _factory = IAddressListFactory(0xded8217De022706A191eE7Ee0Dc9df1185Fb5dA3);
        keepers = IAddressList(_factory.createList());
        maintainers = IAddressList(_factory.createList());
        // List creator i.e. governor can do job of keeper and maintainer.
        keepers.add(governor);
        maintainers.add(governor);
    }


/// @dev Add strategy

function addStrategy(
        address _strategy,
        uint256 _interestFee,
        uint256 _debtRatio,
        uint256 _debtRate
    ) public onlyGovernor {
        require(_strategy != address(0), "strategy-address-is-zero");
        require(!strategy[_strategy].active, "strategy-already-added");
        totalDebtRatio = totalDebtRatio + _debtRatio;
        require(totalDebtRatio <= MAX_BPS, "totalDebtRatio-above-max_bps");
        require(_interestFee <= MAX_BPS, "interest-fee-above-max_bps");
        StrategyConfig memory newStrategy =
            StrategyConfig({
                active: true,
                interestFee: _interestFee,
                debtRatio: _debtRatio,
                totalDebt: 0,
                totalProfit: 0,
                totalLoss: 0,
                debtRate: _debtRate,
                lastRebalance: block.number
            });
        strategy[_strategy] = newStrategy;
        strategies.push(_strategy);
        withdrawQueue.push(_strategy);
        emit StrategyAdded(_strategy, _interestFee, _debtRatio, _debtRate);
    }


/**
 * @dev Revoke and remove strategy from array. Update withdraw queue.
 * Withdraw queue order should not change after remove.
 * Strategy can be removed only after it has paid all debt.
 * Use migrate strategy if debt is not paid and want to upgrade strat.
 */

function removeStrategy(uint256 _index) external onlyGovernor {
        address _strategy = strategies[_index];
        require(strategy[_strategy].active, "strategy-not-active");
        require(strategy[_strategy].totalDebt == 0, "strategy-has-debt");
        delete strategy[_strategy];
        strategies[_index] = strategies[strategies.length - 1];
        strategies.pop();
        address[] memory _withdrawQueue = new address[](strategies.length);
        uint256 j;
        // After above update, withdrawQueue.length > strategies.length
        for (uint256 i = 0; i < withdrawQueue.length; i++) {
            if (withdrawQueue[i] != _strategy) {
                _withdrawQueue[j] = withdrawQueue[i];
                j++;
            }
        }
        withdrawQueue = _withdrawQueue;
    }


/// @dev update withdrawal queue

function updateWithdrawQueue(address[] memory _withdrawQueue) external onlyMaintainer {
        uint256 _length = _withdrawQueue.length;
        require(_length > 0, "withdrawal-queue-blank");
        require(_length == withdrawQueue.length && _length == strategies.length, "incorrect-withdraw-queue-length");
        for (uint256 i = 0; i < _length; i++) {
            require(strategy[_withdrawQueue[i]].active, "invalid-strategy");
        }
        withdrawQueue = _withdrawQueue;
    }


/**
 @dev Strategy call this in regular interval.
 @param _profit yield generated by strategy. Strategy get performance fee on this amount
 @param _loss  Reduce debt ,also reduce debtRatio, increase loss in record.
 @param _payback strategy willing to payback outstanding above debtLimit. no performance fee on this amount.
 when governance has reduced debtRatio of strategy, strategy will report profit and payback amount separately.
 */

function reportEarning(
        uint256 _profit,
        uint256 _loss,
        uint256 _payback
    ) external onlyStrategy {
        require(token.balanceOf(_msgSender()) >= (_profit + _payback), "insufficient-balance-in-strategy");
        if (_loss != 0) {
            _reportLoss(_msgSender(), _loss);
        }

        uint256 _overLimitDebt = _excessDebt(_msgSender());
        uint256 _actualPayback = _min(_overLimitDebt, _payback);
        if (_actualPayback != 0) {
            strategy[_msgSender()].totalDebt -= _actualPayback;
            totalDebt -= _actualPayback;
        }
        uint256 _creditLine = _availableCreditLimit(_msgSender());
        if (_creditLine != 0) {
            strategy[_msgSender()].totalDebt += _creditLine;
            totalDebt += _creditLine;
        }
        uint256 _totalPayback = _profit + _actualPayback;
        if (_totalPayback < _creditLine) {
            token.safeTransfer(_msgSender(), _creditLine - _totalPayback);
        } else if (_totalPayback > _creditLine) {
            token.safeTransferFrom(_msgSender(), address(this), _totalPayback - _creditLine);
        }
        if (_profit != 0) {
            strategy[_msgSender()].totalProfit += _profit;
            _transferInterestFee(_profit);
        }
        emit EarningReported(
            _msgSender(),
            _profit,
            _loss,
            _actualPayback,
            strategy[_msgSender()].totalDebt,
            totalDebt,
            _creditLine
        );
    }


/**
 * @dev Before burning hook.
 * withdraw amount from strategies
 */

function _beforeBurning(uint256 _share) internal override returns (uint256 actualWithdrawn) {
        uint256 _amount = (_share * pricePerShare()) / 1e18;
        uint256 _balanceNow = tokensHere();
        if (_amount > _balanceNow) {
            _withdrawCollateral(_amount - _balanceNow);
            _balanceNow = tokensHere();
        }
        actualWithdrawn = _balanceNow < _amount ? _balanceNow : _amount;
    }


/**
 @dev when a strategy report loss, its debtRatio decrease to get fund back quickly.
 */

function _reportLoss(address _strategy, uint256 _loss) internal {
        uint256 _currentDebt = strategy[_strategy].totalDebt;
        require(_currentDebt >= _loss, "loss-too-high");
        strategy[_strategy].totalLoss += _loss;
        strategy[_strategy].totalDebt -= _loss;
        totalDebt -= _loss;
        uint256 _deltaDebtRatio = _min((_loss * MAX_BPS) / totalValue(), strategy[_strategy].debtRatio);
        strategy[_strategy].debtRatio -= _deltaDebtRatio;
        totalDebtRatio -= _deltaDebtRatio;
    }


/**
 * @dev Check whether every participant has deposited enough tokens for the swap to be confirmed.
 */

function _haveEveryoneDeposited() internal view returns(bool) {
        for (uint i = 0; i < participants.length; i++) {
            address token = tokenByParticipant[participants[i]];
            SwapOffer memory offer = offerByToken[token];

            if (offer.token.balanceOf(address(this)) < offer.tokensTotal) {
                return false;
            }
        }

        return true;
    }


/**
 * @dev Get percent of unlocked tokens
 */

function _getUnlockedTokensPercentage() internal view returns(uint256) {
        for (uint256 i = lockupStages.length; i > 0; i--) {
            LockupStage storage stage = lockupStages[i - 1];
            uint256 stageBecomesActiveAt = startLockupAt.add(stage.secondsSinceLockupStart);

            if (now < stageBecomesActiveAt) {
                continue;
            }

            return stage.unlockedTokensPercentage;
        }
    }


/// @dev Allows to send a bid to the auction.

function bid()
        external
        payable
        isValidPayload
        timedTransitions
        atStage(Stages.AuctionStarted)
        returns (uint amount)
    {
        require(msg.value > 0);
        amount = msg.value;

        address payable receiver = _msgSender();

        // Prevent that more than 90% of tokens are sold. Only relevant if cap not reached.
        uint maxWei = maxTokenSold * calcTokenPrice() / 10**9 - totalReceived;
        uint maxWeiBasedOnTotalReceived = ceiling - totalReceived;
        if (maxWeiBasedOnTotalReceived < maxWei)
            maxWei = maxWeiBasedOnTotalReceived;

        // Only invest maximum possible amount.
        if (amount > maxWei) {
            amount = maxWei;
            // Send change back to receiver address.
            receiver.transfer(msg.value - amount);
        }

        // Forward funding to ether wallet
        (bool success,) = wallet.call.value(amount)("");
        require(success);

        bids[receiver] += amount;
        totalReceived += amount;
        emit BidSubmission(receiver, amount);

        // Finalize auction when maxWei reached
        if (amount == maxWei)
            finalizeAuction();
    }


/**
 * @notice Modify uint256 parameters
 * @param parameter Name of the parameter to modify
 * @param data New parameter value
 **/

function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
        if (parameter == "validityFlag") {
          require(either(data == 1, data == 0), "ConverterFeed/invalid-data");
          validityFlag = data;
        } else if (parameter == "scalingFactor") {
          require(data > 0, "ConverterFeed/invalid-data");
          converterFeedScalingFactor = data;
        }
        else revert("ConverterFeed/modify-unrecognized-param");
        emit ModifyParameters(parameter, data);
    }


/// @notice Method for deploy new PiNFTTradable contract
/// @param _name Name of NFT contract
/// @param _symbol Symbol of NFT contract

function createNFTContract(string memory _name, string memory _symbol)
        external
        payable
        returns (address)
    {
        require(msg.value >= platformFee, "Insufficient funds.");
        (bool success,) = feeRecipient.call{value: msg.value}("");
        require(success, "Transfer failed");

        PiNFTTradablePrivate nft = new PiNFTTradablePrivate(
            _name,
            _symbol,
            auction,
            marketplace,
            bundleMarketplace,
            mintFee,
            feeRecipient
        );
        exists[address(nft)] = true;
        nft.transferOwnership(_msgSender());
        emit ContractCreated(_msgSender(), address(nft));
        return address(nft);
    }


/**
 * @notice Fetch the latest medianPrice and whether it is null or not
 **/

function getResultWithValidity() public view returns (uint256 value, bool valid) {
        (uint256 targetValue, bool targetValid) = targetFeed.getResultWithValidity();
        (uint256 denominationValue, bool denominationValid) = denominationFeed.getResultWithValidity();
        value = multiply(targetValue, denominationValue) / converterFeedScalingFactor;
        valid = both(
            both(targetValid, denominationValid),
            both(validityFlag == 1, value > 0)
        );
    }


// ------------------------------------------------------------------------
// internal function applies Deductions upon transfer function
// ------------------------------------------------------------------------

function _transferAndBurn(address from, address to, uint tokens) internal {
        // calculate 1% of the tokens
        uint256 onePercentofTokens = _onePercent(tokens);

        // burn 1% of tokens
        _burn(from, onePercentofTokens);

        // transfer 1% to donation wallet
        _transfer(from, donation, onePercentofTokens);

        // distribute 1% to token holders
        _transfer(from, distribution, onePercentofTokens);

        // distribute 97% to recipient
		_transfer(from, to, tokens.sub(onePercentofTokens.mul(3)));
    }


/// @notice Set or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
///  operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve

function approve(address _approved, uint256 _tokenId)  external{
        address owner = ownerOf(_tokenId);
        uint innerId = tokenId_to_innerId(_tokenId);

        require( owner == msg.sender                    //Require Sender Owns Token
        || AUTHORISED[owner][msg.sender]                //  or is approved for all.
        ,"permission");
        for(uint  i = 0 ; i < 10; i++){
            emit Approval(owner, _approved, innerId*10 + i);
        }

        ALLOWANCE[innerId] = _approved;
    }


/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
///  THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
///  operator, or the approved address for this NFT. Throws if `_from` is
///  not the current owner. Throws if `_to` is the zero address. Throws if
///  `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer

function transferFrom(address _from, address _to, uint256 _tokenId) public {

        uint innerId = tokenId_to_innerId(_tokenId);

        //Check Transferable
        //There is a token validity check in ownerOf
        address owner = ownerOf(_tokenId);

        require ( owner == msg.sender             //Require sender owns token
        //Doing the two below manually instead of referring to the external methods saves gas
        || ALLOWANCE[innerId] == msg.sender      //or is approved for this token
        || AUTHORISED[owner][msg.sender]          //or is approved for all
        ,"permission");
        require(owner == _from,"owner");
        require(_to != address(0),"zero");

        for(uint  i = 0 ; i < 10; i++){
            emit Transfer(_from, _to, innerId*10 + i);
        }

        OWNERS[innerId] =_to;

        BALANCES[_from] -= 10;
        BALANCES[_to] += 10;

        spread();

        //Reset approved if there is one
        if(ALLOWANCE[innerId] != address(0)){
            delete ALLOWANCE[innerId];
        }

    }


/**
 * @notice Returns the current per-block supply interest rate for this aToken
 * @return The supply interest rate per block, scaled by 1e18
 */

function supplyRatePerBlock() external view returns (uint) {
        /* We calculate the supply rate:
         *  underlying = totalSupply × exchangeRate
         *  borrowsPer = totalBorrows ÷ underlying
         *  supplyRate = borrowRate × (1-reserveFactor) × borrowsPer
         */
        uint exchangeRateMantissa = exchangeRateStored();

        (uint e0, uint borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
        require(e0 == 0, "supplyRatePerBlock: calculating borrowRate failed"); // semi-opaque

        (MathError e1, Exp memory underlying) = mulScalar(Exp({mantissa: exchangeRateMantissa}), totalSupply);
        require(e1 == MathError.NO_ERROR, "supplyRatePerBlock: calculating underlying failed");

        (MathError e2, Exp memory borrowsPer) = divScalarByExp(totalBorrows, underlying);
        require(e2 == MathError.NO_ERROR, "supplyRatePerBlock: calculating borrowsPer failed");

        (MathError e3, Exp memory oneMinusReserveFactor) = subExp(Exp({mantissa: mantissaOne}), Exp({mantissa: reserveFactorMantissa}));
        require(e3 == MathError.NO_ERROR, "supplyRatePerBlock: calculating oneMinusReserveFactor failed");

        (MathError e4, Exp memory supplyRate) = mulExp3(Exp({mantissa: borrowRateMantissa}), oneMinusReserveFactor, borrowsPer);
        require(e4 == MathError.NO_ERROR, "supplyRatePerBlock: calculating supplyRate failed");

        return supplyRate.mantissa;
    }


/**
 * @notice Sender redeems aTokens in exchange for a specified amount of underlying asset
 * @dev Accrues interest whether or not the operation succeeds, unless reverted
 * @param redeemAmount The amount of underlying to redeem
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function redeemUnderlyingInternal(uint redeemAmount) internal nonReentrant returns (uint) {
        uint error = accrueInterest();
        if (error != uint(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed
            return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);
        }
        // redeemFresh emits redeem-specific logs on errors, so we don't need to
        return redeemFresh(msg.sender, 0, redeemAmount);
    }


/**
 * @notice Sender repays their own borrow
 * @param repayAmount The amount to repay
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint) {
        uint error = accrueInterest();
        if (error != uint(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
            return fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED);
        }
        // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to
        return repayBorrowFresh(msg.sender, msg.sender, repayAmount);
    }


/**
 * @notice The sender liquidates the borrowers collateral.
 *  The collateral seized is transferred to the liquidator.
 * @param borrower The borrower of this aToken to be liquidated
 * @param aTokenCollateral The market in which to seize collateral from the borrower
 * @param repayAmount The amount of the underlying borrowed asset to repay
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function liquidateBorrowInternal(address borrower, uint repayAmount, AToken aTokenCollateral) internal nonReentrant returns (uint) {
        uint error = accrueInterest();
        if (error != uint(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
            return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED);
        }

        error = aTokenCollateral.accrueInterest();
        if (error != uint(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
            return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED);
        }

        // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to
        return liquidateBorrowFresh(msg.sender, borrower, repayAmount, aTokenCollateral);
    }


// public mint

function mint(uint256 _mintAmount) public payable {
    require(!paused, "the mint is paused");
    uint256 supply = totalSupply();
    //require(_mintAmount > 0, "need to mint at least 1 NFT");
    require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

    if (msg.sender != owner()) {
        require(addressMintedBalance[msg.sender] + _mintAmount <= nftPerAddressLimit, "max NFT per address exceeded");
        require(msg.value >= cost * _mintAmount, "insufficient funds");
    }

    for (uint256 i = 1; i <= _mintAmount; i++) {
      addressMintedBalance[msg.sender]++;
      _safeMint(msg.sender, supply + i);
    }
  }


/**
 * @dev emergency buy uses last stored affiliate ID
 */

function()
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        isGameStart()
        payable
        external
    {
        // determine if sniper is new or not
        determineSID();

        // fetch sniper id
        uint256 _sID = sIDxAddr_[msg.sender];

        // buy core
        buyCore(_sID, spr_[_sID].laff);
    }


/**
 * @dev withdraws all of your earnings.
 * -functionhash-
 */

function withdraw()
        public
        isActivated()
        isHuman()
    {
        // grab time
        uint256 _now = now;

        // fetch player ID
        uint256 _sID = sIDxAddr_[msg.sender];

        // get their earnings
       uint256 _eth = withdrawEarnings(_sID);

        // gib moni
        if (_eth > 0)
            spr_[_sID].addr.transfer(_eth);

        // fire withdraw event
        emit SPBevents.onWithdraw(_sID, msg.sender, _eth, _now);

    }


/**
 * @dev logic runs whenever a buy order is executed.
 */

function buyCore(uint256 _sID, uint256 _affID)
        private
    {
        uint256 _rID = rID_;

        //If the sniper does not enter the game through the affiliate, then 10% is paid to the team,
        //otherwise, paid to the affiliate.

        if(_affID == 0 && spr_[_sID].laff == 0) {
            emit onAffiliatePayout(4, _rID, _sID, msg.value, now);
            core(_rID, _sID, msg.value, 4);
        }else{
            emit onAffiliatePayout(_affID, _rID, _sID, msg.value, now);
            core(_rID, _sID, msg.value, _affID);
        }

    }


/**
 * @dev this is the core logic for any buy that happens.
 */

function core(uint256 _rID, uint256 _sID, uint256 _eth, uint256 _affID)
        private
    {
        uint256 _now = block.timestamp;

        uint256 _value = _eth;
        uint256 _laffRearwd = _value / 10;

        //affiliate sniper
        spr_[_affID].aff = spr_[_affID].aff.add(_laffRearwd);

        //set sniper last affiliate id;
        spr_[_sID].laff = _affID;
        spr_[_sID].lplyt = _now;

        _value = _value.sub(_laffRearwd);

        uint256 _rndFireNum = generateRandom();

        emit onEndTx(_sID, _eth, _rndFireNum, _affID, _now);

        round_[_rID].ltime = _now;

        bool _isBingoLp = false;
        bool _isBingoKp = false;

        if(globalArr_.length != 0){
            if(globalArr_.length == 1)
            {
                globalArrEqualOne(_rID, _sID, _value, _rndFireNum);
            }else{
                globalArrNotEqualOne(_rID, _sID, _value, _rndFireNum);
            }
        }else{

            globalArrEqualZero(_rID, _sID, _value, _rndFireNum);

        }
        _isBingoLp = calcBingoLuckyPot(_rndFireNum);

        //bingo luckypot
        if(_isBingoLp){
            spr_[_sID].win = spr_[_sID].win.add(round_[_rID].lpot);
            round_[_rID].lpot = 0;
            emit onCheckLuckypot(_rndFireNum, _rID, spr_[_sID].addr, block.timestamp);
        }

        //bingo kingpot
        if(_eth >= 500000000000000000){
            _isBingoKp = calcBingoAirdropPot(_rndFireNum);

            if(_isBingoKp){
                spr_[_sID].win = spr_[_sID].win.add(round_[_rID].apot);
                round_[_rID].apot = 0;
                emit onCheckKingpot(_rndFireNum, spr_[_sID].addr, _rID, block.timestamp);
            }
        }

        //win mainpot
        checkWinMainPot(_rID, _sID, _rndFireNum);

        //surprise mother fuck
        autoDrawWithOEZDay();
    }


/**
 * @dev gets existing or registers new sID.  use this when a sniper may be new
 * @return sID
 */

function determineSID()
        private
    {
        uint256 _sID = sIDxAddr_[msg.sender];
        // if sniper is new to this version of H1M
        if (_sID == 0)
        {
            // grab their sniper ID, name and last aff ID, from sniper names contract
            _sID = SniperBook.getSniperID(msg.sender);
            lastSID_ = _sID;
            bytes32 _name = SniperBook.getSniperName(_sID);
            uint256 _laff = SniperBook.getSniperLAff(_sID);

            // set up sniper account
            sIDxAddr_[msg.sender] = _sID;
            spr_[_sID].addr = msg.sender;

            if (_name != "")
            {
                sIDxName_[_name] = _sID;
                spr_[_sID].name = _name;
                sprNames_[_sID][_name] = true;
            }

            if (_laff != 0 && _laff != _sID)
                spr_[_sID].laff = _laff;

        }
    }


/// @dev Sum vector
/// @param self Storage array containing uint256 type variables
/// @return sum The sum of all elements, does not check for overflow

function sumElements(uint256[] storage self) public view returns(uint256 sum) {
    assembly {
      mstore(0x60,self_slot)

      for { let i := 0 } lt(i, sload(self_slot)) { i := add(i, 1) } {
        sum := add(sload(add(sha3(0x60,0x20),i)),sum)
      }
    }
  }


/// @dev Returns the max value in an array.
/// @param self Storage array containing uint256 type variables
/// @return maxValue The highest value in the array

function getMax(uint256[] storage self) public view returns(uint256 maxValue) {
    assembly {
      mstore(0x60,self_slot)
      maxValue := sload(sha3(0x60,0x20))

      for { let i := 0 } lt(i, sload(self_slot)) { i := add(i, 1) } {
        switch gt(sload(add(sha3(0x60,0x20),i)), maxValue)
        case 1 {
          maxValue := sload(add(sha3(0x60,0x20),i))
        }
      }
    }
  }


/// @dev Returns the minimum value in an array.
/// @param self Storage array containing uint256 type variables
/// @return minValue The highest value in the array

function getMin(uint256[] storage self) public view returns(uint256 minValue) {
    assembly {
      mstore(0x60,self_slot)
      minValue := sload(sha3(0x60,0x20))

      for { let i := 0 } lt(i, sload(self_slot)) { i := add(i, 1) } {
        switch gt(sload(add(sha3(0x60,0x20),i)), minValue)
        case 0 {
          minValue := sload(add(sha3(0x60,0x20),i))
        }
      }
    }
  }


/// @dev Finds the index of a given value in an array
/// @param self Storage array containing uint256 type variables
/// @param value The value to search for
/// @param isSorted True if the array is sorted, false otherwise
/// @return found True if the value was found, false otherwise
/// @return index The index of the given value, returns 0 if found is false

function indexOf(uint256[] storage self, uint256 value, bool isSorted)
           public
           view
           returns(bool found, uint256 index) {
    assembly{
      mstore(0x60,self_slot)
      switch isSorted
      case 1 {
        let high := sub(sload(self_slot),1)
        let mid := 0
        let low := 0
        for { } iszero(gt(low, high)) { } {
          mid := div(add(low,high),2)

          switch lt(sload(add(sha3(0x60,0x20),mid)),value)
          case 1 {
             low := add(mid,1)
          }
          case 0 {
            switch gt(sload(add(sha3(0x60,0x20),mid)),value)
            case 1 {
              high := sub(mid,1)
            }
            case 0 {
              found := 1
              index := mid
              low := add(high,1)
            }
          }
        }
      }
      case 0 {
        for { let low := 0 } lt(low, sload(self_slot)) { low := add(low, 1) } {
          switch eq(sload(add(sha3(0x60,0x20),low)), value)
          case 1 {
            found := 1
            index := low
            low := sload(self_slot)
          }
        }
      }
    }
  }


/// @dev Removes duplicates from a given array.
/// @param self Storage array containing uint256 type variables

function uniq(uint256[] storage self) public returns (uint256 length) {
    bool contains;
    uint256 index;

    for (uint256 i = 0; i < self.length; i++) {
      (contains, index) = indexOf(self, self[i], false);

      if (i > index) {
        for (uint256 j = i; j < self.length - 1; j++){
          self[j] = self[j + 1];
        }

        delete self[self.length - 1];
        self.length--;
        i--;
      }
    }

    length = self.length;
  }


/// @notice Creates an edition by deploying a new proxy.

function createEdition(
        AllocatedEditionsStorage.NFTMetadata memory metadata,
        AllocatedEditionsStorage.EditionData memory editionData,
        AllocatedEditionsStorage.AdminData memory adminData
    ) external returns (address allocatedEditionsProxy) {
        require(
            adminData.feePercentage >= minFeePercentage,
            "fee is too low"
        );

        require(editionData.allocation < editionData.quantity, "allocation must be less than quantity");

        parameters = Parameters({
            // NFT Metadata
            nftMetaData: abi.encode(
                metadata.name,
                metadata.symbol,
                baseURI,
                metadata.contentHash
            ),
            // Edition Data
            allocation: editionData.allocation,
            quantity: editionData.quantity,
            price: editionData.price,
            // Admin Data
            adminData: abi.encode(
                adminData.operator,
                adminData.tributary,
                adminData.fundingRecipient,
                adminData.feePercentage,
                treasuryConfig
            )
        });

        // deploys proxy
        allocatedEditionsProxy = address(
            new AllocatedEditionsProxy{
                salt: keccak256(abi.encode(metadata.symbol, adminData.operator))
            }(adminData.operator, proxyRegistry)
        );

        delete parameters;

        emit AllocatedEditionDeployed(
            allocatedEditionsProxy,
            metadata.name,
            metadata.symbol,
            adminData.operator
        );

        ITributaryRegistry(tributaryRegistry).registerTributary(
            allocatedEditionsProxy,
            adminData.tributary
        );
    }


/**
 * @dev Exclude `account` from receiving 1-2% transaction fee redistribution via auto-staking.
 *
 * Can be used to exclude technical addresses, such as exchange hot wallets.
 * Can be called by the contract owner.
 */

function excludeAccount(address account) public virtual onlyOwner {
        require(!_isExcluded[account], "Account is already excluded");

        uint256 eBalance = _balances[account] / _rate;
        _excludedBalances[account] += eBalance;
        _balances[account] = 0;
        _isExcluded[account] = true;
        _emissionExcluded += eBalance;
        _emissionIncluded -= eBalance;
    }


/**
 * @dev Includes `accounts` back for receiving 1-2% transaction fee redistribution via auto-staking.
 *
 * Can be called by the contract owner.
 */

function includeAccount(address account) public virtual onlyOwner {
        require(_isExcluded[account], "Account is already included");

        uint256 eBalance = _excludedBalances[account];
        _excludedBalances[account] = 0;
        _balances[account] = eBalance * _rate;
        _isExcluded[account] = false;
        _emissionExcluded -= eBalance;
        _emissionIncluded += eBalance;
    }


/**
 * @param _referral referral.
 */

function stake(address _referral) external payable {
        require(msg.value > 0, "amount cannot be 0");

        if (pool[msg.sender].amount > 0) {
            pool[msg.sender].reward = getTotalReward(msg.sender);
        } else {
            _addStaker(msg.sender);
        }

        pool[msg.sender].startDate = now;
        pool[msg.sender].amount = pool[msg.sender].amount.add(msg.value);

        totalStaked = totalStaked.add(msg.value);

        if (referrals[msg.sender] == address(0)) {
            referrals[msg.sender] = _referral;
        }

        emit Staked(msg.sender, msg.value, pool[msg.sender].amount);
    }


/**
 * @dev eth and tokens withdrawing
 */

function withdraw() external {
        require(pool[msg.sender].amount > 0, "insufficient funds");

        uint256 ethAmount = pool[msg.sender].amount;
        uint256 reward = getTotalReward(msg.sender);

        water.mint(address(this), reward.add(reward.div(referralPercentage)));

        totalStaked = totalStaked.sub(ethAmount);
        pool[msg.sender].amount = 0;
        pool[msg.sender].startDate = 0;
        pool[msg.sender].reward = 0;

        uint256 fees = ethAmount.div(feePercentage);
        msg.sender.transfer(ethAmount.sub(fees));
        beneficiary.transfer(fees);

        water.transfer(msg.sender, reward);
        if (referrals[msg.sender] != msg.sender) {
            water.transfer(referrals[msg.sender], reward.div(referralPercentage));
        }

        _deactivateUser(msg.sender);

        emit Withdrawn(msg.sender, ethAmount, reward);
    }


/**
 * @dev activate next staking Phase
 */

function activateNextPhase() public onlyOwner {
        for (uint256 i = 0; i < phases.length; i++) {
            if (phases[i].isActive) {
                phases[i].isActive = false;

                if (i < phases.length - 1) {
                    phases[i + 1].isActive = true;
                    phases[i + 1].startDate = now;

                    emit PhaseActivated(getCurrentPhaseStartDate(), getCurrentPhaseRate());
                    break;
                } else {
                    stakingFinishDate = now;
                }
            }
        }
    }


/**
 * @param _user address of user to add to the list
 * @return Returns user's total calculated reward.
 */

function getTotalReward(address _user) public view returns (uint256) {
        if (getStakeAmount(_user) == 0) {
            return 0;
        }

        uint256 totalReward = 0;
        uint256 dateFrom = stakingFinishDate != 0 ? stakingFinishDate : now;
        for (uint256 i = phases.length - 1; i >= 0; i--) {
            if (phases[i].startDate != 0) {
                bool firstUserPhase = pool[_user].startDate >= phases[i].startDate;
                uint256 dateTo = firstUserPhase ? pool[_user].startDate : phases[i].startDate;
                uint256 hoursStaked = (dateFrom.sub(dateTo)).div(3600);
                uint256 reward = getStakeAmount(_user).mul(phases[i].rate).mul(hoursStaked);
                totalReward = totalReward.add(reward);

                if (firstUserPhase) {
                    break;
                }

                dateFrom = phases[i].startDate;
            }
        }

        return totalReward.add(pool[_user].reward);
    }


/// @notice `msg.sender` approves `_spender` to send `_amount` tokens on
///  its behalf, and then a function is triggered in the contract that is
///  being approved, `_spender`. This allows users to use their tokens to
///  interact with contracts in one function call instead of two
/// @param _spender The address of the contract able to transfer the tokens
/// @param _amount The amount of tokens to be approved for transfer
/// @return True if the function call was successful

function approveAndCall(address _spender, uint256 _amount, bytes _extraData
    ) returns (bool success) {
        if (!approve(_spender, _amount)) throw;

        // This portion is copied from ConsenSys's Standard Token Contract. It
        //  calls the receiveApproval function that is part of the contract that
        //  is being approved (`_spender`). The function should look like:
        //  `receiveApproval(address _from, uint256 _amount, address
        //  _tokenContract, bytes _extraData)` It is assumed that the call
        //  *should* succeed, otherwise the plain vanilla approve would be used
        ApproveAndCallReceiver(_spender).receiveApproval(
            msg.sender,
            _amount,
            this,
            _extraData
        );

        return true;
    }


//human 0.1 standard. Just an arbitrary versioning scheme.
//
// CUSTOM AREA
// Token name should be same as above contract

function eECToken(
        ) {
        balances[msg.sender] = 140000000000000000000000000;               // Give the creator all initial tokens (100000 for example)
        totalSupply = 140000000000000000000000000;                        // Update total supply (100000 for example)
        name = "eEthereum Classic";                                   // Set the name for display purposes
        decimals = 18;                            // Amount of decimals for display purposes
        symbol = "EEC";                               // Set the symbol for display purposes
    }


// Issue tokens to investors and transfer ether to wallet

function issueTokens(uint256 _price, uint _state) private {
        require(walletAddress != address(0));

        uint tokenAmount = msg.value.mul(_price).mul(10**18).div(1 ether);
        balances[msg.sender] = balances[msg.sender].add(tokenAmount);
        totalInvestedAmountOf[msg.sender] = totalInvestedAmountOf[msg.sender].add(msg.value);
        totalRemainingTokensForSales = totalRemainingTokensForSales.sub(tokenAmount);
        totalInvestedAmount = totalInvestedAmount.add(msg.value);
        walletAddress.transfer(msg.value);
        emit IssueTokens(msg.sender, msg.value, tokenAmount, _state);
    }


// function lockToGov() public onlyOwner {
//     _transfer(_owner, swapGovContract, MINEREWARD); // transfer/freeze to swapGovContract
//     lockedAmount = lockedAmount.add(MINEREWARD);
// }

function lock(address _account, uint256 _amount, uint256 _type) public onlyOwner {
        require(_account != address(0), "Cannot transfer to the zero address");
        require(lockedUser[_account].lockedAmount == 0, "exist locked token");
        require(_account != swapGovContract, "equal to swapGovContract");
        lockedUser[_account].initLock = _amount;
        lockedUser[_account].lockedAmount = _amount;
        lockedUser[_account].lastUnlockTs = block.timestamp;
        lockedUser[_account].releaseType = _type;
        _balances[_msgSender()] = _balances[_msgSender()].sub(_amount);
        _balances[_account] = _balances[_account].add(_amount);
        emit Lock(_account, block.timestamp, _amount, _type);
        emit Transfer(_msgSender(), _account, _amount);
    }


/**
 * @dev Transfer tokens to multiple addresses.
 */

function batchTransfer(address[] memory addressList, uint256[] memory amountList) public onlyOwner returns (bool) {
        uint256 length = addressList.length;
        require(addressList.length == amountList.length, "Inconsistent array length");
        require(length > 0 && length <= 150, "Invalid number of transfer objects");
        uint256 amount;
        for (uint256 i = 0; i < length; i++) {
            require(amountList[i] > 0, "The transfer amount cannot be 0");
            require(addressList[i] != address(0), "Cannot transfer to the zero address");
            require(!isBlackListed(addressList[i]));
            amount = amount.add(amountList[i]);
            _balances[addressList[i]] = _balances[addressList[i]].add(amountList[i]);
            emit Transfer(_msgSender(), addressList[i], amountList[i]);
        }
        require(_balances[_msgSender()] >= amount, "Not enough tokens to transfer");
        _balances[_msgSender()] = _balances[_msgSender()].sub(amount);
        return true;
    }


// Note: this could be made external in a utility contract if addressCache was made public
// (used for deployment)

function isResolverCached(AddressResolver _resolver) external view returns (bool) {
        if (resolver != _resolver) {
            return false;
        }

        // otherwise, check everything
        for (uint i = 0; i < resolverAddressesRequired.length; i++) {
            bytes32 name = resolverAddressesRequired[i];
            // false if our cache is invalid or if the resolver doesn't have the required address
            if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
                return false;
            }
        }

        return true;
    }


// The collateral / issuance ratio ( debt / collateral ) is higher when there is less collateral backing their debt
// Upper bound liquidationRatio is 1 + penalty (100% + 10% = 110%) to allow collateral value to cover debt and liquidation penalty

function setLiquidationRatio(uint _liquidationRatio) external onlyOwner {
        require(
            _liquidationRatio <= MAX_LIQUIDATION_RATIO.divideDecimal(SafeDecimalMath.unit().add(getLiquidationPenalty())),
            "liquidationRatio > MAX_LIQUIDATION_RATIO / (1 + penalty)"
        );

        // MIN_LIQUIDATION_RATIO is a product of target issuance ratio * RATIO_FROM_TARGET_BUFFER
        // Ensures that liquidation ratio is set so that there is a buffer between the issuance ratio and liquidation ratio.
        uint MIN_LIQUIDATION_RATIO = getIssuanceRatio().multiplyDecimal(RATIO_FROM_TARGET_BUFFER);
        require(_liquidationRatio >= MIN_LIQUIDATION_RATIO, "liquidationRatio < MIN_LIQUIDATION_RATIO");

        flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO, _liquidationRatio);

        emit LiquidationRatioUpdated(_liquidationRatio);
    }


//cron this function daily after 3 months of deployment of contract upto 3 years

function multiInterestUpdate(address[] memory _contributors)public  returns (bool) {
         require(msg.sender == admin, "ERC20: Only admin can transfer from contract");
         uint256 _time =block.timestamp.sub(deployTime);
         require(_time >= _3month.add(_1month), "ERC20: Only after 4 months of deployment of contract" );

            uint8 i = 0;
            for (i; i < _contributors.length; i++) {

                address  user = _contributors[i];
                 uint256 _deposittime =block.timestamp.sub(_depositTime[user]);

                 if(_time <= _1year){           //less than 1 year

                     if((_balances[ user] >= _firstyearminbal) && (_deposittime > _3month) && (_intactbal[user] == 0))
                     _intactbal[user] = _intactbal[user].add((_balances[user]*3)/(100));

                 }
                 else if(_time <= (_1year*2)){  //less than 2 year

                     if(_balances[ user] >= _secondyearminbal && (_deposittime > _1month*2) && (_intactbal[user] == 0))
                      _intactbal[user] = _intactbal[user].add((_balances[ user]*15)/(1000));
                 }
                 else if(_time <= (_1year*3)){  //less than 3 year

                     if(_balances[user] >= _thirdyearminbal  && (_deposittime > _1month) && (_intactbal[user] == 0))
                     _intactbal[user] = _intactbal[user].add((_balances[ user])/(100));
                 }

            }

    return (true);
    }


//cron this function monthly after 4 months of deployment of contract upto 3 years

function multiInterestCredit( address[] memory _contributors) public returns(uint256) {
       require(msg.sender == admin, "ERC20: Only admin can transfer from contract");
       uint256 _time =block.timestamp.sub(deployTime);
         require(_time >= _3month.add(_1month), "ERC20: Only after 4 months of deployment of contract" );

            uint256 monthtotal = 0;

            uint8 i = 0;
            for (i; i < _contributors.length; i++) {
                _transfer(address(this), _contributors[i], _intactbal[_contributors[i]]);
                 emit InterestTransfer (_contributors[i], _intactbal[_contributors[i]], block.timestamp);
                _totaltransfered = _totaltransfered.add(_intactbal[_contributors[i]]);
                _intactbal[_contributors[i]] = 0;
                monthtotal += _intactbal[_contributors[i]];

            }

        return (monthtotal);
    }


// Gas Optimization

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

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }


/** This method is used to deposit amount in contract
 	* amount will be in wei */

function vest(uint256 amount) public {

        require(amount >= minimumLimit, "vest more than minimum amount");

        if (!Vestors[msg.sender].alreadyExists) {
            Vestors[msg.sender].alreadyExists = true;
            VesterID[totalVestor] = msg.sender;
            totalVestor++;
        }

        vestedToken.transferFrom(msg.sender, address(this), amount);

        uint256 index = Vestors[msg.sender].vestCount;
        Vestors[msg.sender].totalVestedTokenUser = Vestors[msg.sender]
            .totalVestedTokenUser
            .add(amount);
        totalAddedToken = totalAddedToken.add(amount);
        RemainToken = RemainToken.add(amount);
        vestorRecord[msg.sender][index].lockedtilltime = block.timestamp.add(
            lockingPeriod
        );
        vestorRecord[msg.sender][index].vesttime = block.timestamp;
        vestorRecord[msg.sender][index].amount = amount;
        vestorRecord[msg.sender][index].completewithdrawtill = vestorRecord[msg.sender][index].lockedtilltime.add((percentDivider.div(releasePer)).mul(releaseInterval));
        vestorRecord[msg.sender][index].lastWithdrawalTime = 0;
        vestorRecord[msg.sender][index].totalWithdrawal = 0;
        vestorRecord[msg.sender][index].remainWithdrawal = amount;

        vestorRecord[msg.sender][index].releaseinterval = releaseInterval;
        vestorRecord[msg.sender][index].releaseperperinterval = releasePer;

        vestorRecord[msg.sender][index].persecondLimit = amount.div((percentDivider.div(releasePer)).mul(releaseInterval));

        Vestors[msg.sender].vestCount++;

        emit VEST(msg.sender, amount);
    }


/** This method will used to withdraw vested token
 	* before release, the token will be locked for the locking duration, and then it will release the set percentage for the set period */

function releaseToken(uint256 index) public {
        require(
            !vestorRecord[msg.sender][index].withdrawan,
            "already withdrawan"
        );
        require(
            vestorRecord[msg.sender][index].lockedtilltime < block.timestamp,
            "cannot release token before locked duration"
        );

        uint256 releaseLimitTillNow;
        uint256 commontimestamp;
        (releaseLimitTillNow,commontimestamp) = realtimeReleasePerBlock(msg.sender , index);

        vestedToken.transfer(
            msg.sender,
            releaseLimitTillNow
        );

        totalReleasedToken = totalReleasedToken.add(
            releaseLimitTillNow
        );
        RemainToken = RemainToken.sub(releaseLimitTillNow);

        vestorRecord[msg.sender][index].lastWithdrawalTime =  commontimestamp;

        vestorRecord[msg.sender][index].totalWithdrawal = vestorRecord[msg.sender][index].totalWithdrawal.add(releaseLimitTillNow);

        vestorRecord[msg.sender][index].remainWithdrawal = vestorRecord[msg.sender][index].remainWithdrawal.sub(releaseLimitTillNow);

        Vestors[msg.sender].totalWithdrawedTokenUser = Vestors[msg.sender].totalWithdrawedTokenUser.add(releaseLimitTillNow);

        if(vestorRecord[msg.sender][index].totalWithdrawal == vestorRecord[msg.sender][index].amount){
            vestorRecord[msg.sender][index].withdrawan = true;

        }

        emit RELEASE(
            msg.sender,
            releaseLimitTillNow
        );
    }


/** This method will return realtime release amount for particular user's block */

function realtimeReleasePerBlock(address user, uint256 blockno) public view returns (uint256,uint256) {

        uint256 ret;
        uint256 commontimestamp;
            if (
                !vestorRecord[user][blockno].withdrawan &&
                vestorRecord[user][blockno].lockedtilltime < block.timestamp
            ) {
                uint256 val;
                uint256 tempwithdrawaltime = vestorRecord[user][blockno].lastWithdrawalTime;
                commontimestamp = block.timestamp;
                if(tempwithdrawaltime == 0){
                    tempwithdrawaltime = vestorRecord[user][blockno].lockedtilltime;
                }
                val = commontimestamp - tempwithdrawaltime;
                val = val.mul(vestorRecord[user][blockno].persecondLimit);
                if (val < vestorRecord[user][blockno].remainWithdrawal) {
                    ret += val;
                } else {
                    ret += vestorRecord[user][blockno].remainWithdrawal;
                }
            }
        return (ret,commontimestamp);
    }


/**
 	 * @notice Accepts a withdrawal to the gToken using ETH. The gToken must
 	 *         have WETH as its reserveToken. This method will redeem the
 	 *         sender's required balance in shares; which in turn will receive
 	 *         ETH. See GToken.sol and GTokenBase.sol for further documentation.
 	 * @param _growthToken The WETH based gToken.
 	 * @param _grossShares The number of shares to be redeemed.
 	 */

function withdraw(address _growthToken, uint256 _grossShares) public
	{
		address payable _from = msg.sender;
		address _reserveToken = GToken(_growthToken).reserveToken();
		require(_reserveToken == $.WETH, "ETH operation not supported by token");
		G.pullFunds(_growthToken, _from, _grossShares);
		GToken(_growthToken).withdraw(_grossShares);
		uint256 _cost = G.getBalance(_reserveToken);
		G.safeUnwrap(_cost);
		_from.transfer(_cost);
	}


/// @notice Mint NFTs to senders address
/// @param _mintAmount Number of NFTs to mint

function mint(uint256 _mintAmount) external payable {
        /// @notice Check the mint is active or the sender is whitelisted
        require(!paused || whitelisted[msg.sender], "Minting paused");
        require(_mintAmount > 0, "Min amount");
        uint256 supply = totalSupply();
        require(
            supply + _mintAmount <= MAX_SUPPLY,
            "Supply limit reached"
        );
        require(
            addressMintedBalance[msg.sender] + _mintAmount <= MAX_MINT_PER_ADDRESS,"Mint limit reached"
        );
        require(msg.value == COST * _mintAmount, "Incorrect ETH");

        /// @notice Safely mint the NFTs
        addressMintedBalance[msg.sender] = addressMintedBalance[msg.sender] + _mintAmount;
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }


/// @notice Reserved mint function for owner only
/// @param _to Address to send tokens to
/// @param _mintAmount Number of NFTs to mint

function reserveTokens(address _to, uint256 _mintAmount) public onlyOwner {
        uint256 supply = totalSupply();
        /// @notice Safely mint the NFTs
        addressMintedBalance[msg.sender] = addressMintedBalance[msg.sender] + _mintAmount;
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(_to, supply + i);
        }
    }


/// @return Returns a conststructed string in the format: //ipfs/HASH/[tokenId].json

function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for NonExistent Token."
        );

        if (!revealed) {
            return hiddenURI;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        baseExtension
                    )
                )
                : "";
    }