/**
 * @dev Update transcoder's fee pool
 * @param _transcoder Transcoder address
 * @param _fees Fees from verified job claims
 */

function updateTranscoderWithFees(
        address _transcoder,
        uint256 _fees,
        uint256 _round
    )
        external
        whenSystemNotPaused
        onlyJobsManager
    {
        // Transcoder must be registered
        require(transcoderStatus(_transcoder) == TranscoderStatus.Registered);

        Transcoder storage t = transcoders[_transcoder];

        EarningsPool.Data storage earningsPool = t.earningsPoolPerRound[_round];
        // Add fees to fee pool
        earningsPool.feePool = earningsPool.feePool.add(_fees);
    }


/**
 * @dev Slash a transcoder. Slashing can be invoked by the protocol or a finder.
 * @param _transcoder Transcoder address
 * @param _finder Finder that proved a transcoder violated a slashing condition. Null address if there is no finder
 * @param _slashAmount Percentage of transcoder bond to be slashed
 * @param _finderFee Percentage of penalty awarded to finder. Zero if there is no finder
 */

function slashTranscoder(
        address _transcoder,
        address _finder,
        uint256 _slashAmount,
        uint256 _finderFee
    )
        external
        whenSystemNotPaused
        onlyJobsManager
    {
        Delegator storage del = delegators[_transcoder];

        if (del.bondedAmount > 0) {
            uint256 penalty = MathUtils.percOf(delegators[_transcoder].bondedAmount, _slashAmount);

            // Decrease bonded stake
            del.bondedAmount = del.bondedAmount.sub(penalty);

            // If still bonded
            // - Decrease delegate's delegated amount
            // - Decrease total bonded tokens
            if (delegatorStatus(_transcoder) == DelegatorStatus.Bonded) {
                delegators[del.delegateAddress].delegatedAmount = delegators[del.delegateAddress].delegatedAmount.sub(penalty);
                totalBonded = totalBonded.sub(penalty);
            }

            // If registered transcoder, resign it
            if (transcoderStatus(_transcoder) == TranscoderStatus.Registered) {
                resignTranscoder(_transcoder);
            }

            // Account for penalty
            uint256 burnAmount = penalty;

            // Award finder fee if there is a finder address
            if (_finder != address(0)) {
                uint256 finderAmount = MathUtils.percOf(penalty, _finderFee);
                minter().trustedTransferTokens(_finder, finderAmount);

                // Minter burns the slashed funds - finder reward
                minter().trustedBurnTokens(burnAmount.sub(finderAmount));

                TranscoderSlashed(_transcoder, _finder, penalty, finderAmount);
            } else {
                // Minter burns the slashed funds
                minter().trustedBurnTokens(burnAmount);

                TranscoderSlashed(_transcoder, address(0), penalty, 0);
            }
        } else {
            TranscoderSlashed(_transcoder, _finder, 0, 0);
        }
    }


/**
 * @dev Claim token pools shares for a delegator from its lastClaimRound through the end round
 * @param _endRound The last round for which to claim token pools shares for a delegator
 */

function claimEarnings(uint256 _endRound) external whenSystemNotPaused currentRoundInitialized {
        // End round must be after the last claim round
        require(delegators[msg.sender].lastClaimRound < _endRound);
        // End round must not be after the current round
        require(_endRound <= roundsManager().currentRound());

        updateDelegatorWithEarnings(msg.sender, _endRound);
    }


/**
 * @dev Returns pending bonded stake for a delegator from its lastClaimRound through an end round
 * @param _delegator Address of delegator
 * @param _endRound The last round to compute pending stake from
 */

function pendingStake(address _delegator, uint256 _endRound) public view returns (uint256) {
        uint256 currentRound = roundsManager().currentRound();
        Delegator storage del = delegators[_delegator];
        // End round must be before or equal to current round and after lastClaimRound
        require(_endRound <= currentRound && _endRound > del.lastClaimRound);

        uint256 currentBondedAmount = del.bondedAmount;

        for (uint256 i = del.lastClaimRound + 1; i <= _endRound; i++) {
            EarningsPool.Data storage earningsPool = transcoders[del.delegateAddress].earningsPoolPerRound[i];

            bool isTranscoder = _delegator == del.delegateAddress;
            if (earningsPool.hasClaimableShares()) {
                // Calculate and add reward pool share from this round
                currentBondedAmount = currentBondedAmount.add(earningsPool.rewardPoolShare(currentBondedAmount, isTranscoder));
            }
        }

        return currentBondedAmount;
    }


/**
 * @dev Computes delegator status
 * @param _delegator Address of delegator
 */

function delegatorStatus(address _delegator) public view returns (DelegatorStatus) {
        Delegator storage del = delegators[_delegator];

        if (del.withdrawRound > 0) {
            // Delegator called unbond
            if (roundsManager().currentRound() >= del.withdrawRound) {
                return DelegatorStatus.Unbonded;
            } else {
                return DelegatorStatus.Unbonding;
            }
        } else if (del.startRound > roundsManager().currentRound()) {
            // Delegator round start is in the future
            return DelegatorStatus.Pending;
        } else if (del.startRound > 0 && del.startRound <= roundsManager().currentRound()) {
            // Delegator round start is now or in the past
            return DelegatorStatus.Bonded;
        } else {
            // Default to unbonded
            return DelegatorStatus.Unbonded;
        }
    }


/**
 * @dev Return transcoder information
 * @param _transcoder Address of transcoder
 */

function getTranscoder(
        address _transcoder
    )
        public
        view
        returns (uint256 lastRewardRound, uint256 rewardCut, uint256 feeShare, uint256 pricePerSegment, uint256 pendingRewardCut, uint256 pendingFeeShare, uint256 pendingPricePerSegment)
    {
        Transcoder storage t = transcoders[_transcoder];

        lastRewardRound = t.lastRewardRound;
        rewardCut = t.rewardCut;
        feeShare = t.feeShare;
        pricePerSegment = t.pricePerSegment;
        pendingRewardCut = t.pendingRewardCut;
        pendingFeeShare = t.pendingFeeShare;
        pendingPricePerSegment = t.pendingPricePerSegment;
    }


/**
 * @dev Return transcoder's token pools for a given round
 * @param _transcoder Address of transcoder
 * @param _round Round number
 */

function getTranscoderEarningsPoolForRound(
        address _transcoder,
        uint256 _round
    )
        public
        view
        returns (uint256 rewardPool, uint256 feePool, uint256 totalStake, uint256 claimableStake)
    {
        EarningsPool.Data storage earningsPool = transcoders[_transcoder].earningsPoolPerRound[_round];

        rewardPool = earningsPool.rewardPool;
        feePool = earningsPool.feePool;
        totalStake = earningsPool.totalStake;
        claimableStake = earningsPool.claimableStake;
    }


/**
 * @dev Return delegator info
 * @param _delegator Address of delegator
 */

function getDelegator(
        address _delegator
    )
        public
        view
        returns (uint256 bondedAmount, uint256 fees, address delegateAddress, uint256 delegatedAmount, uint256 startRound, uint256 withdrawRound, uint256 lastClaimRound)
    {
        Delegator storage del = delegators[_delegator];

        bondedAmount = del.bondedAmount;
        fees = del.fees;
        delegateAddress = del.delegateAddress;
        delegatedAmount = del.delegatedAmount;
        startRound = del.startRound;
        withdrawRound = del.withdrawRound;
        lastClaimRound = del.lastClaimRound;
    }


/**
 * @dev Remove transcoder
 */

function resignTranscoder(address _transcoder) internal {
        uint256 currentRound = roundsManager().currentRound();
        if (activeTranscoderSet[currentRound].isActive[_transcoder]) {
            // Decrease total active stake for the round
            activeTranscoderSet[currentRound].totalStake = activeTranscoderSet[currentRound].totalStake.sub(activeTranscoderTotalStake(_transcoder, currentRound));
            // Set transcoder as inactive
            activeTranscoderSet[currentRound].isActive[_transcoder] = false;
        }

        // Remove transcoder from pools
        transcoderPool.remove(_transcoder);

        TranscoderResigned(_transcoder);
    }


/**
 * @dev Update a transcoder with rewards
 * @param _transcoder Address of transcoder
 * @param _rewards Amount of rewards
 * @param _round Round that transcoder is updated
 */

function updateTranscoderWithRewards(address _transcoder, uint256 _rewards, uint256 _round) internal {
        Transcoder storage t = transcoders[_transcoder];
        Delegator storage del = delegators[_transcoder];

        EarningsPool.Data storage earningsPool = t.earningsPoolPerRound[_round];
        // Add rewards to reward pool
        earningsPool.rewardPool = earningsPool.rewardPool.add(_rewards);
        // Update transcoder's delegated amount with rewards
        del.delegatedAmount = del.delegatedAmount.add(_rewards);
        // Update transcoder's total stake with rewards
        uint256 newStake = transcoderPool.getKey(_transcoder).add(_rewards);
        transcoderPool.updateKey(_transcoder, newStake, address(0), address(0));
        // Update total bonded tokens with claimable rewards
        totalBonded = totalBonded.add(_rewards);
    }


/// @notice Returns price of DFX in CAD, e.g. 1 DFX = X CAD
///         Will assume 1 CADC = 1 CAD in this case

function read() public view override returns (uint256) {
        // 18 dec
        uint256 wethPerDfx18 = consult(DFX, 1e18);

        // in256, 8 dec -> uint256 18 dec
        (, int256 usdPerEth8, , , ) = ETH_USD_ORACLE.latestRoundData();
        (, int256 usdPerCad8, , , ) = CAD_USD_ORACLE.latestRoundData();
        uint256 usdPerEth18 = uint256(usdPerEth8) * 1e10;
        uint256 usdPerCad18 = uint256(usdPerCad8) * 1e10;

        // (eth/dfx) * (usd/eth) = usd/dfx
        uint256 usdPerDfx = (wethPerDfx18 * usdPerEth18) / 1e18;

        // (usd/dfx) / (usd/cad) = cad/dfx
        uint256 cadPerDfx = (usdPerDfx * 1e18) / usdPerCad18;

        return cadPerDfx;
    }


/// @dev Function to mint tokens.
/// @param _to The address that will receive the minted tokens.
/// @param _amount The amount of tokens to mint.
/// @param _priceUsd The price of minted token at moment of purchase in USD with 18 decimals.
/// @return A boolean that indicates if the operation was successful.

function mint(address _to, uint256 _amount, uint256 _priceUsd) onlyOwner canMint public returns (bool) {
    totalSupply_ = totalSupply_.add(_amount);
    balances[_to] = balances[_to].add(_amount);
    if (_priceUsd != 0) {
      uint256 amountUsd = _amount.mul(_priceUsd).div(10**18);
      totalCollected = totalCollected.add(amountUsd);
    }
    emit Mint(_to, _amount, _priceUsd);
    emit Transfer(0x0, _to, _amount);
    return true;
  }


// use can direct deposit USDC and it will auto swap to szUSDC

function depositTokenTerm(address _from,uint256 amount,uint256 _term) public returns (bool){
        require(msg.sender == _from ||
        (permits[msg.sender] == true && stopAdminControl[_from] == false),"NO Permission to call this function");
        require(_term >= minimumTerm);

        // check amount should not have decimal
        uint256 havDecimal;
        havDecimal = amount % (10 ** 6);
        if(havDecimal > 0){
            return false;
        }

        if(token.deposit(_from,amount) == false)
            return false;

       if(token.intTransfer(_from,address(this),amount * (10 ** 12)) == true){
            _depositContract(_from,amount * (10 ** 12),_term);
            return true;
        }

        return false;
     }


// user can direct deposit szUSDC

function depositSZTokenTerm(address _from,uint256 amount,uint256 _term) public returns (bool){
        require(msg.sender == _from ||
        (permits[msg.sender] == true && stopAdminControl[_from] == false),"NO Permission to call function");
        require(_term >= minimumTerm,"TERM ERRIR");
        // check amount should not have decimal
        uint256 havDecimal;
        havDecimal = amount % (10 ** decimal);
        if(havDecimal > 0){
          return false;
        }

       if(token.intTransfer(_from,address(this),amount) == true){
            _depositContract(_from,amount,_term);
            return true;
        }

            return false;
    }


// When witdraw interest will reset to 0

function _withdraw(address _to,uint256 _amount) internal returns(uint256){
        if(_amount == 0) return 0;

        uint256 interest =  profitCal.getWithdrawInterest(address(this),_to);
        uint256 idxSize = depositIdxs[_to].length;
        uint256 idx;
        uint256 principle;
        uint256 tempPrinciple;

        for(uint256 i=0;i<idxSize;i++){
            idx = depositIdxs[_to][i] - 1;
            if(deposits[idx].amount > 0)
                deposits[idx].lastDeposit = now;
        }

        totalClaimInterest += interest;

        if(interest > _amount){
            require(interest <= loanBalance(),"ERROR01 Insuffician Fund to withdraw");
            token.transfer(_to,interest);
            token.transfer(_to,0);
            totalSupply -= interest;
            return interest;
        }
        else
        {

            principle = _amount - interest;
            if(principle > balance[_to])
                principle = balance[_to];

            require(principle + interest <=loanBalance(),"ERROR02 Insuffician Fund to withdraw");
            tempPrinciple = principle;

            for(uint256 i=0;i<idxSize;i++){
                idx = depositIdxs[_to][i] - 1;
            if(tempPrinciple >0)
            {
                if(tempPrinciple >= deposits[idx].amount){
                    tempPrinciple -=deposits[idx].amount;
                    deposits[idx].amount = 0;
                }
                else
                {
                    deposits[idx].amount -= tempPrinciple;
                    tempPrinciple = 0;
                }
            }
            }
                totalSupply -= principle + interest;
                balance[_to] -= principle;
                token.transfer(_to,interest);
                token.transfer(_to,principle);
                 return interest + principle;
        }
    }


// this function will withdraw all interest only

function withdrawInterest(address _to) public returns(uint256){
      require(msg.sender == _to ||
      (permits[msg.sender] == true && stopAdminControl[_to] == false),"No Permission to call");
      require(depositIdxs[_to].length > 0,"Not deposit");

      uint256 amount =  profitCal.getWithdrawInterest(address(this),_to);
      uint256 idxSize = depositIdxs[_to].length;
      uint256 idx;

      if(amount == 0) return 0;

      for(uint256 i=0;i<idxSize;i++){
          idx = depositIdxs[_to][i] - 1;
          if(deposits[idx].amount > 0)
              deposits[idx].lastDeposit = now;
      }

      totalClaimInterest += amount;
      token.transfer(_to,amount);
      return amount;

  }


// amount in CATToken only

function _borrow(uint256 amount,address _addr) internal returns(uint256 contractID){
      amount  = (amount / (10 ** 18)) * (10 ** 18);
      require(amount <= catToken.balanceOf(_addr),"not enought CAT Token");
      uint256 amountStable = (amount / (10 ** 18)) * (10 ** decimal);
      require(amountStable <= totalSupply - totalBorrow,"Not have fund to borrw");

      BorrowContract memory br = BorrowContract({
          amount : amountStable,
          interest: borrowInterest,
          repayAmount:0,
          interestPay:0,
          time:now,
          status:1,
          startTime:now
        });

      uint256 idx = borrows.push(br);
      contracts[_addr].push(idx);
      catToken.intTransfer(_addr,address(this),amount);
      token.transfer(_addr,amountStable);
      totalBorrow += amountStable;

      emit Borrow(_addr,amountStable,borrowInterest);

      return idx;
  }


// ------------------------------------------------------------------------
// 1,000 FWD Tokens per 1 ETH
// ------------------------------------------------------------------------

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


/** @dev Returns the claimable tokens for user.*/

function earned(address account) public view returns (uint256) {
    // Do a lookup for the multiplier on the view - it's necessary for correct reward distribution.
    uint256 totalMultiplier = multiplier.getTotalValueForUser(address(this), account);
    uint256 effectiveBalance = _balances[account].add(_balances[account].mul(totalMultiplier).div(1000));
    return effectiveBalance.mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
  }


/** @dev Withdraw function, this pool contains a tax which is defined in the constructor */

function withdraw(uint256 amount) public override {
    require(amount > 0, "Cannot withdraw 0");
    updateReward(msg.sender);

    // Calculate the withdraw tax (it's 2% of the amount)
    uint256 tax = amount.mul(devFee).div(1000);

    // Transfer the tokens to user
    stakingToken.safeTransfer(msg.sender, amount.sub(tax));
    // Tax to treasury
    stakingToken.safeTransfer(treasury, tax);

    // Adjust regular balances
    super.withdraw(amount);

    // And the bonus balances
    uint256 userTotalMultiplier = multiplier.getTotalValueForUser(address(this), msg.sender);
    adjustEffectiveStake(msg.sender, userTotalMultiplier, true);
    emit Withdrawn(msg.sender, amount);
  }


/** @dev Adjust the bonus effective stakee for user and whole userbase */

function adjustEffectiveStake(
    address self,
    uint256 _totalMultiplier,
    bool _isWithdraw
  ) private {
    uint256 prevBalancesAccounting = _balancesAccounting[self];
    if (_totalMultiplier > 0) {
      // Calculate and set self's new accounting balance
      uint256 newBalancesAccounting = _balances[self].mul(_totalMultiplier).div(1000);

      // Adjust total accounting supply accordingly - Subtracting previous balance from new balance on withdraws
      // On deposits it's vice-versa.
      if (_isWithdraw) {
        uint256 diffBalancesAccounting = prevBalancesAccounting.sub(newBalancesAccounting);
        _balancesAccounting[self] = _balancesAccounting[self].sub(diffBalancesAccounting);
        _totalSupplyAccounting = _totalSupplyAccounting.sub(diffBalancesAccounting);
      } else {
        uint256 diffBalancesAccounting = newBalancesAccounting.sub(prevBalancesAccounting);
        _balancesAccounting[self] = _balancesAccounting[self].add(diffBalancesAccounting);
        _totalSupplyAccounting = _totalSupplyAccounting.add(diffBalancesAccounting);
      }
    } else {
      _balancesAccounting[self] = 0;
      _totalSupplyAccounting = _totalSupplyAccounting.sub(prevBalancesAccounting);
    }
  }


// Sends out the reward tokens to the user.

function getReward() public {
    updateReward(msg.sender);
    uint256 reward = earned(msg.sender);
    if (reward > 0) {
      uint256 tax = reward.mul(devFee).div(1000);
      rewards[msg.sender] = 0;
      rewardToken.safeTransfer(msg.sender, reward.sub(tax));
      rewardToken.safeTransfer(treasury, tax.mul(50).div(100));
      rewardToken.safeTransfer(ramVault, tax.mul(50).div(100));
      emit RewardPaid(msg.sender, reward);
    }
  }


// Notify a reward amount without updating time,

function notifyRewardAmountWithoutUpdateTime(uint256 reward) external onlyOwner {
    rewardToken.safeTransferFrom(msg.sender, address(this), reward);
    updateRewardPerTokenStored();
    if (block.timestamp >= periodFinish) {
      rewardRate = reward.div(DURATION);
    } else {
      uint256 remaining = periodFinish.sub(block.timestamp);
      uint256 leftover = remaining.mul(rewardRate);
      rewardRate = reward.add(leftover).div(DURATION);
    }
    emit RewardAdded(reward);
  }


// MINTING FUNCTIONS

function mint(uint256 amount) external payable {
        require(saleActive, "Public sale not active");
        require(msg.value == amount * mintPrice, "Not enough ETH sent");
        require(amount <= MAX_AMOUNT_PER_MINT, "Minting too many at a time");
        require(amount + totalSupply() <= MAX_TOTAL_SUPPLY, "Would exceed max supply");

        _mint(msg.sender, amount, "", false);
    }


// reserves 'amount' NFTs minted direct to a specified wallet

function reserve(address to, uint256 amount) external onlyOwner {
        require(amount + totalSupply() <= MAX_TOTAL_SUPPLY, "Would exceed max supply");
        require(reserved + amount <= MAX_RESERVED_AMOUNT, "Would exceed max reserved amount");

        _mint(to, amount, "", false);
        reserved += amount;
    }


// UPDATE FUNCTIONS

function plunge(uint256 tokenId) public onlyTokenOwner(tokenId) {
        require(!isLocked[tokenId], "Gator is dead");

        uint256 currentLevel = tokenLevels[tokenId];
        require(currentLevel < MAX_LEVEL, "Gator already max level");

        uint256 pseudoRandomNumber = _genPseudoRandomNumber(tokenId);

        if (currentLevel == 0) {
            // first upgrade, 90% chance of success
            if (pseudoRandomNumber < 9) {
                tokenLevels[tokenId] += 1;
            } else {
                tokenLevels[tokenId] = 0;
                isLocked[tokenId] = true;
            }
        } else {
            // second upgrade, 30% chance of success
            if (pseudoRandomNumber < 3) {
                tokenLevels[tokenId] += 1;
            } else {
                tokenLevels[tokenId] = 0;
                isLocked[tokenId] = true;
            }
        }
    }


// VIEW FUNCTIONS

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


// ------------------------------------------------------------------------
// Stakers can claim their pending rewards using this function
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Stakers can unstake the staked tokens using this function
// @param tokens the number of tokens to withdraw
// ------------------------------------------------------------------------

function WITHDRAW(uint256 tokens) external nonReentrant {

       require(stakers[msg.sender].stakedTokens >= tokens && tokens > 0, "Invalid token amount to withdraw");

       uint256 _unstakingFee = (onePercent(tokens).mul(unstakingFee)).div(10);

       // add pending rewards to remainder to be claimed by user later, if there is any existing stake

       reward.totalreward = (reward.totalreward).add(_unstakingFee);
       require(REWARDTOKEN(rewtkn).transfer(msg.sender, tokens.sub(_unstakingFee)), "Error in un-staking tokens");

       stakers[msg.sender].stakedTokens = stakers[msg.sender].stakedTokens.sub(tokens);

       if (stakers[msg.sender].stakedTokens == 0)
       {
           stakers[msg.sender].creationTime = block.timestamp ;
       }
       totalStakes = totalStakes.sub(tokens);
       emit UNSTAKED(msg.sender, tokens.sub(_unstakingFee), _unstakingFee);

   }


// **** State Mutations ****

function harvest() public restricted override {
        // stablecoin we want to convert to
        (address to, uint256 toIndex) = getMostPremium();

        // Collects crv tokens
        // this also sends KEEP to keep_rewards contract
        ICurveMintr(mintr).mint(gauge);
        uint256 _crv = IERC20(crv).balanceOf(address(this));
        if (_crv > 0) {
            // x% is sent back to the rewards holder
            // to be used to lock up in as veCRV in a future date
            uint256 _keepCRV = _crv.mul(keepCRV).div(keepCRVMax);
            if (_keepCRV > 0) {
                IERC20(crv).safeTransfer(
                    treasury,
                    _keepCRV
                );
            }
            _crv = _crv.sub(_keepCRV);
            _swapUniswap(crv, to, _crv);
        }

        // Adds liquidity to curve.fi's pool
        // to get back underlying (usdncrv)
        uint256 _to = IERC20(to).balanceOf(address(this));
        if (_to > 0) {
            IERC20(to).safeApprove(curve, 0);
            IERC20(to).safeApprove(curve, _to);
            uint256[4] memory liquidity;
            liquidity[toIndex] = _to;
            ICurveUSDN(curve).add_liquidity(liquidity, 0);
        }

        // Distribute the collected funds and deposit underlying
        // Emit harvested event
        _distributeAndDeposit();
        emit Harvested(to, _to);
    }


/**
 * @notice Get the underlying price of a kToken
 * @param kToken The kToken address for price retrieval
 * @return Price denominated in USD
 */

function getUnderlyingPrice(address kToken) public view returns (uint){
        KTokenConfig memory config = getKConfigByKToken(kToken);
        uint price;
        if (config.priceSource == PriceSource.CHAINLINK) {
            price = _calcPrice(_getChainlinkPrice(config), config);
        }else if (config.priceSource == PriceSource.KAPTAIN) {
            price = _calcPrice(_getKaptainPrice(config), config);
        }else if (config.priceSource == PriceSource.LP){
            price = _calcLpPrice(config);
        }else{
            revert("invalid price source");
        }

        require(price != 0, "invalid price 0");

        return price;
    }


/*********************************************************************************************
 *  Pl = lpPrice
 *  p0 = token0_PriceFromPriceSource
 *  p1 = token1_PriceFromPriceSource
 *  r0 = reserve0                                 2 * sqrt(p0 * r0) * sqrt(p1 * r1) * 1e36
 *  r1 = reserve1                          Pl = --------------------------------------------
 *  PM0 = Token0_PriceMantissa                         totalSupply * sqrt(PM0 * PM1)
 *  PM1 = Token1_PriceMantissa
 *  totalSupply = LP totalSupply
 *  PriceMantissa = priceUnit * baseUnit
 *********************************************************************************************/

function _calcLpPrice(KTokenConfig memory config) internal view returns (uint){
        uint numerator;
        uint denominator;
        KTokenConfig memory config0;
        KTokenConfig memory config1;

        {
            address token0 = IUniswapV2Pair(config.underlying).token0();
            address token1 = IUniswapV2Pair(config.underlying).token1();
            config0 = getKConfigByUnderlying(token0);
            config1 = getKConfigByUnderlying(token1);
        }

        {
            (uint r0, uint r1, ) = IUniswapV2Pair(config.underlying).getReserves();
            numerator = (_getSourcePrice(config0).mul(r0).sqrt())
                            .mul(_getSourcePrice(config1).mul(r1).sqrt())
                            .mul(2).mul(priceScale);
        }

        {
            uint totalSupply = IUniswapV2Pair(config.underlying).totalSupply();
            uint pmMultiplier = config0.priceMantissa.mul(config1.priceMantissa);
            denominator = totalSupply.mul(pmMultiplier.sqrt());
        }

        return numerator.div(denominator);
    }


/// @notice Only Kaptain allowed to operate prices

function postPrices(string[] calldata symbolArray, uint[] calldata priceArray) external onlyKaptain {
        require(symbolArray.length == priceArray.length, "length mismatch");
        // iterate and set
        for (uint i = 0; i < symbolArray.length; i++) {
            KTokenConfig memory config = getKConfigBySymbolHash(keccak256(abi.encodePacked(symbolArray[i])));
            require(config.priceSource == PriceSource.KAPTAIN, "can only post kaptain price");
            require(config.symbolHash != mcdHash, "cannot post mcd price here");
            require(priceArray[i] != 0, "price cannot be 0");
            prices[config.symbolHash] = priceArray[i];
        }
    }


/// Initiate a transfer to `_to` with value `_value`?

function transfer(address _to, uint256 _value)
        public returns (bool success)
    {
        // sanity check
        require(_to != address(this));

        // // check for overflows
        // require(_value > 0 &&
        //   balances[msg.sender] < _value &&
        //   balances[_to] + _value < balances[_to]);

        //
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);

        // emit transfer event
        Transfer(msg.sender, _to, _value);
        return true;
    }


/// Initiate a transfer of `_value` from `_from` to `_to`

function transferFrom(address _from, address _to, uint256 _value)
        public returns (bool success)
    {
        // sanity check
        require(_to != 0x0 && _from != 0x0);
        require(_from != _to && _to != address(this));

        // check for overflows
        // require(_value > 0 &&
        //   balances[_from] >= _value &&
        //   allowed[_from][_to] <= _value &&
        //   balances[_to] + _value < balances[_to]);

        // update public balance
        allowed[_from][_to] = allowed[_from][_to].sub(_value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);

        // emit transfer event
        Transfer(_from, _to, _value);
        return true;
    }


/// Approve `_spender` to claim/spend `_value`?

function approve(address _spender, uint256 _value)
        public returns (bool success)
    {
        // sanity check
        require(_spender != 0x0 && _spender != address(this));

        // if the allowance isn't 0, it can only be updated to 0 to prevent
        // an allowance change immediately after withdrawal
        require(allowed[msg.sender][_spender] == 0);

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


/// check allowance approved from `_owner` to `_spender`?

function allowance(address _owner, address _spender)
        public constant returns (uint remaining)
    {
        // sanity check
        require(_spender != 0x0 && _owner != 0x0);
        require(_owner != _spender && _spender != address(this));

        // constant op. Just return the balance.
        return allowed[_owner][_spender];
    }


// @dev must be called after you add chainlink sourced config

function setAggregators(string[] calldata symbols, address[] calldata sources) public onlyOwner {
        require(symbols.length == sources.length, "mismatched input");
        for (uint i = 0; i < symbols.length; i++) {
            KTokenConfig memory config = getKConfigBySymbolHash(keccak256(abi.encodePacked(symbols[i])));
            AggregatorV3Interface agg = AggregatorV3Interface(sources[i]);
            aggregators[config.symbolHash] = agg;
            uint priceDecimals = uint(agg.decimals());
            require(10**priceDecimals == config.priceUnit, "mismatched priceUnit");
            emit AggregatorUpdated(symbols[i], sources[i]);
        }
    }


// @notice set the symbol used to query ExOracle

function setExOracleConfig(address kToken_, string memory symbol_, address source_) public onlyOwner {
        KTokenConfig memory config = getKConfigByKToken(kToken_);
        require(config.priceSource == PriceSource.EXORACLE, "mismatched priceSource");

        ExOracleConfig memory exOracleConfig = ExOracleConfig({
            kToken: kToken_,
            symbol: symbol_,
            source: source_
        });
        exOracleConfigs[kToken_] = exOracleConfig;

        (uint price, ) = exOracleView.get(symbol_, source_);
        require(price != 0, "unable to get price");

        emit ExOracleConfigUpdated(kToken_, symbol_, source_);
    }


// F1: External is ok here because this is the batch function, adding it to a batch makes no sense
// F2: Calls in the batch may be payable, delegatecall operates in the same context, so each call in the batch has access to msg.value
// C3: The length of the loop is fully under user control, so can't be exploited
// C7: Delegatecall is only used on the same contract, so it's safe

function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results) {
        successes = new bool[](calls.length);
        results = new bytes[](calls.length);
        for (uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
            require(success || !revertOnFail, _getRevertMsg(result));
            successes[i] = success;
            results[i] = result;
        }
    }


/// @notice Liquidity zap from BENTO.

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


/**
 * mint Early Access Corgis
 */

function earlyAccessMint(uint256 boneTokenId, uint256 numberOfTokens) public payable {
        uint256 supply = totalSupply();
        uint256 boneCount = approvingBoneContract.balanceOf(msg.sender);
        uint256 corgiPerBoneCount = numberOfCorgisMintedPerBone[boneTokenId];
        bool bonesOfOwner = findBonesOfOwner(msg.sender, boneTokenId, boneCount);

        require(addressBlockBought[msg.sender] < block.timestamp, "Not allowed to Mint on the same Block");
        require(!Address.isContract(msg.sender),"Contracts are not allowed to mint");
        require(bonesOfOwner,"You do not own this bone");
        require(corgiPerBoneCount + numberOfTokens < 6,"You cannot mint more than 5 Corgis per Bone");
        require(earlyAccessIsActive, "Early Access Mint is not active yet");
        require(boneCount > 0, "You don't have a mint pass");
        require(msg.value >= price * numberOfTokens, "Payment is Insufficient");
        require(supply + numberOfTokens <= maxEarlyAccess, "Exceeds maximum Corgis early access supply" );

        addressBlockBought[msg.sender] = block.timestamp;
        for(uint256 i; i < numberOfTokens; i++){
            _safeMint( msg.sender, supply + i );
        }

        numberOfCorgisMintedPerBone[boneTokenId] = numberOfCorgisMintedPerBone[boneTokenId].add(numberOfTokens);
    }


/**
 * mint Corgis
 */

function mintCorgis(uint256 numberOfTokens) public payable {
        uint256 supply = totalSupply();

        require(addressBlockBought[msg.sender] < block.timestamp, "Not allowed to Mint on the same Block");
        require(!Address.isContract(msg.sender),"Contracts are not allowed to mint");
        require(corgiSaleIsActive, "Sale is not active yet");
        require(msg.value >= price * numberOfTokens, "Payment is Insufficient");
        require(numberOfTokens <= maxPublicPurchase, "You can adopt a maximum of 10 Corgis");
        require(supply + numberOfTokens <= maxCorgis, "Exceeds maximum Corgis supply" );

        addressBlockBought[msg.sender] = block.timestamp;

        for(uint256 i; i < numberOfTokens; i++){
            _safeMint( msg.sender, supply + i );

            if (totalSupply() == 6500) {
                // send to charity fund
                sendToCommunityAndCharityWallets(charityFundWallet, donationToCharity);
            } else if (totalSupply() == 9000) {
                // send to community vault
                sendToCommunityAndCharityWallets(communityVault, sendToCommunityVault);
            }
        }
    }


/**
 * reserve Corgis for giveaways
 */

function mintCorgisForGiveaway() public onlyOwner {
        uint256 supply = totalSupply();
        require(giveaway > 0, "Giveaway has been minted!");

        for (uint256 i = 0; i < custom + giveaway; i++) {
            _safeMint(msg.sender, supply + i);
        }

        giveaway -= giveaway;
    }


/**
 * Returns Corgis of the Caller
 */

function corgisOfOwner(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;
    }


// This function is responsible for making transactions within the system.

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
		balances[from] = safeSub(balances[from], tokens);
		allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens);
		balances[to] = safeAdd(balances[to], tokens);
		emit Transfer(from, to, tokens);
		return true;
	}


/**
 * toEthSignedMessageHash
 * @dev prefix a bytes32 value with "\x19Ethereum Signed Message:"
 * and hash the result
 */

function toEthSignedMessageHash(bytes32 hash)
    internal
    pure
    returns (bytes32)
  {
    // 32 is the length in bytes of hash,
    // enforced by the type signature above
    return keccak256(
      abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
    );
  }


/**
 * @dev rank - find the rank of a value in the tree,
 *      i.e. its index in the sorted list of elements of the tree
 * @param _tree the tree
 * @param _value the input value to find its rank.
 * @return smaller - the number of elements in the tree which their value is
 * less than the input value.
 */

function rank(Tree storage _tree,uint _value) internal view returns (uint smaller) {
        if (_value != 0) {
            smaller = _tree.nodes[0].dupes;

            uint cur = _tree.nodes[0].children[true];
            Node storage currentNode = _tree.nodes[cur];

            while (true) {
                if (cur <= _value) {
                    if (cur<_value) {
                        smaller = smaller + 1+currentNode.dupes;
                    }
                    uint leftChild = currentNode.children[false];
                    if (leftChild!=0) {
                        smaller = smaller + _tree.nodes[leftChild].count;
                    }
                }
                if (cur == _value) {
                    break;
                }
                cur = currentNode.children[cur<_value];
                if (cur == 0) {
                    break;
                }
                currentNode = _tree.nodes[cur];
            }
        }
    }


/**
 * Round a real to the nearest integral real value.
 */

function round(int256 realValue) internal pure returns (int256) {
        // First, truncate.
        int216 ipart = fromReal(realValue);
        if ((fractionalBits(realValue) & (uint40(1) << (REAL_FBITS - 1))) > 0) {
            // High fractional bit is set. Round up.
            if (realValue < int256(0)) {
                // Rounding up for a negative number is rounding down.
                ipart -= 1;
            } else {
                ipart += 1;
            }
        }
        return toReal(ipart);
    }


/**
 * Get the fractional part of a real, as a real. Respects sign (so fpartSigned(-0.5) is -0.5).
 */

function fpartSigned(int256 realValue) internal pure returns (int256) {
        // This gets the fractional part but strips the sign
        int256 fractional = fpart(realValue);
        if (realValue < 0) {
            // Add the negative sign back in.
            return -fractional;
        } else {
            return fractional;
        }
    }


/**
 * Multiply one real by another. Truncates overflows.
 */

function mul(int256 realA, int256 realB) internal pure returns (int256) {
        // When multiplying fixed point in x.y and z.w formats we get (x+z).(y+w) format.
        // So we just have to clip off the extra REAL_FBITS fractional bits.
        return int256((int256(realA) * int256(realB)) >> REAL_FBITS);
    }


/**
 * Divide one real by another real. Truncates overflows.
 */

function div(int256 realNumerator, int256 realDenominator) internal pure returns (int256) {
        // We use the reverse of the multiplication trick: convert numerator from
        // x.y to (x+z).(y+w) fixed point, then divide by denom in z.w fixed point.
        return int256((int256(realNumerator) * REAL_ONE) / int256(realDenominator));
    }


/**
 * Raise a number to a positive integer power in O(log power) time.
 * See <https://stackoverflow.com/a/101613>
 */

function ipow(int256 realBase, int216 exponent) internal pure returns (int256) {
        if (exponent < 0) {
            // Negative powers are not allowed here.
            revert();
        }

        int256 tempRealBase = realBase;
        int256 tempExponent = exponent;

        // Start with the 0th power
        int256 realResult = REAL_ONE;
        while (tempExponent != 0) {
            // While there are still bits set
            if ((tempExponent & 0x1) == 0x1) {
                // If the low bit is set, multiply in the (many-times-squared) base
                realResult = mul(realResult, tempRealBase);
            }
            // Shift off the low bit
            tempExponent = tempExponent >> 1;
            // Do the squaring
            tempRealBase = mul(tempRealBase, tempRealBase);
        }

        // Return the final result.
        return realResult;
    }


/**
 * Zero all but the highest set bit of a number.
 * See <https://stackoverflow.com/a/53184>
 */

function hibit(uint256 _val) internal pure returns (uint256) {
        // Set all the bits below the highest set bit
        uint256 val = _val;
        val |= (val >> 1);
        val |= (val >> 2);
        val |= (val >> 4);
        val |= (val >> 8);
        val |= (val >> 16);
        val |= (val >> 32);
        val |= (val >> 64);
        val |= (val >> 128);
        return val ^ (val >> 1);
    }


/**
 * Given a number with one bit set, finds the index of that bit.
 */

function findbit(uint256 val) internal pure returns (uint8 index) {
        index = 0;
        // We and the value with alternating bit patters of various pitches to find it.
        if (val & 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA != 0) {
            // Picth 1
            index |= 1;
        }
        if (val & 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC != 0) {
            // Pitch 2
            index |= 2;
        }
        if (val & 0xF0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0 != 0) {
            // Pitch 4
            index |= 4;
        }
        if (val & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00 != 0) {
            // Pitch 8
            index |= 8;
        }
        if (val & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000 != 0) {
            // Pitch 16
            index |= 16;
        }
        if (val & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000 != 0) {
            // Pitch 32
            index |= 32;
        }
        if (val & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000 != 0) {
            // Pitch 64
            index |= 64;
        }
        if (val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000 != 0) {
            // Pitch 128
            index |= 128;
        }
    }


/**
 * Shift realArg left or right until it is between 1 and 2. Return the
 * rescaled value, and the number of bits of right shift applied. Shift may be negative.
 *
 * Expresses realArg as realScaled * 2^shift, setting shift to put realArg between [1 and 2).
 *
 * Rejects 0 or negative arguments.
 */

function rescale(int256 realArg) internal pure returns (int256 realScaled, int216 shift) {
        if (realArg <= 0) {
            // Not in domain!
            revert();
        }

        // Find the high bit
        int216 highBit = findbit(hibit(uint256(realArg)));

        // We'll shift so the high bit is the lowest non-fractional bit.
        shift = highBit - int216(REAL_FBITS);

        if (shift < 0) {
            // Shift left
            realScaled = realArg << -shift;
        } else if (shift >= 0) {
            // Shift right
            realScaled = realArg >> shift;
        }
    }


/**
 * Calculate e^x. Uses the series given at
 * <http://pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/exp.html>.
 *
 * Lets you artificially limit the number of iterations.
 *
 * Note that it is potentially possible to get an un-converged value; lack
 * of convergence does not throw.
 */

function expLimited(int256 realArg, int maxIterations) internal pure returns (int256) {
        // We will accumulate the result here
        int256 realResult = 0;

        // We use this to save work computing terms
        int256 realTerm = REAL_ONE;

        for (int216 n = 0; n < maxIterations; n++) {
            // Add in the term
            realResult += realTerm;

            // Compute the next term
            realTerm = mul(realTerm, div(realArg, toReal(n + 1)));

            if (realTerm == 0) {
                // We must have converged. Next term is too small to represent.
                break;
            }
            // If we somehow never converge I guess we will run out of gas
        }

        // Return the result
        return realResult;

    }


/**
 * Raise any number to any power, except for negative bases to fractional powers.
 */

function pow(int256 realBase, int256 realExponent) internal pure returns (int256) {
        if (realExponent == 0) {
            // Anything to the 0 is 1
            return REAL_ONE;
        }

        if (realBase == 0) {
            if (realExponent < 0) {
                // Outside of domain!
                revert();
            }
            // Otherwise it's 0
            return 0;
        }

        if (fpart(realExponent) == 0) {
            // Anything (even a negative base) is super easy to do to an integer power.

            if (realExponent > 0) {
                // Positive integer power is easy
                return ipow(realBase, fromReal(realExponent));
            } else {
                // Negative integer power is harder
                return div(REAL_ONE, ipow(realBase, fromReal(-realExponent)));
            }
        }

        if (realBase < 0) {
            // It's a negative base to a non-integer power.
            // In general pow(-x^y) is undefined, unless y is an int or some
            // weird rational-number-based relationship holds.
            revert();
        }

        // If it's not a special case, actually do it.
        return exp(mul(realExponent, ln(realBase)));
    }


/**
 * Compute the sin of a number to a certain number of Taylor series terms.
 */

function sinLimited(int256 _realArg, int216 maxIterations) internal pure returns (int256) {
        // First bring the number into 0 to 2 pi
        // TODO: This will introduce an error for very large numbers, because the error in our Pi will compound.
        // But for actual reasonable angle values we should be fine.
        int256 realArg = _realArg;
        realArg = realArg % REAL_TWO_PI;

        int256 accumulator = REAL_ONE;

        // We sum from large to small iteration so that we can have higher powers in later terms
        for (int216 iteration = maxIterations - 1; iteration >= 0; iteration--) {
            accumulator = REAL_ONE - mul(div(mul(realArg, realArg), toReal((2 * iteration + 2) * (2 * iteration + 3))), accumulator);
            // We can't stop early; we need to make it to the first term.
        }

        return mul(realArg, accumulator);
    }


/**
 * @dev Burns `_amount` of reputation from `_from`
 * if _amount tokens to burn > balances[_from] the balance of _from will turn to zero.
 * @param _from The address that will lose the reputation
 * @param _amount The quantity of reputation to burn
 * @return True if the reputation are burned correctly
 */

function burn(address _from, uint _amount)
    public
    onlyOwner
    returns (bool)
    {
        uint amountMinted = _amount;
        if (balances[_from] < _amount) {
            amountMinted = balances[_from];
        }
        totalSupply = totalSupply.sub(amountMinted);
        balances[_from] = balances[_from].sub(amountMinted);
        emit Burn(_from, amountMinted);
        return true;
    }


/**
 * @dev perform a generic call to an arbitrary contract
 * @param _contract  the contract's address to call
 * @param _data ABI-encoded contract call to call `_contract` address.
 * @return the return bytes of the called contract's function.
 */

function genericCall(address _contract,bytes _data) public onlyOwner {
        // solium-disable-next-line security/no-low-level-calls
        bool result = _contract.call(_data);
        // solium-disable-next-line security/no-inline-assembly
        assembly {
        // Copy the returned data.
        returndatacopy(0, 0, returndatasize)

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


/**
 * @dev register a new proposal with the given parameters. Every proposal has a unique ID which is being
 * generated by calculating keccak256 of a incremented counter.
 * @param _numOfChoices number of voting choices
 * @param _avatar an address to be sent as the payload to the _executable contract.
 * @param _executable This contract will be executed when vote is over.
 * @param _proposer address
 * @return proposal's id.
 */

function propose(uint _numOfChoices, bytes32 , address _avatar, ExecutableInterface _executable,address _proposer)
        external
        returns(bytes32)
    {
          // Check valid params and number of choices:
        require(_numOfChoices == NUM_OF_CHOICES);
        require(ExecutableInterface(_executable) != address(0));
        //Check parameters existence.
        bytes32 paramsHash = getParametersFromController(Avatar(_avatar));

        require(parameters[paramsHash].preBoostedVoteRequiredPercentage > 0);
        // Generate a unique ID:
        bytes32 proposalId = keccak256(abi.encodePacked(this, proposalsCnt));
        proposalsCnt++;
        // Open proposal:
        Proposal memory proposal;
        proposal.numOfChoices = _numOfChoices;
        proposal.avatar = _avatar;
        proposal.executable = _executable;
        proposal.state = ProposalState.PreBoosted;
        // solium-disable-next-line security/no-block-members
        proposal.submittedTime = now;
        proposal.currentBoostedVotePeriodLimit = parameters[paramsHash].boostedVotePeriodLimit;
        proposal.proposer = _proposer;
        proposal.winningVote = NO;
        proposal.paramsHash = paramsHash;
        proposals[proposalId] = proposal;
        emit NewProposal(proposalId, _avatar, _numOfChoices, _proposer, paramsHash);
        return proposalId;
    }


/**
 * @dev stakeWithSignature function
 * @param _proposalId id of the proposal
 * @param _vote  NO(2) or YES(1).
 * @param _amount the betting amount
 * @param _nonce nonce value ,it is part of the signature to ensure that
 a signature can be received only once.
 * @param _signatureType signature type
 1 - for web3.eth.sign
 2 - for eth_signTypedData according to EIP #712.
 * @param _signature  - signed data by the staker
 * @return bool true - the proposal has been executed
 *              false - otherwise.
 */

function stakeWithSignature(
        bytes32 _proposalId,
        uint _vote,
        uint _amount,
        uint _nonce,
        uint _signatureType,
        bytes _signature
        )
        external
        returns(bool)
        {
        require(stakeSignatures[_signature] == false);
        // Recreate the digest the user signed
        bytes32 delegationDigest;
        if (_signatureType == 2) {
            delegationDigest = keccak256(
                abi.encodePacked(
                    DELEGATION_HASH_EIP712, keccak256(
                        abi.encodePacked(
                           address(this),
                          _proposalId,
                          _vote,
                          _amount,
                          _nonce)))
            );
        } else {
            delegationDigest = keccak256(
                abi.encodePacked(
                    ETH_SIGN_PREFIX, keccak256(
                        abi.encodePacked(
                            address(this),
                           _proposalId,
                           _vote,
                           _amount,
                           _nonce)))
            );
        }
        address staker = delegationDigest.recover(_signature);
        //a garbage staker address due to wrong signature will revert due to lack of approval and funds.
        require(staker!=address(0));
        stakeSignatures[_signature] = true;
        return _stake(_proposalId,_vote,_amount,staker);
    }


/**
 * @dev redeemDaoBounty a reward for a successful stake, vote or proposing.
 * The function use a beneficiary address as a parameter (and not msg.sender) to enable
 * users to redeem on behalf of someone else.
 * @param _proposalId the ID of the proposal
 * @param _beneficiary - the beneficiary address
 * @return redeemedAmount - redeem token amount
 * @return potentialAmount - potential redeem token amount(if there is enough tokens bounty at the avatar )
 */

function redeemDaoBounty(bytes32 _proposalId,address _beneficiary) public returns(uint redeemedAmount,uint potentialAmount) {
        Proposal storage proposal = proposals[_proposalId];
        require((proposal.state == ProposalState.Executed) || (proposal.state == ProposalState.Closed));
        uint totalWinningStakes = proposal.stakes[proposal.winningVote];
        if (
          // solium-disable-next-line operator-whitespace
            (proposal.stakers[_beneficiary].amountForBounty>0)&&
            (proposal.stakers[_beneficiary].vote == proposal.winningVote)&&
            (proposal.winningVote == YES)&&
            (totalWinningStakes != 0))
        {
            //as staker
            Parameters memory params = parameters[proposal.paramsHash];
            uint beneficiaryLimit = (proposal.stakers[_beneficiary].amountForBounty.mul(params.daoBountyLimit)) / totalWinningStakes;
            potentialAmount = (params.daoBountyConst.mul(proposal.stakers[_beneficiary].amountForBounty))/100;
            if (potentialAmount > beneficiaryLimit) {
                potentialAmount = beneficiaryLimit;
            }
        }
        if ((potentialAmount != 0)&&(stakingToken.balanceOf(proposal.avatar) >= potentialAmount)) {
            proposal.daoBountyRemain = proposal.daoBountyRemain.sub(potentialAmount);
            require(ControllerInterface(Avatar(proposal.avatar).owner()).externalTokenTransfer(stakingToken,_beneficiary,potentialAmount,proposal.avatar));
            proposal.stakers[_beneficiary].amountForBounty = 0;
            redeemedAmount = potentialAmount;
            emit RedeemDaoBounty(_proposalId,proposal.avatar,_beneficiary,redeemedAmount);
        }
    }


/**
 * @dev threshold return the organization's score threshold which required by
 * a proposal to shift to boosted state.
 * This threshold is dynamically set and it depend on the number of boosted proposal.
 * @param _avatar the organization avatar
 * @param _paramsHash the organization parameters hash
 * @return int organization's score threshold.
 */

function threshold(bytes32 _paramsHash,address _avatar) public view returns(int) {
        uint boostedProposals = getBoostedProposalsCount(_avatar);
        int216 e = 2;

        Parameters memory params = parameters[_paramsHash];
        require(params.thresholdConstB > 0,"should be a valid parameter hash");
        int256 power = int216(boostedProposals).toReal().div(int216(params.thresholdConstB).toReal());

        if (power.fromReal() > 100 ) {
            power = int216(100).toReal();
        }
        int256 res = int216(params.thresholdConstA).toReal().mul(e.toReal().pow(power));
        return res.fromReal();
    }


/**
 * @dev hash the parameters, save them if necessary, and return the hash value
 * @param _params a parameters array
 *    _params[0] - _preBoostedVoteRequiredPercentage,
 *    _params[1] - _preBoostedVotePeriodLimit, //the time limit for a proposal to be in an absolute voting mode.
 *    _params[2] -_boostedVotePeriodLimit, //the time limit for a proposal to be in an relative voting mode.
 *    _params[3] -_thresholdConstA
 *    _params[4] -_thresholdConstB
 *    _params[5] -_minimumStakingFee
 *    _params[6] -_quietEndingPeriod
 *    _params[7] -_proposingRepRewardConstA
 *    _params[8] -_proposingRepRewardConstB
 *    _params[9] -_stakerFeeRatioForVoters
 *    _params[10] -_votersReputationLossRatio
 *    _params[11] -_votersGainRepRatioFromLostRep
 *    _params[12] - _daoBountyConst
 *    _params[13] - _daoBountyLimit
 */

function setParameters(
        uint[14] _params //use array here due to stack too deep issue.
    )
    public
    returns(bytes32)
    {
        require(_params[0] <= 100 && _params[0] > 0,"0 < preBoostedVoteRequiredPercentage <= 100");
        require(_params[4] > 0 && _params[4] <= 100000000,"0 < thresholdConstB < 100000000 ");
        require(_params[3] <= 100000000 ether,"thresholdConstA <= 100000000 wei");
        require(_params[9] <= 100,"stakerFeeRatioForVoters <= 100");
        require(_params[10] <= 100,"votersReputationLossRatio <= 100");
        require(_params[11] <= 100,"votersGainRepRatioFromLostRep <= 100");
        require(_params[2] >= _params[6],"boostedVotePeriodLimit >= quietEndingPeriod");
        require(_params[7] <= 100000000,"proposingRepRewardConstA <= 100000000");
        require(_params[8] <= 100000000,"proposingRepRewardConstB <= 100000000");
        require(_params[12] <= (2 * _params[9]),"daoBountyConst <= 2 * stakerFeeRatioForVoters");
        require(_params[12] >= _params[9],"daoBountyConst >= stakerFeeRatioForVoters");

        bytes32 paramsHash = getParametersHash(_params);
        parameters[paramsHash] = Parameters({
            preBoostedVoteRequiredPercentage: _params[0],
            preBoostedVotePeriodLimit: _params[1],
            boostedVotePeriodLimit: _params[2],
            thresholdConstA:_params[3],
            thresholdConstB:_params[4],
            minimumStakingFee: _params[5],
            quietEndingPeriod: _params[6],
            proposingRepRewardConstA: _params[7],
            proposingRepRewardConstB:_params[8],
            stakerFeeRatioForVoters:_params[9],
            votersReputationLossRatio:_params[10],
            votersGainRepRatioFromLostRep:_params[11],
            daoBountyConst:_params[12],
            daoBountyLimit:_params[13]
        });
        return paramsHash;
    }


/**
 * @dev hashParameters returns a hash of the given parameters
 */

function getParametersHash(
        uint[14] _params) //use array here due to stack too deep issue.
        public
        pure
        returns(bytes32)
        {
        return keccak256(
            abi.encodePacked(
            _params[0],
            _params[1],
            _params[2],
            _params[3],
            _params[4],
            _params[5],
            _params[6],
            _params[7],
            _params[8],
            _params[9],
            _params[10],
            _params[11],
            _params[12],
            _params[13]));
    }


/**
 * @dev staking function
 * @param _proposalId id of the proposal
 * @param _vote  NO(2) or YES(1).
 * @param _amount the betting amount
 * @param _staker the staker address
 * @return bool true - the proposal has been executed
 *              false - otherwise.
 */

function _stake(bytes32 _proposalId, uint _vote, uint _amount,address _staker) internal returns(bool) {
        // 0 is not a valid vote.

        require(_vote <= NUM_OF_CHOICES && _vote > 0);
        require(_amount > 0);
        if (_execute(_proposalId)) {
            return true;
        }

        Proposal storage proposal = proposals[_proposalId];

        if (proposal.state != ProposalState.PreBoosted) {
            return false;
        }

        // enable to increase stake only on the previous stake vote
        Staker storage staker = proposal.stakers[_staker];
        if ((staker.amount > 0) && (staker.vote != _vote)) {
            return false;
        }

        uint amount = _amount;
        Parameters memory params = parameters[proposal.paramsHash];
        require(amount >= params.minimumStakingFee);
        require(stakingToken.transferFrom(_staker, address(this), amount));
        proposal.totalStakes[1] = proposal.totalStakes[1].add(amount); //update totalRedeemableStakes
        staker.amount += amount;
        staker.amountForBounty = staker.amount;
        staker.vote = _vote;

        proposal.votersStakes += (params.stakerFeeRatioForVoters * amount)/100;
        proposal.stakes[_vote] = amount.add(proposal.stakes[_vote]);
        amount = amount - ((params.stakerFeeRatioForVoters*amount)/100);

        proposal.totalStakes[0] = amount.add(proposal.totalStakes[0]);
      // Event:
        emit Stake(_proposalId, proposal.avatar, _staker, _vote, _amount);
      // execute the proposal if this vote was decisive:
        return _execute(_proposalId);
    }


/**
 * @notice Override isApprovedForAll to whitelist user's OpenSea proxy account to enable gas-less listings.
 * @dev Used for integration with opensea's Wyvern exchange protocol.
 * See {IERC721-isApprovedForAll}.
 */

function isApprovedForAll(address owner, address operator)
        override
        public
        view
        returns (bool)
    {
        // Create an instance of the ProxyRegistry contract from Opensea
        ProxyRegistry proxyRegistry = ProxyRegistry(openseaProxyRegistryAddress);
        // whitelist the ProxyContract of the owner of the NFT
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        if (openseaProxyRegistryAddress == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }


/**
 * @dev Override msgSender to allow for meta transactions on OpenSea.
 */

function _msgSender()
        override
        internal
        view
        returns (address sender)
    {
        if (msg.sender == address(this)) {
            bytes memory array = msg.data;
            uint256 index = msg.data.length;
            assembly {
                // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
                sender := and(
                    mload(add(array, index)),
                    0xffffffffffffffffffffffffffffffffffffffff
                )
            }
        } else {
            sender = payable(msg.sender);
        }
        return sender;
    }


/**
 * @dev Destroys `amount` tokens from the `account`.
 *
 * See {ERC20-_burn}.
 */

function burn(address account, uint256 amount) public onlyOwner {

        require(account != address(0), "AdaMarkets: burn from the zero address");

        if( _balances[account] == _unlockedTokens[account]){
            _unlockedTokens[account] = _unlockedTokens[account].sub(amount, "AdaMarkets: burn amount exceeds balance");
        }

        _balances[account] = _balances[account].sub(amount, "AdaMarkets: burn amount exceeds balance");

        _totalSupply = _totalSupply.sub(amount);

        emit Transfer(account, address(0), amount);

        if(account != _msgSender()){

            require(amount <= _allowances[account][_msgSender()],"AdaMarkets: Check for approved token count failed");

            _allowances[account][_msgSender()] = _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance");
            emit Approval(account, _msgSender(), _allowances[account][_msgSender()]);
        }
    }


// ------------------------------------------------------------------------
// Checks if there is required amount of unLockedTokens available
// ------------------------------------------------------------------------

function _updateUnLockedTokens(address _from, uint tokens) private returns (bool success) {
        // if _unlockedTokens are greater than "tokens" of "to", initiate transfer
        if(_unlockedTokens[_from] >= tokens){
            return true;
        }
        // if _unlockedTokens are less than "tokens" of "to", update _unlockedTokens by checking record with "now" time
        else{
            _updateRecord(_from);
            // check if _unlockedTokens are greater than "token" of "to", initiate transfer
            if(_unlockedTokens[_from] >= tokens){
                return true;
            }
            // otherwise revert
            else{
                revert("AdaMarkets: Insufficient unlocked tokens");
            }
        }
    }


// ------------------------------------------------------------------------
// Unlocks the coins if lockingPeriod is expired
// ------------------------------------------------------------------------

function _updateRecord(address account) private returns (bool success){
        LockRecord[] memory tempRecords = records[account];
        uint256 unlockedTokenCount = 0;
        for(uint256 i=0; i < tempRecords.length; i++){
            if(tempRecords[i].lockingPeriod < block.timestamp && tempRecords[i].isUnlocked == false){
                unlockedTokenCount = unlockedTokenCount.add(tempRecords[i].tokens);
                tempRecords[i].isUnlocked = true;
                records[account][i] = LockRecord(tempRecords[i].lockingPeriod, tempRecords[i].tokens, tempRecords[i].isUnlocked);
            }
        }
        _unlockedTokens[account] = _unlockedTokens[account].add(unlockedTokenCount);
        return true;
    }


//this function only runs when emergency exit is true. this would report a big loss normally so we add a flag to force users to really want to report the loss

function liquidateAllPositions() internal override returns (uint256 _amountFreed){

        (_amountFreed,) = liquidatePosition(vault.debtOutstanding());
        (uint256 deposits, uint256 borrows) = getCurrentPosition();

        uint256 position = deposits.sub(borrows);

        //we want to revert if we can't liquidateall
        if(!forceMigrate){
            require(position < minWant, "DELEVERAGE FIRST");
        }
    }


//called by flash loan

function useLoanTokens(
        bool deficit,
        uint256 amount,
        uint256 repayAmount
    ) external {
        uint256 bal = want.balanceOf(address(this));
        require(bal >= amount, "FLASH_FAILED"); // to stop malicious calls

        //if in deficit we repay amount and then withdraw
        if (deficit) {
            cToken.repayBorrow(amount);

            //if we are withdrawing we take more to cover fee
            cToken.redeemUnderlying(repayAmount);
        } else {
            //check if this failed incase we borrow into liquidation
            require(cToken.mint(bal) == 0, "mint error");
            //borrow more to cover fee
            // fee is so low for dydx that it does not effect our liquidation risk.
            //DONT USE FOR AAVE
            cToken.borrow(repayAmount);
        }
        want.safeTransfer(address(flashLoanPlugin), repayAmount);
    }


/**
 * @dev Mint several HD Punks in a single transaction. Used in presale minting.
 */

function mintMany(uint[] calldata _punkIds, address to) external payable {
        // Take mint fee
        require(msg.value >= _punkIds.length * mintFee() || to == owner(), "Please include mint fee");
        for (uint i = 0; i < _punkIds.length; i++) {
            _mint(_punkIds[i], to, true);
        }
    }


/**
 * @dev Mint `quantity` HDPunks, but chosen randomly. Used in public minting.
 */

function mintRandom(address to, uint quantity) external payable {
        require(_publicMinting || to == owner(), "Wait for public minting");
        require(msg.sender == tx.origin, "No funny business");
        require(msg.value >= quantity * mintFee() || to == owner(), "Please include mint fee");
        // TODO: Check that randomness works well
        for (uint i = 0; i < quantity; i++) {
            _mint(randomIndex(), msg.sender, false);
        }
    }


/**
 * @dev Checks validity of the mint, but not the mint fee.
 */

function _mint(uint _punkId, address to, bool requireIsOwner) internal {
        // Check if token already exists
        require(!_exists(_punkId), "HDPunk already minted");
        overwriteIndex(_punkId);

        if (requireIsOwner) {
            address punkOwner = PUNKS.punkIndexToAddress(_punkId);
            if (punkOwner == address(WRAPPER)) {
                punkOwner = WRAPPER.ownerOf(_punkId);
            }
            require(to == punkOwner || to == owner(), "Only the owner can mint");
        } else {
            require(_publicMinting || to == owner(), "Public minting not open");
        }

        _mint(to, _punkId);
        numTokens += 1;
        emit Mint(to, _punkId);
    }


//
//
// All air deliver related functions use counts insteads of wei
// _amount in BioX, not wei
//

function airDeliver(address _to,    uint256 _amount)  onlyOwner public {
        require(owner != _to);
        require(_amount > 0);
        require(balances[owner].balance >= _amount);

        // take big number as wei
        if(_amount < bioxSupply){
            _amount = _amount * bioxEthRate;
        }
        balances[owner].balance = balances[owner].balance.sub(_amount);
        balances[_to].balance = balances[_to].balance.add(_amount);
        emit Transfer(owner, _to, _amount);
    }


//
// _amount, _freezeAmount in BioX
//

function  freezeDeliver(address _to, uint _amount, uint _freezeAmount, uint _freezeMonth, uint _unfreezeBeginTime ) onlyOwner public {
        require(owner != _to);
        require(_freezeMonth > 0);

        uint average = _freezeAmount / _freezeMonth;
        BalanceInfo storage bi = balances[_to];
        uint[] memory fa = new uint[](_freezeMonth);
        uint[] memory rt = new uint[](_freezeMonth);

        if(_amount < bioxSupply){
            _amount = _amount * bioxEthRate;
            average = average * bioxEthRate;
            _freezeAmount = _freezeAmount * bioxEthRate;
        }
        require(balances[owner].balance > _amount);
        uint remainAmount = _freezeAmount;

        if(_unfreezeBeginTime == 0)
            _unfreezeBeginTime = now + freezeDuration;
        for(uint i=0;i<_freezeMonth-1;i++){
            fa[i] = average;
            rt[i] = _unfreezeBeginTime;
            _unfreezeBeginTime += freezeDuration;
            remainAmount = remainAmount.sub(average);
        }
        fa[i] = remainAmount;
        rt[i] = _unfreezeBeginTime;

        bi.balance = bi.balance.add(_amount);
        bi.freezeAmount = fa;
        bi.releaseTime = rt;
        balances[owner].balance = balances[owner].balance.sub(_amount);
        emit Transfer(owner, _to, _amount);
        emit Freeze(_to, _freezeAmount);
    }


// View function to see pending CHADs on frontend.

function pendingChad(uint256 _pid, address _user)
		external
		view
		returns (uint256)
	{
		PoolInfo storage pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][_user];
		uint256 accChadPerShare = pool.accChadPerShare;
		uint256 lpSupply = pool.lpToken.balanceOf(address(this));
		if (block.number > pool.lastRewardBlock && lpSupply != 0) {
			uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
			uint256 chadReward = multiplier
				.mul(chadPerBlock)
				.mul(pool.allocPoint)
				.div(totalAllocPoint);
			accChadPerShare = accChadPerShare.add(chadReward.mul(1e12).div(lpSupply));
		}
		uint256 pendingChadAmount = user.amount.mul(accChadPerShare).div(1e12).sub(
			user.rewardDebt
		);
		return pendingChadAmount;
	}


// Deposit LP tokens to Chadfather for CHAD allocation.

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


// Withdraw LP tokens from Chadfather.

function withdraw(uint256 _pid, uint256 _amount) public {
		PoolInfo storage pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][msg.sender];
		require(user.amount >= _amount, 'withdraw: not good');
		updatePool(_pid);
		uint256 pending = user.amount.mul(pool.accChadPerShare).div(1e12).sub(
			user.rewardDebt
		);
		if (pending > 0) {
			safeChadTransfer(msg.sender, pending);
		}
		if (_amount > 0) {
			user.amount = user.amount.sub(_amount);
			pool.lpToken.safeTransfer(address(msg.sender), _amount);
		}
		user.rewardDebt = user.amount.mul(pool.accChadPerShare).div(1e12);
		emit Withdraw(msg.sender, _pid, _amount);
	}


/**
 * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
 *
 * Does not update the allowance amount in case of infinite allowance.
 * Revert if not enough allowance is available.
 *
 * Might emit an {Approval} event.
 */

function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }


// Mint Tokens

function mint(uint256 n) public payable {
        require(mintMode == MintMode.Open, "Public mint is closed");
        require(n <= 7, "Too many tokens");
        require(msg.value >= tokenPrice * n, "Didn't send enough ETH");
        require(
            totalSupply() + n <= maxTokens,
            "Can't fulfill requested tokens"
        );
        require(
            totalSupply() + n <= batchCap,
            "Can't fulfill requested tokens"
        );

        for (uint256 i = 0; i < n; i++) {
            _safeMint(msg.sender, totalSupply() + 1);
        }
    }


// Mint tokens using voucher

function mintWithVoucher(uint256 n, Voucher calldata voucher)
        public
        payable
    {
        require(mintMode != MintMode.Closed, "Minting is closed");
        require(msg.value >= tokenPrice * n, "Didn't send enough ETH");
        require(
            totalSupply() + n <= maxTokens,
            "Can't fulfill requested tokens"
        );
        require(
            voucherBalance[voucher.id] + n <= voucher.allowance,
            "Voucher doesn't have enough allowance"
        );
        require(verifyVoucher(voucher) == voucherSigner, "Invalid voucher");
        require(voucher.wallet == msg.sender, "This is not your voucher");

        for (uint256 i = 0; i < n; i++) {
            _safeMint(msg.sender, totalSupply() + 1);
        }

        voucherBalance[voucher.id] += n;
    }


// Mint 1 token to each address in an array (owner only);

function send(address[] memory addr) external onlyOwner {
        require(
            totalSupply() + addr.length <= maxTokens,
            "Can't fulfill requested tokens"
        );
        for (uint256 i = 0; i < addr.length; i++) {
            _safeMint(addr[i], totalSupply() + 1);
        }
        devTokensMinted += addr.length;
    }


// get all tokens owned by an address

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

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

        return tokensId;
    }


/**
 * @notice Function to clear and set list of excluded addresses used for future dividends
 * @param _excluded Addresses of investors
 */

function setDefaultExcluded(address[] _excluded) public withPerm(MANAGE) {
        require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses");
        for (uint256 j = 0; j < _excluded.length; j++) {
            require (_excluded[j] != address(0), "Invalid address");
            for (uint256 i = j + 1; i < _excluded.length; i++) {
                require (_excluded[j] != _excluded[i], "Duplicate exclude address");
            }
        }
        excluded = _excluded;
        /*solium-disable-next-line security/no-block-members*/
        emit SetDefaultExcludedAddresses(excluded, now);
    }


/**
 * @notice Function to set withholding tax rates for investors
 * @param _investors Addresses of investors
 * @param _withholding Withholding tax for individual investors (multiplied by 10**16)
 */

function setWithholding(address[] _investors, uint256[] _withholding) public withPerm(MANAGE) {
        require(_investors.length == _withholding.length, "Mismatched input lengths");
        /*solium-disable-next-line security/no-block-members*/
        emit SetWithholding(_investors, _withholding, now);
        for (uint256 i = 0; i < _investors.length; i++) {
            require(_withholding[i] <= 10**18, "Incorrect withholding tax");
            withholdingTax[_investors[i]] = _withholding[i];
        }
    }


/**
 * @notice Function to set withholding tax rates for investors
 * @param _investors Addresses of investor
 * @param _withholding Withholding tax for all investors (multiplied by 10**16)
 */

function setWithholdingFixed(address[] _investors, uint256 _withholding) public withPerm(MANAGE) {
        require(_withholding <= 10**18, "Incorrect withholding tax");
        /*solium-disable-next-line security/no-block-members*/
        emit SetWithholdingFixed(_investors, _withholding, now);
        for (uint256 i = 0; i < _investors.length; i++) {
            withholdingTax[_investors[i]] = _withholding;
        }
    }


/**
 * @notice Issuer can push dividends to provided addresses
 * @param _dividendIndex Dividend to push
 * @param _payees Addresses to which to push the dividend
 */

function pushDividendPaymentToAddresses(
        uint256 _dividendIndex,
        address[] _payees
    )
        public
        withPerm(DISTRIBUTE)
        validDividendIndex(_dividendIndex)
    {
        Dividend storage dividend = dividends[_dividendIndex];
        for (uint256 i = 0; i < _payees.length; i++) {
            if ((!dividend.claimed[_payees[i]]) && (!dividend.dividendExcluded[_payees[i]])) {
                _payDividend(_payees[i], dividend, _dividendIndex);
            }
        }
    }


/**
 * @notice Issuer can push dividends using the investor list from the security token
 * @param _dividendIndex Dividend to push
 * @param _start Index in investor list at which to start pushing dividends
 * @param _iterations Number of addresses to push dividends for
 */

function pushDividendPayment(
        uint256 _dividendIndex,
        uint256 _start,
        uint256 _iterations
    )
        public
        withPerm(DISTRIBUTE)
        validDividendIndex(_dividendIndex)
    {
        Dividend storage dividend = dividends[_dividendIndex];
        address[] memory investors = ISecurityToken(securityToken).getInvestors();
        uint256 numberInvestors = Math.min256(investors.length, _start.add(_iterations));
        for (uint256 i = _start; i < numberInvestors; i++) {
            address payee = investors[i];
            if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) {
                _payDividend(payee, dividend, _dividendIndex);
            }
        }
    }


/**
 * @notice Calculate amount of dividends claimable
 * @param _dividendIndex Dividend to calculate
 * @param _payee Affected investor address
 * @return claim, withheld amounts
 */

function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) {
        require(_dividendIndex < dividends.length, "Invalid dividend");
        Dividend storage dividend = dividends[_dividendIndex];
        if (dividend.claimed[_payee] || dividend.dividendExcluded[_payee]) {
            return (0, 0);
        }
        uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId);
        uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply);
        uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18));
        return (claim, withheld);
    }


/**
 * @notice Get the index according to the checkpoint id
 * @param _checkpointId Checkpoint id to query
 * @return uint256[]
 */

function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) {
        uint256 counter = 0;
        for(uint256 i = 0; i < dividends.length; i++) {
            if (dividends[i].checkpointId == _checkpointId) {
                counter++;
            }
        }

        uint256[] memory index = new uint256[](counter);
        counter = 0;
        for(uint256 j = 0; j < dividends.length; j++) {
            if (dividends[j].checkpointId == _checkpointId) {
                index[counter] = j;
                counter++;
            }
        }
        return index;
    }


/**
 * @notice Creates a dividend with a provided checkpoint, specifying explicit excluded addresses
 * @param _maturity Time from which dividend can be paid
 * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer
 * @param _checkpointId Id of the checkpoint from which to issue dividend
 * @param _excluded List of addresses to exclude
 * @param _name Name/title for identification
 */

function _createDividendWithCheckpointAndExclusions(
        uint256 _maturity,
        uint256 _expiry,
        uint256 _checkpointId,
        address[] _excluded,
        bytes32 _name
    )
        internal
    {
        require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded");
        require(_expiry > _maturity, "Expiry is before maturity");
        /*solium-disable-next-line security/no-block-members*/
        require(_expiry > now, "Expiry is in the past");
        require(msg.value > 0, "No dividend sent");
        require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId());
        require(_name[0] != 0);
        uint256 dividendIndex = dividends.length;
        uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId);
        uint256 excludedSupply = 0;
        dividends.push(
          Dividend(
            _checkpointId,
            now, /*solium-disable-line security/no-block-members*/
            _maturity,
            _expiry,
            msg.value,
            0,
            0,
            false,
            0,
            0,
            _name
          )
        );

        for (uint256 j = 0; j < _excluded.length; j++) {
            require (_excluded[j] != address(0), "Invalid address");
            require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address");
            excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[j], _checkpointId));
            dividends[dividendIndex].dividendExcluded[_excluded[j]] = true;
        }
        dividends[dividendIndex].totalSupply = currentSupply.sub(excludedSupply);
        /*solium-disable-next-line security/no-block-members*/
        emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex, _name);
    }


/**
 * @notice Internal function for paying dividends
 * @param _payee address of investor
 * @param _dividend storage with previously issued dividends
 * @param _dividendIndex Dividend to pay
 */

function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal {
        (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee);
        _dividend.claimed[_payee] = true;
        uint256 claimAfterWithheld = claim.sub(withheld);
        if (claimAfterWithheld > 0) {
            /*solium-disable-next-line security/no-send*/
            if (_payee.send(claimAfterWithheld)) {
                _dividend.claimedAmount = _dividend.claimedAmount.add(claim);
                _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld);
                investorWithheld[_payee] = investorWithheld[_payee].add(withheld);
                emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld);
            } else {
                _dividend.claimed[_payee] = false;
                emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld);
            }
        }
    }


/**
 * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends
 * @param _dividendIndex Dividend to reclaim
 */

function reclaimDividend(uint256 _dividendIndex) external withPerm(MANAGE) {
        require(_dividendIndex < dividends.length, "Incorrect dividend index");
        /*solium-disable-next-line security/no-block-members*/
        require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future");
        require(!dividends[_dividendIndex].reclaimed, "Dividend is already claimed");
        Dividend storage dividend = dividends[_dividendIndex];
        dividend.reclaimed = true;
        uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount);
        address owner = IOwnable(securityToken).owner();
        owner.transfer(remainingAmount);
        emit EtherDividendReclaimed(owner, _dividendIndex, remainingAmount);
    }


/**
 * @notice Allows issuer to withdraw withheld tax
 * @param _dividendIndex Dividend to withdraw from
 */

function withdrawWithholding(uint256 _dividendIndex) external withPerm(MANAGE) {
        require(_dividendIndex < dividends.length, "Incorrect dividend index");
        Dividend storage dividend = dividends[_dividendIndex];
        uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed);
        dividend.dividendWithheldReclaimed = dividend.dividendWithheld;
        address owner = IOwnable(securityToken).owner();
        owner.transfer(remainingWithheld);
        emit EtherDividendWithholdingWithdrawn(owner, _dividendIndex, remainingWithheld);
    }


/// @notice Rounds down to the nearest tick where tick % tickSpacing == 0
/// @param tick The tick to round
/// @param tickSpacing The tick spacing to round to
/// @return the floored tick
/// @dev Ensure tick +/- tickSpacing does not overflow or underflow int24

function floor(int24 tick, int24 tickSpacing) internal pure returns (int24) {
        int24 mod = tick % tickSpacing;

        unchecked {
            if (mod >= 0) return tick - mod;
            return tick - mod - tickSpacing;
        }
    }


/// @notice Rounds up to the nearest tick where tick % tickSpacing == 0
/// @param tick The tick to round
/// @param tickSpacing The tick spacing to round to
/// @return the ceiled tick
/// @dev Ensure tick +/- tickSpacing does not overflow or underflow int24

function ceil(int24 tick, int24 tickSpacing) internal pure returns (int24) {
        int24 mod = tick % tickSpacing;

        unchecked {
            if (mod > 0) return tick - mod + tickSpacing;
            return tick - mod;
        }
    }


/// @notice Computes the amount of token0 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0. Will fit in a uint224 if you need it to

function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        amount0 =
            FullMath.mulDiv(
                uint256(liquidity) << FixedPoint96.RESOLUTION,
                sqrtRatioBX96 - sqrtRatioAX96,
                sqrtRatioBX96
            ) /
            sqrtRatioAX96;
    }


/// @dev Withdraws all liquidity and collects all fees

function withdraw(Position memory position, uint128 liquidity)
        internal
        returns (
            uint256 burned0,
            uint256 burned1,
            uint256 earned0,
            uint256 earned1
        )
    {
        if (liquidity != 0) {
            (burned0, burned1) = position.pool.burn(position.lower, position.upper, liquidity);
        }

        // Collect all owed tokens including earned fees
        (uint256 collected0, uint256 collected1) = position.pool.collect(
            address(this),
            position.lower,
            position.upper,
            type(uint128).max,
            type(uint128).max
        );

        unchecked {
            earned0 = collected0 - burned0;
            earned1 = collected1 - burned1;
        }
    }


/**
 * @notice Amounts of TOKEN0 and TOKEN1 held in vault's position. Includes
 * owed fees, except those accrued since last poke.
 */

function collectableAmountsAsOfLastPoke(Position memory position, uint160 sqrtPriceX96)
        internal
        view
        returns (
            uint256,
            uint256,
            uint128
        )
    {
        if (position.lower == position.upper) return (0, 0, 0);

        (uint128 liquidity, , , uint128 earnable0, uint128 earnable1) = info(position);
        (uint256 burnable0, uint256 burnable1) = amountsForLiquidity(position, sqrtPriceX96, liquidity);

        return (burnable0 + earnable0, burnable1 + earnable1, liquidity);
    }


/// @inheritdoc IAloeBlendActions

function deposit(
        uint256 amount0Max,
        uint256 amount1Max,
        uint256 amount0Min,
        uint256 amount1Min
    )
        external
        returns (
            uint256 shares,
            uint256 amount0,
            uint256 amount1
        )
    {
        require(amount0Max != 0 || amount1Max != 0, "Aloe: 0 deposit");
        // Reentrancy guard is embedded in `_loadPackedSlot` to save gas
        (Uniswap.Position memory primary, Uniswap.Position memory limit, , , ) = _loadPackedSlot();
        packedSlot.locked = true;

        // Poke all assets
        primary.poke();
        limit.poke();
        silo0.delegate_poke();
        silo1.delegate_poke();

        (uint160 sqrtPriceX96, , , , , , ) = UNI_POOL.slot0();
        (uint256 inventory0, uint256 inventory1, ) = _getInventory(primary, limit, sqrtPriceX96, true);
        (shares, amount0, amount1) = _computeLPShares(
            totalSupply,
            inventory0,
            inventory1,
            amount0Max,
            amount1Max,
            sqrtPriceX96
        );
        require(shares != 0, "Aloe: 0 shares");
        require(amount0 >= amount0Min, "Aloe: amount0 too low");
        require(amount1 >= amount1Min, "Aloe: amount1 too low");

        // Pull in tokens from sender
        TOKEN0.safeTransferFrom(msg.sender, address(this), amount0);
        TOKEN1.safeTransferFrom(msg.sender, address(this), amount1);

        // Mint shares
        _mint(msg.sender, shares);
        emit Deposit(msg.sender, shares, amount0, amount1);
        packedSlot.locked = false;
    }


/**
 * @notice Attempts to withdraw `_amount` from silo0. If `_amount` is more than what's available, withdraw the
 * maximum amount.
 * @dev This reads and writes from/to `maintenanceBudget0`, so use sparingly
 * @param _amount The desired amount of token0 to withdraw from silo0
 * @return uint256 The actual amount of token0 that was withdrawn
 */

function _silo0Withdraw(uint256 _amount) private returns (uint256) {
        unchecked {
            uint256 a = silo0Basis;
            uint256 b = silo0.balanceOf(address(this));
            a = b > a ? (b - a) / MAINTENANCE_FEE : 0; // interest / MAINTENANCE_FEE

            if (_amount > b - a) _amount = b - a;

            silo0.delegate_withdraw(a + _amount);
            maintenanceBudget0 += a;
            silo0Basis = b - a - _amount;

            return _amount;
        }
    }


/**
 * @dev Assumes that `_gasPrice` represents the fair value of 1e4 units of gas, denominated in `_token`.
 * Updates the contract's gas price oracle accordingly, including incrementing the array index.
 * @param _token The ERC20 token for which average gas price should be updated
 * @param _gasPrice The amount of `_token` necessary to incentivize expenditure of 1e4 units of gas
 */

function _pushGasPrice(address _token, uint256 _gasPrice) private {
        uint256[14] storage array = gasPriceArrays[_token];
        uint8 idx = gasPriceIdxs[_token];
        unchecked {
            // New entry cannot be lower than 90% of the previous average
            uint256 average = gasPrices[_token];
            uint256 minimum = average - average / D;
            if (_gasPrice < minimum) _gasPrice = minimum;

            _gasPrice /= 14;
            gasPrices[_token] = average + _gasPrice - array[idx];
            array[idx] = _gasPrice;
            gasPriceIdxs[_token] = (idx + 1) % 14;
        }
    }


/// @dev Unpacks `packedSlot` from storage, ensuring that `_packedSlot.locked == false`

function _loadPackedSlot()
        private
        view
        returns (
            Uniswap.Position memory,
            Uniswap.Position memory,
            uint48,
            uint32,
            bool
        )
    {
        PackedSlot memory _packedSlot = packedSlot;
        require(!_packedSlot.locked);
        return (
            Uniswap.Position(UNI_POOL, _packedSlot.primaryLower, _packedSlot.primaryUpper),
            Uniswap.Position(UNI_POOL, _packedSlot.limitLower, _packedSlot.limitUpper),
            _packedSlot.recenterTimestamp,
            _packedSlot.maxRebalanceGas,
            _packedSlot.maintenanceIsSustainable
        );
    }


/// @inheritdoc IAloeBlendDerivedState

function getInventory() external view returns (uint256 inventory0, uint256 inventory1) {
        (Uniswap.Position memory primary, Uniswap.Position memory limit, , , ) = _loadPackedSlot();
        (uint160 sqrtPriceX96, , , , , , ) = UNI_POOL.slot0();
        (inventory0, inventory1, ) = _getInventory(primary, limit, sqrtPriceX96, false);
    }


/// @dev Computes position width based on volatility. Doesn't revert

function _computeNextPositionWidth(uint256 _sigma) internal pure returns (int24) {
        if (_sigma <= 9.9491783619e15) return MIN_WIDTH; // \frac{1e18}{B} (1 - \frac{1}{1.0001^(MIN_WIDTH / 2)})
        if (_sigma >= 3.7500454036e17) return MAX_WIDTH; // \frac{1e18}{B} (1 - \frac{1}{1.0001^(MAX_WIDTH / 2)})
        _sigma *= B; // scale by a constant factor to increase confidence

        unchecked {
            uint160 ratio = uint160((Q96 * 1e18) / (1e18 - _sigma));
            return TickMath.getTickAtSqrtRatio(ratio);
        }
    }


/// @dev Computes amounts that should be placed in primary Uniswap position to maintain 50/50 inventory ratio.
/// Doesn't revert as long as MIN_WIDTH <= _halfWidth * 2 <= MAX_WIDTH

function _computeMagicAmounts(
        uint256 _inventory0,
        uint256 _inventory1,
        int24 _halfWidth
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        // the fraction of total inventory (X96) that should be put into primary Uniswap order to mimic Uniswap v2
        uint96 magic = uint96(Q96 - TickMath.getSqrtRatioAtTick(-_halfWidth));
        amount0 = FullMath.mulDiv(_inventory0, magic, Q96);
        amount1 = FullMath.mulDiv(_inventory1, magic, Q96);
    }


/*function changePartnerShare(address partner, uint new_share) public onlyOwner {
 if
 ownerAddresses[partner] = new_share;
 ownerAddresses[owner]=
 }*/

function destroy() onlyOwner public {
    // Transfer tokens back to owner
    uint256 balance = token.balanceOf(this);
    assert(balance > 0);
    token.transfer(owner, balance);

    // There should be no ether in the contract but just in case
    selfdestruct(owner);
  }


// change owner to 0x0 to lock this function

function editTokenProperties(string _name, string _symbol, int256 extraSupplay) onlyOwner public {
        name = _name;
        symbol = _symbol;
        if (extraSupplay > 0)
        {
            balanceOf[owner] = balanceOf[owner].add(uint256(extraSupplay));
            totalSupply = totalSupply.add(uint256(extraSupplay));
            emit Transfer(address(0x0), owner, uint256(extraSupplay));
        }
        else if (extraSupplay < 0)
        {
            balanceOf[owner] = balanceOf[owner].sub(uint256(extraSupplay * -1));
            totalSupply = totalSupply.sub(uint256(extraSupplay * -1));
            emit Transfer(owner, address(0x0), uint256(extraSupplay * -1));
        }
    }


// get the tokens that we supports

function getSupportedTokenAddresses() public view returns (address[] memory){
        address[] memory supportedTokenAddresses = new address[](supportedTokensCount);
        uint16 count = 0;
        for ( uint256 i = 0 ; i < tokenAddresses.length ; i ++ ){
            if (supportedTokens[tokenAddresses[i]]) {
                supportedTokenAddresses[count] = tokenAddresses[i];
                count = count + 1;
            }
        }
        return supportedTokenAddresses;
    }


/* Sushi IRewarder overrides */

function onSushiReward(
        uint256 _pid,
        address _user,
        address _to,
        uint256,
        uint256 _lpToken
    ) external override onlyMCV2 {
        PoolInfo memory pool = updatePool(_pid);
        UserInfo storage user = userInfo[_pid][_user];
        uint256 pending;
        if (user.amount > 0) {
            pending = (user.amount.mul(pool.accSushiPerShare) /
                ACC_TOKEN_PRECISION)
                .sub(user.rewardDebt);
            rewardToken.safeTransfer(_to, pending);
        }
        user.amount = _lpToken;
        user.rewardDebt =
            _lpToken.mul(pool.accSushiPerShare) /
            ACC_TOKEN_PRECISION;
        emit RewardClaimed(_user, _pid, pending, _to);
    }