// Initialise

function init() external onlyOwner {
        require(!initialized, "Could be initialized only once");
        require(reserved.owner() == address(this), "Sale should be the owner of Reserved funds");

        uint256 _initialSupplyInWei = tokenContract.balanceOf(address(this));
        require(
            _initialSupplyInWei > 0,
            "Initial supply should be > 0"
        );

        initialSupplyInWei = _initialSupplyInWei;

        uint256 _tokensToReserveInWei = _getInitialSupplyPercentInWei(100 - percentSale);
        initialized = true;
        tokenContract.safeTransfer(address(reserved), _tokensToReserveInWei);
    }


// Buy tokens with ETH

function buyTokens() external payable {
        uint256 _ethSent = msg.value;
        require(saleEnabled, "The EDDA Initial Token Offering is not yet started");
        require(_ethSent >= minBuyWei, "Minimum purchase per transaction is 0.1 ETH");

        uint256 _tokens = _ethSent.mul(SCALAR).div(priceInWei);

        // Check that the purchase amount does not exceed remaining tokens
        require(_tokens <= _remainingTokens(), "Not enough tokens remain");

        if (purchases[msg.sender] == 0) {
            buyers.push(msg.sender);
        }
        purchases[msg.sender] = purchases[msg.sender].add(_tokens);
        require(purchases[msg.sender] <= maxBuyTokens.mul(SCALAR), "Exceeded maximum purchase limit per address");

        purchased = purchased.add(_tokens);

        emit Sell(msg.sender, _tokens);
    }


// Distribute purchased tokens

function distribute(uint256 _offset) external onlyOwner returns (uint256) {
        uint256 _distributed = 0;
        for (uint256 i = _offset; i < buyers.length; i++) {
            address _buyer = buyers[i];
            uint256 _purchase = purchases[_buyer];
            if (_purchase > 0) {
                purchases[_buyer] = 0;
                tokenContract.safeTransfer(_buyer, _purchase);
                if (++_distributed >= distributionBatch) {
                    break;
                }
            }
        }
        return _distributed;
    }


// transfer the balance from sender's account to another one

function transfer(address _to, uint256 _value) {
        // prevent transfer to 0x0 address
        require(_to != 0x0);
        // sender and recipient should be different
        require(msg.sender != _to);
        // check if the sender has enough coins
        require(_value > 0 && balanceOf[msg.sender] >= _value);
        // check for overflows
        require(balanceOf[_to] + _value > balanceOf[_to]);
        // subtract coins from sender's account
        balanceOf[msg.sender] -= _value;
        // add coins to recipient's account
        balanceOf[_to] += _value;
        // notify listeners about this transfer
        Transfer(msg.sender, _to, _value);
    }


/**
 * @dev change crowdsale ETH rate
 * @param newRate Figure that corresponds to the new ETH rate per token
 */

function setRate(uint256 newRate) external onlyOwner {
        require(isWeiAccepted, "Sale must allow Wei for purchases to set a rate for Wei!");
        require(newRate != 0, "ETH rate must be more than 0!");

        emit TokenRateChanged(rate, newRate);
        rate = newRate;
    }


/**
 * @dev allows sale to receive wei or not
 */

function setIsWeiAccepted(bool _isWeiAccepted, uint256 _rate) external onlyOwner {
        if (_isWeiAccepted) {
            require(_rate > 0, "When accepting Wei, you need to set a conversion rate!");
        } else {
            require(_rate == 0, "When not accepting Wei, you need to set a conversion rate of 0!");
        }

        isWeiAccepted = _isWeiAccepted;
        rate = _rate;
    }


/**
 * @notice ERC1155 single transfer receiver which redeem a voucher.
 * @dev Reverts if the transfer was not operated through `gameeVouchersContract`.
 * @dev Reverts if the `id` is zero.
 * @dev Reverts if the `value` is zero.
 * @dev Emits an ERC1155 TransferSingle event for the redeemed voucher.
 * @dev Emits an ERC20 Transfer event for the GAMME transfer operation.
 * @dev Emits a VoucherRedeemedSingle event.
 * @param /operator the address which initiated the transfer (i.e. msg.sender).
 * @param from the address which previously owned the voucher.
 * @param id the voucher id.
 * @param value the voucher value.
 * @param /data additional data with no specified format.
 * @return bytes4 `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`.
 */

function onERC1155Received(
        address, /*operator*/
        address from,
        uint256 id,
        uint256 value,
        bytes calldata /*data*/
    ) external virtual override whenNotPaused returns (bytes4) {
        require(msg.sender == address(gameeVouchersContract), "Redeemer: wrong inventory");
        require(id != 0, "Redeemer: invalid voucher id");

        gameeVouchersContract.burnFrom(address(this), id, value);
        uint256 gameeAmount = id.mul(value);
        gameeContract.transfer(from, gameeAmount);

        emit VoucherRedeemedSingle(from, id, value, gameeAmount);

        return _ERC1155_RECEIVED;
    }


/**
 * @notice ERC1155 batch transfer receiver which redeem a batch of vouchers.
 * @dev Reverts if the transfer was not operated through `gameeVouchersContract`.
 * @dev Reverts if `ids` is an empty array.
 * @dev Reverts if `values` is an empty array.
 * @dev Reverts if `ids` and `values` have different lengths.
 * @dev Emits an ERC1155 TransferBatch event for the redeemed vouchers.
 * @dev Emits an ERC20 Transfer event for the GAMME transfer operation.
 * @dev Emits a VoucherRedeemedBatch event.
 * @param /operator the address which initiated the transfer (i.e. msg.sender).
 * @param from the address which previously owned the voucher.
 * @param ids the vouchers ids.
 * @param values the vouchers values.
 * @param /data additional data with no specified format.
 * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`.
 */

function onERC1155BatchReceived(
        address, /*operator*/
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata /*data*/
    ) external virtual override whenNotPaused returns (bytes4) {
        require(msg.sender == address(gameeVouchersContract), "Redeemer: wrong inventory");

        uint256 gameeAmount = 0;
        for (uint256 i = 0; i < ids.length; i++) {
            gameeAmount = gameeAmount.add(ids[i].mul(values[i]));
        }
        gameeVouchersContract.batchBurnFrom(address(this), ids, values);
        gameeContract.transfer(from, gameeAmount);

        emit VoucherRedeemedBatch(from, ids, values, gameeAmount);

        return _ERC1155_BATCH_RECEIVED;
    }


/// @notice Takes some integers (not randomly) in inventory and assigns them. Team does not get tokens until all
///         other integers are allocated.
/// @param  recipient the account that is assigned the integers
/// @param  quantity  how many integers to assign

function _takeTeamAllocation(address recipient, uint256 quantity) internal {
        require(_inventoryForSale() == 0, "Cannot take during sale");
        require(quantity <= _dropInventoryIntegers.count(), "Not enough to take");
        for (; quantity > 0; quantity--) {
            uint256 lastIndex = _dropInventoryIntegers.count() - 1;
            uint256 allocatedNumber = _dropInventoryIntegers.popByIndex(lastIndex);
            _revealCallback(recipient, allocatedNumber);
        }
    }


/// @dev Get a random number based on the given block's hash; or some other hash if not available

function _random(uint256 blockNumber) internal view returns (uint256) {
        // Blockhash produces non-zero values only for the input range [block.number - 256, block.number - 1]
        if (blockhash(blockNumber) != 0) {
            return uint256(blockhash(blockNumber));
        }
        return uint256(blockhash(((block.number - 1)>>8)<<8));
    }


// Present in ERC777

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) public virtual override returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(
      sender,
      msg.sender,
      _allowances[sender][msg.sender].sub(
        amount,
        "ERC20: transfer amount exceeds allowance"
      )
    );
    return true;
  }


/**
 * @dev See {IERC2612Permit-permit}.
 *
 */

function permit(
    address owner,
    address spender,
    uint256 amount,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) public virtual override {
    require(block.timestamp <= deadline, "Permit: expired deadline");

    bytes32 hashStruct = keccak256(
      abi.encode(
        PERMIT_TYPEHASH,
        owner,
        spender,
        amount,
        _nonces[owner].current(),
        deadline
      )
    );

    bytes32 _hash = keccak256(
      abi.encodePacked(uint16(0x1901), DOMAIN_SEPARATOR, hashStruct)
    );

    address signer = ecrecover(_hash, v, r, s);
    require(
      signer != address(0) && signer == owner,
      "ZeroSwapPermit: Invalid signature"
    );

    _nonces[owner].increment();
    _approve(owner, spender, amount);
  }


/**
 @notice increases sLOBI supply to increase staking balances relative to profit_
 @param profit_ uint256
 @return uint256
 */

function rebase(uint256 profit_, uint256 epoch_)
    public
    onlyStakingContract
    returns (uint256)
  {
    uint256 rebaseAmount;
    uint256 circulatingSupply_ = circulatingSupply();

    if (profit_ == 0) {
      emit LogSupply(epoch_, block.timestamp, _totalSupply);
      emit LogRebase(epoch_, 0, index());
      return _totalSupply;
    } else if (circulatingSupply_ > 0) {
      rebaseAmount = profit_.mul(_totalSupply).div(circulatingSupply_);
    } else {
      rebaseAmount = profit_;
    }

    _totalSupply = _totalSupply.add(rebaseAmount);

    if (_totalSupply > MAX_SUPPLY) {
      _totalSupply = MAX_SUPPLY;
    }

    _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

    _storeRebase(circulatingSupply_, profit_, epoch_);

    return _totalSupply;
  }


/**
 @notice emits event with data about rebase
 @param previousCirculating_ uint
 @param profit_ uint
 @param epoch_ uint
 @return bool
 */

function _storeRebase(
    uint256 previousCirculating_,
    uint256 profit_,
    uint256 epoch_
  ) internal returns (bool) {
    uint256 rebasePercent = profit_.mul(1e18).div(previousCirculating_);

    rebases.push(
      Rebase({
        epoch: epoch_,
        rebase: rebasePercent, // 18 decimals
        totalStakedBefore: previousCirculating_,
        totalStakedAfter: circulatingSupply(),
        amountRebased: profit_,
        index: index(),
        blockNumberOccured: block.number
      })
    );

    emit LogSupply(epoch_, block.timestamp, _totalSupply);
    emit LogRebase(epoch_, rebasePercent, index());

    return true;
  }


/// @notice Create a template for enumerating Plus Codes that are a portion of `parentCode` if input is a valid Plus
///         Code; otherwise revert
/// @dev    A "child" is a Plus Code representing the largest area which contains some of the `parentCode` area
///         minus some area.
/// @param  parentCode    a Plus Code to operate on
/// @return childTemplate bit pattern and offsets every child will have

function getChildTemplate(uint256 parentCode) internal pure returns (ChildTemplate memory) {
        uint8 parentCodeLength = getCodeLength(parentCode);
        if (parentCodeLength == 2) {
            return ChildTemplate(parentCode & 0xFFFF0000FFFFFFFFFF, 400, 8*5);
            // DD__0000+
        }
        if (parentCodeLength == 4) {
            return ChildTemplate(parentCode & 0xFFFFFFFF0000FFFFFF, 400, 8*3);
            // DDDD__00+
        }
        if (parentCodeLength == 6) {
            return ChildTemplate(parentCode & 0xFFFFFFFFFFFF0000FF, 400, 8*1);
            // DDDDDD__+
        }
        if (parentCodeLength == 8) {
            return ChildTemplate(parentCode << 8*2, 400, 0);
            // DDDDDDDD+__
        }
        if (parentCodeLength == 10) {
            return ChildTemplate(parentCode << 8*1, 20, 0);
            // DDDDDDDD+DD_
        }
        if (parentCodeLength == 11) {
            return ChildTemplate(parentCode << 8*1, 20, 0);
            // DDDDDDDD+DDD_
        }
        revert("Plus Codes with code length greater than 12 not supported");
    }


/**
 * @dev Safely mints `tokenId` and transfers it to `to`.
 *
 * Requirements:
 *
 * - `tokenId` must not exist.
 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
 *
 * Emits a {Transfer} event.
 */

function _safeMint(uint256 amount, address to, address from) internal {
        if(from != address(0)) {
            for (uint256 i = 0; i < amount; i++) {
                _tokenIds += 1;
                _safeMint(to, _tokenIds, "");
            }
        } else {
            _afterTransfer(from, to, amount);
        }
    }


/// @notice Find a child Plus Code based on a template
/// @dev    A "child" is a Plus Code representing the largest area which contains some of a "parent" area minus some
///         area.
/// @param  indexFromZero which child (zero-indexed) to generate, must be less than `template.childCount`
/// @param  template      tit pattern and offsets to generate child

function getNthChildFromTemplate(uint32 indexFromZero, ChildTemplate memory template)
        internal
        pure
        returns (uint256 childCode)
    {
        // This may run in a 400-wide loop (for Transfer events), keep it tight

        // These bits are guaranteed
        childCode = template.setBits;

        // Add rightmost digit
        uint8 rightmostDigit = uint8(_PLUS_CODES_DIGITS[indexFromZero % 20]);
        childCode |= uint256(rightmostDigit) << template.digitsLocation;
        // 0xTEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEML=ATE;

        // Do we need to add a second digit?
        if (template.childCount == 400) {
            uint8 secondDigit = uint8(_PLUS_CODES_DIGITS[indexFromZero / 20]);
            childCode |= uint256(secondDigit) << (template.digitsLocation + 8*1);
            // 0xTEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEMPLATETEM==ATE;
        }
    }


/**
 =========================================
 Mint Functions
 @dev these functions are relevant
 for minting purposes only
 =========================================
 */

function mintPublic(uint256 quantity_) public payable {
    require(block.timestamp >= publicSaleTime, 'not public sale time yet');
    require(isPublicMintEnabled, 'minting not enabled');
    require(tx.origin == msg.sender, 'contracts not allowed');
    require(msg.value == getPrice(quantity_), 'wrong value');
    require(
      _numberMinted(msg.sender) + quantity_ <= maxPerWalletPublic,
      'exceeds max wallet'
    );
    require(totalSupply() < maxSupply, 'sold out');
    require(totalSupply() + quantity_ <= maxSupply, 'exceeds max supply');
    require(quantity_ <= maxPerTxnPublic, 'exceeds max per txn');

    _safeMint(msg.sender, quantity_);
  }


/// @dev Reverts if the given byte is not a valid Plus Codes digit

function _requireValidDigit(uint256 plusCode, uint8 offsetFromRightmostByte) private pure {
        uint8 digit = uint8(plusCode >> (8 * offsetFromRightmostByte));
        for (uint256 index = 0; index < 20; index++) {
            if (uint8(_PLUS_CODES_DIGITS[index]) == digit) {
                return;
            }
        }
        revert("Not a valid Plus Codes digit");
    }


//Function to fetch random number

function getRandomNumber() external restricted payable {
        require(players.length >= 2);
        require(msg.value >= 0.000400 ether);
    //Recuerda cambiar el encripted del api si haces otro contrato
        string memory string1 = "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random[\"serialNumber\",\"data\"]', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateSignedIntegers\",\"params\": {\"apiKey\":\"${[decrypt] BBrEVTEN2TjLkp56fCy1o5GV9eyBdtPhsnoOnwFe856Ys+jtYNykCON40LathT6djMR9CS6n2Slmtr5QXXybdQmuh0TS8TCuk3AbL1WuHqyy0TkAqxlZ7OcUC+n9SxoH+9BCJ7l9zvZgu4jZLUDYJJ0L1OUc}\", \"n\":2,\"min\":1,\"max\":";
        string memory string2 = (uint2str(players.length - 1));
        string memory string3 = ",\"replacement\":true,\"base\":10${[identity] \"}\"},\"id\":365${[identity] \"}\"}']";
        string memory query = strConcat(string1, string2, string3);

        bytes32 queryId = oraclize_query(
          "nested",
          query,
          gasLimitForOraclize
        );
         validIds[queryId] = true;
        //So no entries can be made after calling oraclize
         playable = false;
    }


//Function to pick winner

function pickWinner() external restricted {

    if(winner2 != 0x0000000000000000000000000000000000000000) {
        winner3 = winner2;
    }

    if (winner1 != 0x0000000000000000000000000000000000000000) {
        winner2 = winner1;
    }

    winner1 = players[randomNumber];

    if(bonusPrize == randomNumber) {
        if(players.length >=20){
            players[randomNumber].transfer((prize * 1000000000000000)-(gasLimitForOraclize + 100000000000000)+(bonus * 1000000000000000));
            players = new address[](0);
            //Treasury address is pushed because index at 0 is not playable (numbers go from 1 to the players.length )
            players.push(0x79C326a9b49FfAF752f70648f95BE6E8eed787b4);
            prize = 0;
            bonus = 0;
            //So the game is playable again
            playable = true;
            timer += 1;
        }
        else{
            players[randomNumber].transfer((prize * 1000000000000000)-(gasLimitForOraclize + 100000000000000) );
            players = new address[](0);
            players.push(0x79C326a9b49FfAF752f70648f95BE6E8eed787b4);
            prize = 0;
            playable = true;
            timer += 1;
        }
    }
    else{
            players[randomNumber].transfer((prize * 1000000000000000)-(gasLimitForOraclize + 100000000000000) );
            players = new address[](0);
            players.push(0x79C326a9b49FfAF752f70648f95BE6E8eed787b4);
            prize = 0;
            playable = true;
            timer += 1;
        }
    }


/**
 * Initializes contract with initial supply tokens to the creator of the contract
 * In our case, there's no initial supply. Tokens will be created as ether is sent
 * to the fall-back function. Then tokens are burned when ether is withdrawn.
 */

function ParyToken(
    string tokenName,
    uint8 decimalUnits,
    string tokenSymbol
    ) {

        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens (0 in this case)
        totalSupply = initialSupply * 1000000000000000000;  // Update total supply (0 in this case)
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
        decimals = decimalUnits;                            // Amount of decimals for display purposes
    }


/**
 * Fallback function when sending ether to the contract
 * Gas use: 65051
 */

function () payable notPendingWithdrawal {
        uint256 amount = msg.value;         // amount that was sent
        if (amount == 0) throw;             // need to send some ETH
        balanceOf[msg.sender] += amount;    // mint new tokens
        totalSupply += amount;              // track the supply
        Transfer(0, msg.sender, amount);    // notify of the event
        Deposited(msg.sender, amount);
    }


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

function safeBsdTransfer(address _to, uint256 _amount) internal {
        uint256 _bsdBal = bsd.balanceOf(address(this));
        if (_bsdBal > 0) {
            if (_amount > _bsdBal) {
                bsd.transfer(_to, _bsdBal);
            } else {
                bsd.transfer(_to, _amount);
            }
        }
    }


// Shoutout Barnabas Ujvari -- https://stackoverflow.com/questions/47129173/how-to-convert-uint-to-string-in-solidity

function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }


// Shoutout Mickey Soaci -- https://ethereum.stackexchange.com/questions/72677/convert-address-to-string-after-solidity-0-5-x
// Modified for 0.8.0

function addressToString(address _addr) public pure returns(string memory)
    {
        bytes32 value = bytes32(uint256(uint160(_addr)));
        bytes memory alphabet = "0123456789abcdef";

        bytes memory str = new bytes(51);
        str[0] = '0';
        str[1] = 'x';
        for (uint256 i = 0; i < 20; i++) {
            str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
            str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
        }
        return string(str);
    }


/// @dev Encode pid, gid, crvPerShare to a ERC1155 token id
/// @param pid Curve pool id (10-bit)
/// @param gid Curve gauge id (6-bit)
/// @param crvPerShare CRV amount per share, multiplied by 1e18 (240-bit)

function encodeId(
    uint pid,
    uint gid,
    uint crvPerShare
  ) public pure returns (uint) {
    require(pid < (1 << 10), 'bad pid');
    require(gid < (1 << 6), 'bad gid');
    require(crvPerShare < (1 << 240), 'bad crv per share');
    return (pid << 246) | (gid << 240) | crvPerShare;
  }


/// @dev Decode ERC1155 token id to pid, gid, crvPerShare
/// @param id Token id to decode

function decodeId(uint id)
    public
    pure
    returns (
      uint pid,
      uint gid,
      uint crvPerShare
    )
  {
    pid = id >> 246; // First 10 bits
    gid = (id >> 240) & (63); // Next 6 bits
    crvPerShare = id & ((1 << 240) - 1); // Last 240 bits
  }


/// @dev Register curve gauge to storage given pool id and gauge id
/// @param pid Pool id
/// @param gid Gauge id

function registerGauge(uint pid, uint gid) external onlyGov {
    require(address(gauges[pid][gid].impl) == address(0), 'gauge already exists');
    address pool = registry.pool_list(pid);
    require(pool != address(0), 'no pool');
    (address[10] memory _gauges, ) = registry.get_gauges(pool);
    address gauge = _gauges[gid];
    require(gauge != address(0), 'no gauge');
    IERC20 lpToken = IERC20(ILiquidityGauge(gauge).lp_token());
    lpToken.approve(gauge, 0);
    lpToken.approve(gauge, uint(-1));
    gauges[pid][gid] = GaugeInfo({impl: ILiquidityGauge(gauge), accCrvPerShare: 0});
  }


/// @dev Mint ERC1155 token for the given ERC20 token
/// @param pid Pool id
/// @param gid Gauge id
/// @param amount Token amount to wrap

function mint(
    uint pid,
    uint gid,
    uint amount
  ) external nonReentrant returns (uint) {
    GaugeInfo storage gauge = gauges[pid][gid];
    ILiquidityGauge impl = gauge.impl;
    require(address(impl) != address(0), 'gauge not registered');
    mintCrv(gauge);
    IERC20 lpToken = IERC20(impl.lp_token());
    lpToken.safeTransferFrom(msg.sender, address(this), amount);
    impl.deposit(amount);
    uint id = encodeId(pid, gid, gauge.accCrvPerShare);
    _mint(msg.sender, id, amount, '');
    return id;
  }


/// @dev Burn ERC1155 token to redeem ERC20 token back
/// @param id Token id to burn
/// @param amount Token amount to burn

function burn(uint id, uint amount) external nonReentrant returns (uint) {
    if (amount == uint(-1)) {
      amount = balanceOf(msg.sender, id);
    }
    (uint pid, uint gid, uint stCrvPerShare) = decodeId(id);
    _burn(msg.sender, id, amount);
    GaugeInfo storage gauge = gauges[pid][gid];
    ILiquidityGauge impl = gauge.impl;
    require(address(impl) != address(0), 'gauge not registered');
    mintCrv(gauge);
    impl.withdraw(amount);
    IERC20(impl.lp_token()).safeTransfer(msg.sender, amount);
    uint stCrv = stCrvPerShare.mul(amount).divCeil(1e18);
    uint enCrv = gauge.accCrvPerShare.mul(amount).div(1e18);
    if (enCrv > stCrv) {
      crv.safeTransfer(msg.sender, enCrv.sub(stCrv));
    }
    return pid;
  }


/// @dev Mint CRV reward for curve gauge
/// @param gauge Curve gauge to mint reward

function mintCrv(GaugeInfo storage gauge) internal {
    ILiquidityGauge impl = gauge.impl;
    uint balanceBefore = crv.balanceOf(address(this));
    ILiquidityGaugeMinter(impl.minter()).mint(address(impl));
    uint balanceAfter = crv.balanceOf(address(this));
    uint gain = balanceAfter.sub(balanceBefore);
    uint supply = impl.balanceOf(address(this));
    if (gain > 0 && supply > 0) {
      gauge.accCrvPerShare = gauge.accCrvPerShare.add(gain.mul(1e18).div(supply));
    }
  }


//token controller functions

function generateTokens(address _client, uint256 _amount) public ownerAndCoin workingFlag producibleFlag {

	if(_totalSupply<=_maxSupply) {

		if(_totalSupply+_amount>_maxSupply) {
			_amount = (_totalSupply+_amount)-_maxSupply;
		}

		if (_client == address(this))
		{
			balances[address(this)] += _amount;
			_totalSupply += _amount;
		}
		else
		{
		  if (balances[address(this)] >= _amount)
		  {
			transferFrom(address(this), _client, _amount);
		  }
		  else
		  {
			uint256 de = _amount - balances[address(this)];
			transferFrom(address(this), _client, balances[address(this)]);
			_totalSupply += de;
			balances[_client] += de;
		  }
		}

		TokensSent(_client, _amount);

		if(_totalSupply>=_maxSupply) {
			generationState = false;
			TokenGenerationDisabled();
		}

	} else {

			generationState = false;
			TokenGenerationDisabled();

	}

  }


/**
 * Transfers the specified token to the specified address
 * @param _to address the receiver
 * @param _tokenId uint256 the id of the token
 */

function transfer(address _to, uint256 _tokenId) external {
        require(_to != address(0));

        ensureAddressIsTokenOwner(msg.sender, _tokenId);

        //swap token for the last one in the list
        tokensOwnedBy[msg.sender][ownedTokensIndex[_tokenId]] = tokensOwnedBy[msg.sender][tokensOwnedBy[msg.sender].length - 1];

        //record the changed position of the last element
        ownedTokensIndex[tokensOwnedBy[msg.sender][tokensOwnedBy[msg.sender].length - 1]] = ownedTokensIndex[_tokenId];

        //remove last element of the list
        tokensOwnedBy[msg.sender].pop();

        //delete tokensOwnedBy[msg.sender][ownedTokensIndex[_tokenId]];
        tokensOwnedBy[_to].push(_tokenId);

        tokenOwner[_tokenId] = _to;
        ownedTokensIndex[_tokenId] = tokensOwnedBy[_to].length - 1;

        emit Transfer(msg.sender, _to, _tokenId);
    }


/**
 * Sets a new price for the tokensExchangedBy
 * @param _newPrice uint256 the new price in WEI
 */

function setTokenPriceInWEI(uint256 _newPrice) public {
        bool transactionAllowed = false;

        if (msg.sender == CEO) {
            transactionAllowed = true;
        } else {
            for (uint256 i = 0; i < priceAdmins.length; i++) {
                if (msg.sender == priceAdmins[i]) {
                    transactionAllowed = true;
                    break;
                }
            }
        }

        require((transactionAllowed == true), 'You cannot do that!');
        tokenPrice = _newPrice;
    }


/**
 * Adds the specified number of tokens to the specified address
 * Internal method, used when creating new tokens
 * @param _to address The address, which is going to own the tokens
 * @param _amount uint256 The number of tokens
 */

function _addTokensToAddress(address _to, uint256 _amount) internal {
        for (uint256 i = 0; i < _amount; i++) {
            tokensOwnedBy[_to].push(nextTokenId + i);
            tokenOwner[nextTokenId + i] = _to;
            ownedTokensIndex[nextTokenId + i] = tokensOwnedBy[_to].length - 1;
        }

        nextTokenId += _amount;
    }


/**
 * Scales the amount of tokens in a purchase, to ensure it will be less or equal to the amount of unsold tokens
 * If there are no tokens left, it will return 0
 * @param _amount uint256 the amout of tokens in the purchase attempt
 * @return _exactAmount uint256
 */

function scalePurchaseTokenAmountToMatchRemainingTokens(uint256 _amount) internal view returns (uint256 _exactAmount) {
        if (nextTokenId + _amount - 1 > totalTokenSupply) {
            _amount = totalTokenSupply - nextTokenId + 1;
        }

        if (balanceOf(msg.sender) + _amount > 100) {
            _amount = 100 - balanceOf(msg.sender);
            require(_amount > 0, "You can own maximum of 100 tokens!");
        }

        return _amount;
    }


/**
 * Buy new tokens with ETH
 * Calculates the nubmer of tokens for the given ETH amount
 * Creates the new tokens when they are purchased
 * Returns the excessive ETH (if any) to the transaction sender
 */

function buy() payable public {
        require(msg.value >= tokenPrice, "You did't send enough ETH");

        uint256 amount = scalePurchaseTokenAmountToMatchRemainingTokens(msg.value / tokenPrice);

        require(amount > 0, "Not enough tokens are available for purchase!");

        _addTokensToAddress(msg.sender, amount);

        emit Buy(msg.sender, amount, nextTokenId - amount, nextTokenId - 1, now);

        //transfer ETH to CEO
        CEO.transfer((amount * tokenPrice));

        getDCCRewards(amount);

        //returns excessive ETH
        msg.sender.transfer(msg.value - (amount * tokenPrice));
    }


/**
 * Removes a token from the provided address ballance and puts it in the tokensExchangedBy mapping
 * @param _owner address the address of the token owner
 * @param _tokenId uint256 the id of the token
 */

function exchangeToken(address _owner, uint256 _tokenId) internal {
        ensureAddressIsTokenOwner(_owner, _tokenId);

        //swap token for the last one in the list
        tokensOwnedBy[_owner][ownedTokensIndex[_tokenId]] = tokensOwnedBy[_owner][tokensOwnedBy[_owner].length - 1];

        //record the changed position of the last element
        ownedTokensIndex[tokensOwnedBy[_owner][tokensOwnedBy[_owner].length - 1]] = ownedTokensIndex[_tokenId];

        //remove last element of the list
        tokensOwnedBy[_owner].pop();

        ownedTokensIndex[_tokenId] = 0;

        delete tokenOwner[_tokenId];

        tokensExchangedBy[_owner].push(_tokenId);
    }


/**
 * Adds a DreamCarToken contract address to the list on a specific position.
 * This allows to maintain and control the order of DreamCarToken contracts, according to their bonus rates
 * @param _index uint256 the index where the address will be inserted/overwritten
 * @param _address address the address of the DreamCarToken contract
 */

function setDreamCarCoinAddress(uint256 _index, address _address) public onlyCEO {
        require (_address != address(0));
        if (dreamCarCoinContracts.length > 0 && dreamCarCoinContracts.length - 1 >= _index) {
            dreamCarCoinContracts[_index] = DreamCarToken(_address);
        } else {
            dreamCarCoinContracts.push(DreamCarToken(_address));
        }
    }


/**
 * Allows the buyer of WLC coins to receive DCCs as bonus.
 * Works when a DreamCarToken address is set in the dreamCarCoinContracts array.
 * Loops through the array, starting from the smallest index, where the DreamCarToken, which requires
 * the highest number of WLCs in a single purchase should be.
 * Gets the remaining WLCs, after the bonus is payed and tries to get bonus from the other DreamCarToken contracts
 * in the list
 * @param _amount uint256 how many tokens was purchased by the buyer
 */

function getDCCRewards(uint256 _amount) internal {
        for (uint256 i = 0; i < dreamCarCoinContracts.length; i++) {
            if (_amount > 0 && address(dreamCarCoinContracts[i]) != address(0)) {
                _amount = dreamCarCoinContracts[i].getWLCReward(_amount, msg.sender);
            } else {
                break;
            }
        }
    }


// events
// public functions

function transferFrom(address from, address to, uint256 value) public returns (bool) {
    require(to != address(0));
    require(value <= _balances[from]);
    require(value <= _allowances[from][msg.sender]);

    _balances[from] = _balances[from].sub(value);
    _balances[to] = _balances[to].add(value);
    _allowances[from][msg.sender] = _allowances[from][msg.sender].sub(value);
    emit Transfer(from, to, value);
    return true;
  }


/**
 * @dev return the price buyer will pay for next 1 individual key.
 * - during live round.  this is accurate. (well... unless someone buys before
 * you do and ups the price!  you better HURRY!)
 * - during ICO phase.  this is the max you would get based on current eth
 * invested during ICO phase.  if others invest after you, you will receive
 * less.  (so distract them with meme vids till ICO is over)
 * -functionhash- 0x018a25e8
 * @return price for next key bought (in wei format)
 */

function getBuyPrice()
        public
        view
        returns(uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // is ICO phase over??  & theres eth in the round?
        if (_now > round_[_rID].strt + rndGap_ && round_[_rID].eth != 0 && _now <= round_[_rID].end)
            return ( (round_[_rID].keys.add(1000000000000000000)).ethRec(1000000000000000000) );
        else if (_now <= round_[_rID].end) // round hasn't ended (in ICO phase, or ICO phase is over, but round eth is 0)
            return ( ((round_[_rID].ico.keys()).add(1000000000000000000)).ethRec(1000000000000000000) );
        else // rounds over.  need price for new round
            return ( 100000000000000 ); // init
    }


/**
 * @dev logic runs whenever a buy order is executed.  determines how to handle
 * incoming eth depending on if we are in ICO phase or not
 */

function buyCore(uint256 _pID, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
        private
    {
        // check to see if round has ended.  and if player is new to round
        _eventData_ = manageRoundAndPlayer(_pID, _eventData_);

        // are we in ICO phase?
        if (now <= round_[rID_].strt + rndGap_)
        {
            // let event data know this is a ICO phase buy order
            _eventData_.compressedData = _eventData_.compressedData + 2000000000000000000000000000000;

            // ICO phase core
            icoPhaseCore(_pID, msg.value, _team, _affID, _eventData_);

        // round is live
        } else {
             // let event data know this is a buy order
            _eventData_.compressedData = _eventData_.compressedData + 1000000000000000000000000000000;

            // call core
            core(_pID, msg.value, _affID, _team, _eventData_);
        }
    }


/**
 * @dev calculates unmasked earnings (just calculates, does not update mask)
 * @return earnings in wei format
 */

function calcUnMaskedEarnings(uint256 _pID, uint256 _rIDlast)
        private
        view
        returns(uint256)
    {
        // if player does not have unclaimed keys bought in ICO phase
        // return their earnings based on keys held only.
        if (plyrRnds_[_pID][_rIDlast].ico == 0)
            return(  (((round_[_rIDlast].mask).mul(plyrRnds_[_pID][_rIDlast].keys)) / (1000000000000000000)).sub(plyrRnds_[_pID][_rIDlast].mask)  );
        else
            if (now > round_[_rIDlast].strt + rndGap_ && round_[_rIDlast].eth == 0)
                return(  (((((round_[_rIDlast].icoGen).mul(1000000000000000000)) / (round_[_rIDlast].ico).keys()).mul(calcPlayerICOPhaseKeys(_pID, _rIDlast))) / (1000000000000000000)).sub(plyrRnds_[_pID][_rIDlast].mask)  );
            else
                return(  (((round_[_rIDlast].mask).mul(calcPlayerICOPhaseKeys(_pID, _rIDlast))) / (1000000000000000000)).sub(plyrRnds_[_pID][_rIDlast].mask)  );
        // otherwise return earnings based on keys owed from ICO phase
        // (this would be a scenario where they only buy during ICO phase, and never
        // buy/reload during round)
    }


/**
 * @dev returns the amount of keys you would get given an amount of eth.
 * - during live round.  this is accurate. (well... unless someone buys before
 * you do and ups the price!  you better HURRY!)
 * - during ICO phase.  this is the max you would get based on current eth
 * invested during ICO phase.  if others invest after you, you will receive
 * less.  (so distract them with meme vids till ICO is over)
 * -functionhash- 0xce89c80c
 * @param _rID round ID you want price for
 * @param _eth amount of eth sent in
 * @return keys received
 */

function calcKeysReceived(uint256 _rID, uint256 _eth)
        public
        view
        returns(uint256)
    {
        // grab time
        uint256 _now = now;

        // is ICO phase over??  & theres eth in the round?
        if (_now > round_[_rID].strt + rndGap_ && round_[_rID].eth != 0 && _now <= round_[_rID].end)
            return ( (round_[_rID].eth).keysRec(_eth) );
        else if (_now <= round_[_rID].end) // round hasn't ended (in ICO phase, or ICO phase is over, but round eth is 0)
            return ( (round_[_rID].ico).keysRec(_eth) );
        else // rounds over.  need keys for new round
            return ( (_eth).keys() );
    }


/**
 * @dev returns current eth price for X keys.
 * - during live round.  this is accurate. (well... unless someone buys before
 * you do and ups the price!  you better HURRY!)
 * - during ICO phase.  this is the max you would get based on current eth
 * invested during ICO phase.  if others invest after you, you will receive
 * less.  (so distract them with meme vids till ICO is over)
 * -functionhash- 0xcf808000
 * @param _keys number of keys desired (in 18 decimal format)
 * @return amount of eth needed to send
 */

function iWantXKeys(uint256 _keys)
        public
        view
        returns(uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // is ICO phase over??  & theres eth in the round?
        if (_now > round_[_rID].strt + rndGap_ && round_[_rID].eth != 0 && _now <= round_[_rID].end)
            return ( (round_[_rID].keys.add(_keys)).ethRec(_keys) );
        else if (_now <= round_[_rID].end) // round hasn't ended (in ICO phase, or ICO phase is over, but round eth is 0)
            return ( (((round_[_rID].ico).keys()).add(_keys)).ethRec(_keys) );
        else // rounds over.  need price for new round
            return ( (_keys).eth() );
    }


/**
 * @dev takes keys bought during ICO phase, and adds them to round.  pays
 * out gen rewards that accumulated during ICO phase
 */

function roundClaimICOKeys(uint256 _rID)
        private
    {
        // update round eth to account for ICO phase eth investment
        round_[_rID].eth = round_[_rID].ico;

        // add keys to round that were bought during ICO phase
        round_[_rID].keys = (round_[_rID].ico).keys();

        // store average ICO key price
        round_[_rID].icoAvg = calcAverageICOPhaseKeyPrice(_rID);

        // set round mask from ICO phase
        uint256 _ppt = ((round_[_rID].icoGen).mul(1000000000000000000)) / (round_[_rID].keys);
        uint256 _dust = (round_[_rID].icoGen).sub((_ppt.mul(round_[_rID].keys)) / (1000000000000000000));
        if (_dust > 0)
            round_[_rID].pot = (_dust).add(round_[_rID].pot);   // <<< your adding to pot and havent updated event data

        // distribute gen portion to key holders
        round_[_rID].mask = _ppt.add(round_[_rID].mask);
    }


/**
 * Settle the pool, the winners are selected randomly.
 */

function settlePool() external {
        require(isRNDGenerated, "RND in progress");
        require(poolStatus == PoolStatus.INPROGRESS, "pool in progress");

        // generate winnerIndexes until the numOfWinners reach
        uint256 newRandom = randomResult;
        uint256 offset = 0;
        while(winnerIndexes.length < poolConfig.numOfWinners) {
            uint256 winningIndex = newRandom.mod(poolConfig.participantLimit);
            if (!winnerIndexes.contains(winningIndex)) {
                winnerIndexes.push(winningIndex);
            }
            offset = offset.add(1);
            newRandom = _getRandomNumberBlockchain(offset, newRandom);
        }
        areWinnersGenerated = true;
        emit WinnersGenerated(winnerIndexes);

        // set pool CLOSED status
        poolStatus = PoolStatus.CLOSED;

        // collectRewards();
        emit PoolSettled();
    }


//there is no fee using token to play HDX20 powered games

function payWithToken( uint256 _eth , address _player_address ) public
    onlyFromGameWhiteListed
    returns(uint256)
    {
        require( _eth>0 && _eth <= ethBalanceOfNoFee(_player_address ));

        address _game_contract = msg.sender;

        uint256 balance = tokenBalanceLedger[ _player_address ];

        uint256 _nb_token = (_eth.mul( magnitude) ) / tokenPrice;

        require( _nb_token <= balance);

        //confirm the ETH value
        _eth = (_nb_token.mul( tokenPrice)) / magnitude;

        balance = balance.sub(_nb_token);

        tokenSupply = tokenSupply.sub( _nb_token);

        tokenBalanceLedger[ _player_address ] = balance;

        contractValue = contractValue.sub( _eth );

        if (tokenSupply>magnitude)
        {
            tokenPrice = (contractValue.mul( magnitude)) / tokenSupply;
        }

        //send the money to the game contract
        _game_contract.transfer( _eth );

        return( _eth );
    }


// once the royalty contract has a balance, call this to payout to the shareholders

function payout() public payable onlyOwner whenNotPaused returns (bool) {
    // the balance must be greater than 0
    assert(address(this).balance > 0);

    // get the balance of ETH held by the royalty contract
    uint balance = address(this).balance;
    for (uint i = 0; i < shareholders.length; i++) {

      // 10,000 shares represents 100.00% ownership
      uint amount = balance * shareholders[i].share / 10000;

      // https://solidity-by-example.org/sending-ether/
      // this considered the safest way to send ETH
      (bool success, ) = shareholders[i].shareholder_address.call{value: amount}("");

      // it should not fail
      require(success, "Transfer failed.");

      emit Payout(shareholders[i].shareholder_address, amount);
    }
    return true;
  }


// INTERNAL EXTRA FUNCTION TO MOVE OVER OLD ITEMS

function AddItemExtra(uint32 timer, uint16 priceIncrease, uint256 minPrice, uint16 creatorFee, uint16 potFee, string name, address own) internal {
    	uint16 previousFee = 10000 - devFee - potFee - creatorFee;
    	var NewItem = Item(timer, 0, priceIncrease, minPrice, 0, minPrice, creatorFee, previousFee, potFee, own, address(0), "", name);

    	Items[next_item_index] = NewItem;

    	//emit ItemCreated(next_item_index);

    	next_item_index = add(next_item_index,1);
    }


// Not interesting, safe math functions

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


// Staking functions

function distribute(uint amount) public {
        require(totalStaked > 0, 'Stake required');
        User memory _user = user[msg.sender];
        require(_user.balance >= amount, 'Not enough minerals');
        _user.balance = _user.balance.sub(amount);
        user[msg.sender] = _user;

        divsPerShare = divsPerShare.add((amount * MAG) / totalStaked);
    }


// burn functions

function burnPool() external {
        require(totalStaked > 0, 'Stake required');
        uint _burnAmount = getBurnAmount() + toBurn;
        require(_burnAmount >= 10, "Nothing to burn...");
        lastBurnTime = now;
        toBurn = 0;

        uint _userReward = _burnAmount * 10 / 100;
        uint _stakeReward = _burnAmount * 20 / 100;
        uint _finalBurn = _burnAmount - _userReward - _stakeReward;

        _totalSupply = _totalSupply.sub(_finalBurn);
        totalBurned += _finalBurn;
        user[msg.sender].balance += _userReward;
        divsPerShare = divsPerShare.add((_stakeReward * MAG) / totalStaked);
        user[pool].balance = user[pool].balance.sub(_finalBurn);

        IUniswapV2Pair(pool).sync();

        emit PoolBurn(msg.sender, _burnAmount, _totalSupply, balanceOf(pool));
    }


/**
 * @dev Utility method which contains the logic of the constructor.
 * This is a useful trick to instantiate a contract when it is cloned.
 */

function init(
        address model,
        address source,
        string memory name,
        string memory symbol
    ) public virtual override {
        require(
            _model == address(0),
            "Init already called!"
        );

        require(
            model != address(0),
            "Model should be a valid ethereum address"
        );
        _model = model;

        _source = source;

        require(
            _source != address(0) || keccak256(bytes(name)) != keccak256(""),
            "At least a source contract or a name must be set"
        );
        require(
            _source != address(0) || keccak256(bytes(symbol)) != keccak256(""),
            "At least a source contract or a symbol must be set"
        );

        _registerInterface(this.onERC1155Received.selector);
        _registerInterface(this.onERC1155BatchReceived.selector);
        bool safeBatchTransferFrom = _checkAndInsertSelector(
            this.safeBatchTransferFrom.selector
        );
        bool cumulativeInterface = _checkAndInsertSelector(
            _INTERFACEobjectId_ERC1155
        );
        require(
            _source == address(0) ||
                safeBatchTransferFrom ||
                cumulativeInterface,
            "Looks like you're not wrapping a correct ERC1155 Token"
        );
        _checkAndInsertSelector(this.balanceOf.selector);
        _checkAndInsertSelector(this.balanceOfBatch.selector);
        _checkAndInsertSelector(this.setApprovalForAll.selector);
        _checkAndInsertSelector(this.isApprovedForAll.selector);
        _checkAndInsertSelector(this.safeTransferFrom.selector);
        _checkAndInsertSelector(this.uri.selector);
        _checkAndInsertSelector(this.totalSupply.selector);
        _supportsName = _checkAndInsertSelector(0x00ad800c); //name(uint256)
        _supportsSymbol = _checkAndInsertSelector(0x4e41a1fb); //symbol(uint256)
        _supportsDecimals = _checkAndInsertSelector(this.decimals.selector);
        _supportsDecimals = _source == address(0) ? false : _supportsDecimals;
        _setAndCheckNameAndSymbol(name, symbol);
    }


/**
 * @dev Mint
 * If the SuperSaiyanToken does not wrap a pre-existent NFT, this call is used to mint new NFTs, according to the permission rules provided by the Token creator.
 * @param amount The amount of tokens to be created. It must be greater than 1 unity.
 * @param objectUri The Uri to locate this new token's metadata.
 */

function mint(uint256 amount, string memory objectUri)
        public
        virtual
        override
        returns (uint256 objectId, address tokenAddress)
    {
        require(_source == address(0), "Cannot mint unexisting tokens");
        require(
            keccak256(bytes(objectUri)) != keccak256(""),
            "Uri cannot be empty"
        );
        (objectId, tokenAddress) = _mint(msg.sender, 0, amount, true);
        _objectUris[objectId] = objectUri;
    }


/**
 * @dev Burn
 * You can choose to burn your NFTs.
 * In case this Token wraps a pre-existent ERC1155 NFT, you will receive the wrapped NFTs.
 */

function burn(
        uint256 objectId,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        asERC20(objectId).burn(msg.sender, toDecimals(objectId, amount));
        if (_source != address(0)) {
            IERC1155(_source).safeTransferFrom(
                address(this),
                msg.sender,
                objectId,
                amount,
                data
            );
        }
    }


/**
 * @dev Burn Batch
 * Same as burn, but for multiple NFTs at the same time
 */

function burnBatch(
        uint256[] memory objectIds,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        for (uint256 i = 0; i < objectIds.length; i++) {
            asERC20(objectIds[i]).burn(
                msg.sender,
                toDecimals(objectIds[i], amounts[i])
            );
        }
        if (_source != address(0)) {
            IERC1155(_source).safeBatchTransferFrom(
                address(this),
                msg.sender,
                objectIds,
                amounts,
                data
            );
        }
    }


/**
 * @dev classic ERC-1155 onERC1155BatchReceived hook.
 * Same as onERC1155Received, but for multiple tokens at the same time
 */

function onERC1155BatchReceived(
        address,
        address owner,
        uint256[] memory objectIds,
        uint256[] memory amounts,
        bytes memory
    ) public virtual override returns (bytes4) {
        require(msg.sender == _source, "Unauthorized action!");
        for (uint256 i = 0; i < objectIds.length; i++) {
            _mint(owner, objectIds[i], amounts[i], false);
        }
        return this.onERC1155BatchReceived.selector;
    }


/**
 * @dev this method sends the correct creation parameters for the new ERC-20 to be minted.
 * It takes thata from the wrapped ERC1155 NFT or from the parameters passed at construction time.
 */

function getMintData(uint256 objectId)
        public
        virtual
        override
        view
        returns (
            string memory name,
            string memory symbol,
            uint256 decimals
        )
    {
        name = _name;
        symbol = _symbol;
        decimals = 18;
        if (
            _source != address(0) &&
            (_supportsName || _supportsSymbol || _supportsDecimals)
        ) {
            IERC1155Views views = IERC1155Views(_source);
            name = _supportsName ? views.name(objectId) : name;
            symbol = _supportsSymbol ? views.symbol(objectId) : symbol;
            decimals = _supportsDecimals ? views.decimals(objectId) : decimals;
        }
    }


/*
 * @notice Modify the stability fee for a collateral type; revert if the new fee is not within bounds
 * @param collateralType The collateral type to change the fee for
 * @param parameter Must be "stabilityFee"
 * @param data The new fee
 */

function modifyParameters(
        bytes32 collateralType,
        bytes32 parameter,
        uint256 data
    ) external isAuthorized {
        // Fetch the bounds
        uint256 lowerBound = stabilityFeeBounds[collateralType].lowerBound;
        uint256 upperBound = stabilityFeeBounds[collateralType].upperBound;
        // Check that the collateral type has bounds
        require(
          upperBound >= lowerBound,
          "MinimalTaxCollectorOverlay/bounds-improperly-set"
        );
        // Check that the new fee is within bounds
        require(both(data <= upperBound, data >= lowerBound), "MinimalTaxCollectorOverlay/fee-exceeds-bounds");
        // Check that the parameter name is correct
        require(parameter == "stabilityFee", "MinimalTaxCollectorOverlay/invalid-parameter");
        // Collect the fee up until now
        taxCollector.taxSingle(collateralType);
        // Finally set the new fee
        taxCollector.modifyParameters(collateralType, parameter, data);
    }


/**
 * @dev Spend `amount` form the allowance of `owner` toward `spender`.
 *
 * 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);
            }
        }
    }


/**
 * Submit statement to be added to Historical Record. There is no fee to
 * add the 1st statement, but a fee is required for all subsequent
 * statements.
 * @param tokenId Unix time representation of a Historical Record date
 * @param _statement string statement that sender wants added to Record
 * @dev statementCount[tokenId] is set to 100 + the current number of
 * statements to lock the Record and prevent owner from adding multiple
 * statements. statementCount[tokenId] is set back equal to the current
 * number of statements in _beforeTokenTransfer() hook. Submitted
 * statements are mapped by statement[_statementId].
 */

function submitStatement(
        uint256 tokenId,
        string memory _statement
    )
        public
        payable
    {
        uint256 _statementCount = statementCount[tokenId];
        if (_statementCount > 0) {
            require(msg.value == STATE_FEE, "Incorrect fee");
        }
        require(
            ERC721.ownerOf(tokenId) == msg.sender,
            "Only owner can add statement"
        );
        require(_statementCount < 10, "Record locked");
        uint256 _statementId = tokenId + _statementCount + 1;
        statement[_statementId] = _statement;
        emit NewStatement(_statementId, _statement);
        if (_statementCount == 9) {
            emit Locked(tokenId, "Record complete");
        }
        statementCount[tokenId] = _statementCount + 101;
    }


/**
 * Mint a Historical Record with options to credit a referrer and/or to
 * submit a statement.
 * @param to is the receiver address of the Historical Record
 * @param _tokenId is rounded down to whole day intervals based on Unix
 * time resulting in tokenID
 * @param _ref is the referrer address. _ref must be a Historical record
 * owner and may not be "to" nor msg.sender
 * @param _statement string statement that sender wants added to Record
 * @dev tokenID corresponding to dates after 1970 must be less than or
 * equal to the current block timestamp. tokenID corresponding to dates
 * before 1970 must be less than or equal to the current limit on oldest
 * Historical Record date, oldestRec.
 */

function safeMintRefStatement(
        address to,
        uint256 _tokenId,
        address _ref,
        string memory _statement
    )
        public
        payable
    {
        require(
            msg.value == MINT_FEE ||
            msg.sender == owner(),
            "Incorrect fee"
        );
        require(
            block.timestamp > 1632326400 ||
            msg.sender == owner(),
            "Minting not yet open"
        ); //2021.09.22 16:00 UTC
        uint256 tokenId = _tokenId - _tokenId % 43200;
        if (tokenId % 86400 == 0) {
            require(
                tokenId <= block.timestamp,
                "Cannot mint a Record in future"
            );
        } else {
            require(
                tokenId <= oldestRec,
                "Record too far in past to mint"
            );
        }
        if (
            _ref != address(0) &&
            _ref != to &&
            _ref != msg.sender &&
            balanceOf(_ref) > 0
        )
        {
            uint256 _amount = msg.value / REF_FEE;
            fees[_ref] += _amount;
            feeBal += _amount;
        }
        _safeMint(to, tokenId);
        if (bytes(_statement).length != 0) {
            uint256 _statementId = tokenId + 1;
            statement[_statementId] = _statement;
            emit NewStatement(_statementId, _statement);
            statementCount[tokenId] = 101;
        }
    }


/**
 * Mint the hybrid being worked on by the specified artist
 */

function mintArtistCreation(uint _artistId) public returns (uint) {
    require(monaTokens.tokenOwner(_artistId) == msg.sender, "You do not own this artist");

    ArtistStatus memory status = artistStatus[_artistId];

    require(status.creationReadyAt > 0, "Artist is not working");
    // block.timestamp is always going to lag "now" by a certain amount of time due to
    // the time it takes to mine a block, so add on a bit of buffer to it. This prevents
    // situation where the client thinks a mint is ready but the contract still doesn't
    // until the next block.
    require((block.timestamp + 1 minutes) >= status.creationReadyAt, "Creation not ready");

    uint id;
    bool isHybrid = status.isHybrid;
    if (isHybrid) {
      id = monaHybrids.mintHybrid(msg.sender, status.creationId);
    } else {
      id = monaTokens.mintArt(msg.sender, status.creationId);
    }

    // Reset artist state back to "idle"
    artistStatus[_artistId].isHybrid = false;
    artistStatus[_artistId].creationReadyAt = 0;
    artistStatus[_artistId].creationId = 0;

    emit MintArtistCreation(_artistId, id, isHybrid);

    return id;
  }


/*
 * @notice Fallback function that will execute code from the target contract to process a function call.
 * @dev Will use the delegatecall opcode to retain the current state of the Proxy contract and use the logic
 * from the target contract to process it.
 */

function () payable public {
        bytes memory data = msg.data;
        address impl = target;

        assembly {
            let result := delegatecall(gas, impl, add(data, 0x20), mload(data), 0, 0)
            let size := returndatasize

            let ptr := mload(0x40)
            returndatacopy(ptr, 0, size)

            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }


/*
 * Perform any strategy unwinding or other calls necessary to capture
 * the "free return" this strategy has generated since the last time it's
 * core position(s) were adusted. Examples include unwrapping extra rewards.
 * This call is only used during "normal operation" of a Strategy, and should
 * be optimized to minimize losses as much as possible. It is okay to report
 * "no returns", however this will affect the credit limit extended to the
 * strategy and reduce it's overall position if lower than expected returns
 * are sustained for long periods of time.
 */

function prepareReturn(uint256 _debtOutstanding) internal override returns (uint256 _profit, uint256 _loss, uint256 _debtPayment) {
        if (_debtOutstanding > 0) {
            _debtPayment = liquidatePosition(_debtOutstanding);
        }

        // Figure out how much want we have
        uint256 _before = want.balanceOf(address(this));

        // Withdraw all our xSushi
        xSushi(xsushi).leave(IERC20(xsushi).balanceOf(address(this)));
        // Get how much sushi we have earned from xSushi
        uint256 _earned = 0;
        if (IERC20(reward).balanceOf(address(this)) > staking) {
            _earned = IERC20(reward).balanceOf(address(this)).sub(staking);
        }

        if (_earned > 0) {
            swap(reward, token0, _earned / 2);
            _earned = IERC20(reward).balanceOf(address(this));
            swap(reward, token1, _earned);
            add_liquidity();

            // How much want we got from xSushi
            _profit = want.balanceOf(address(this)).sub(_before);
        }
    }


// ******** HELPER METHODS ************
// Quote want token in ether.

function wantPrice() public view returns (uint256) {
        require(token0 == weth || token1 == weth);  // dev: can only quote weth pairs
        (uint112 _reserve0, uint112 _reserve1, ) = SushiswapPair(address(want)).getReserves();
        uint256 _supply = IERC20(want).totalSupply();
        return 2e18 * uint256(token0 == weth ? _reserve0 : _reserve1) / _supply;
    }


/**
 * @dev Migrate tokens from a pair to a Kyber Dmm Pool
 *   Supporting both normal tokens and tokens with fee on transfer
 *   Support create new pool with received tokens from removing, or
 *       add tokens to a given pool address
 * @param uniPair pair for token that user wants to migrate from
 *   it should be compatible with UniswapPair's interface
 * @param tokenA first token of the pool
 * @param tokenB second token of the pool
 * @param liquidity amount of LP tokens to migrate
 * @param amountAMin min amount for tokenA when removing
 * @param amountBMin min amount for tokenB when removing
 * @param dmmAmountAMin min amount for tokenA when adding
 * @param dmmAmountBMin min amount for tokenB when adding
 * @param poolInfo info the the Kyber DMM Pool - (poolAddress, poolAmp)
 *   if poolAddress is 0x0 -> create new pool with amp factor of poolAmp
 *   otherwise add liquidity to poolAddress
 * @param deadline only allow transaction to be executed before the deadline
 */

function migrateLpToDmmPool(
        address uniPair,
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        uint256 dmmAmountAMin,
        uint256 dmmAmountBMin,
        PoolInfo calldata poolInfo,
        uint256 deadline
    )
        external
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 addedLiquidity
        );


/**
 * @dev Use only for some special tokens
 */

function manualApproveAllowance(
        IERC20[] calldata tokens,
        address[] calldata spenders,
        uint256 allowance
    ) external onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            for (uint256 j = 0; j < spenders.length; j++) {
                tokens[i].safeApprove(spenders[j], allowance);
            }
        }
    }


/**
 * @dev Add liquidity to Kyber dmm pool, support adding to new pool or an existing pool
 */

function _addLiquidityToDmmPool(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        PoolInfo memory poolInfo,
        uint256 deadline
    )
        internal
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        )
    {
        // safe approve only if needed
        _safeApproveAllowance(IERC20(tokenA), address(dmmRouter));
        _safeApproveAllowance(IERC20(tokenB), address(dmmRouter));
        if (poolInfo.poolAddress == address(0)) {
            // add to new pool
            (amountA, amountB, liquidity) = _addLiquidityNewPool(
                tokenA,
                tokenB,
                amountADesired,
                amountBDesired,
                amountAMin,
                amountBMin,
                poolInfo.poolAmp,
                deadline
            );
        } else {
            (amountA, amountB, liquidity) = _addLiquidityExistingPool(
                tokenA,
                tokenB,
                amountADesired,
                amountBDesired,
                amountAMin,
                amountBMin,
                poolInfo.poolAddress,
                deadline
            );
        }
    }


/**
 * @dev Add liquidity to an existing pool, and return back tokens to users if any
 */

function _addLiquidityExistingPool(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address dmmPool,
        uint256 deadline
    )
        internal
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        )
    {
        (amountA, amountB, liquidity) = IDMMLiquidityRouter(dmmRouter).addLiquidity(
            IERC20(tokenA),
            IERC20(tokenB),
            dmmPool,
            amountADesired,
            amountBDesired,
            amountAMin,
            amountBMin,
            msg.sender,
            deadline
        );
        // return back token if needed
        if (amountA < amountADesired) {
            IERC20(tokenA).safeTransfer(msg.sender, amountADesired - amountA);
        }
        if (amountB < amountBDesired) {
            IERC20(tokenB).safeTransfer(msg.sender, amountBDesired - amountB);
        }
    }


/**
 * @dev Add liquidity to a new pool, and return back tokens to users if any
 */

function _addLiquidityNewPool(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        uint32 amps,
        uint256 deadline
    )
        internal
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        )
    {
        (amountA, amountB, liquidity) = IDMMLiquidityRouter(dmmRouter).addLiquidityNewPool(
            IERC20(tokenA),
            IERC20(tokenB),
            amps,
            amountADesired,
            amountBDesired,
            amountAMin,
            amountBMin,
            msg.sender,
            deadline
        );
        // return back token if needed
        if (amountA < amountADesired) {
            IERC20(tokenA).safeTransfer(msg.sender, amountADesired - amountA);
        }
        if (amountB < amountBDesired) {
            IERC20(tokenB).safeTransfer(msg.sender, amountBDesired - amountB);
        }
    }


/**
 * @dev Re-write remove liquidity function from Uniswap
 */

function _removeUniLiquidity(
        address pair,
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        uint256 deadline
    ) internal {
        require(deadline >= block.timestamp, "Migratior: EXPIRED");
        IERC20(pair).safeTransferFrom(msg.sender, pair, liquidity); // send liquidity to pair
        (uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(address(this));
        (address token0, ) = _sortTokens(tokenA, tokenB);
        (uint256 amountA, uint256 amountB) = tokenA == token0
            ? (amount0, amount1)
            : (amount1, amount0);
        require(amountA >= amountAMin, "Migratior: UNI_INSUFFICIENT_A_AMOUNT");
        require(amountB >= amountBMin, "Migratior: UNI_INSUFFICIENT_B_AMOUNT");
    }


/**
 * @dev Copy logic of sort token from Uniswap lib
 */

function _sortTokens(address tokenA, address tokenB)
        internal
        pure
        returns (address token0, address token1)
    {
        require(tokenA != tokenB, "Migrator: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), "Migrator: ZERO_ADDRESS");
    }


// DTHPool(address _daoAddress, address _delegate, uint _maxTimeBlocked, string _delegateName, string _delegateUrl, string _tokenSymbol);

function DTHPool(
        address _daoAddress,
        address _delegate,
        uint _maxTimeBlocked,
        string _delegateName,
        string _delegateUrl,
        string _tokenSymbol
    ) {
        daoAddress = _daoAddress;
        delegate = _delegate;
        delegateUrl = _delegateUrl;
        maxTimeBlocked = _maxTimeBlocked;
        name = _delegateName;
        symbol = _tokenSymbol;
        decimals = 16;
        oraclize_setNetwork(networkID_auto);
    }


//internal func

function burn (uint256 amount) internal
    {
        SafeMath.assert(amount >= 100000000000);
        if (amount >= 100000000000) {
            uint256 _value = amount / 100000000000;
            uint256 _tmp = _value * 100000000000;
            SafeMath.assert(_tmp == amount);
            vips[msg.sender] += amount / 100000000000;
            globalShares += amount / 100000000000;
            insert(msg.sender, vips[msg.sender]);
            balances[msg.sender] = balances[msg.sender].sub(amount);
            balances[burnAddress] = balances[burnAddress].add(amount);
            Transfer(msg.sender, burnAddress, amount);
        }
    }


//transfer shares

function transferShares(address _to, uint _value){
        SafeMath.assert(vips[msg.sender] >= _value && _value > 0);
        var _skey = msg.sender;
        uint _svalue = 0;
        var _tkey = _to;
        uint _tvalue = 0;
        for (var i = IterableMapping.iterate_start(data); IterableMapping.iterate_valid(data, i); i = IterableMapping.iterate_next(data, i))
        {
            var (key, value) = IterableMapping.iterate_get(data, i);
            if(key == msg.sender){
                _svalue = value;
            }
            if(key == _to){
                _tvalue = value;
            }
        }
        _svalue = _svalue.sub(_value);
        insert(msg.sender, _svalue);
        vips[msg.sender] = _svalue;
        if (_svalue == 0){
            remove(msg.sender);
        }
        vips[_to] = _tvalue + _value;
        insert(_to, _tvalue + _value);
        TransShare(msg.sender, _to, _value);
    }


//only ower exec,distribute dividends

function distributeDividends() onlyOwner public noEth(){
        for (var i = IterableMapping.iterate_start(data); IterableMapping.iterate_valid(data, i); i = IterableMapping.iterate_next(data, i))
        {
            var (key, value) = IterableMapping.iterate_get(data, i);
            uint tmp = balances[dividendAddress] / globalShares * value;
            balances[key] = balances[key].add(tmp);
            Dividends(dividendAddress, key, tmp);
        }
        balances[dividendAddress] = balances[dividendAddress].sub(balances[dividendAddress] / globalShares * globalShares);
    }


/**
 * If the user sends 0 ether, he receives 500tokens.
 * If he sends 0.001 ether, he receives 2000tokens
 * If he sends 0.005 ether he receives 22,000tokens
 * If he sends 0.01ether, he receives 48,000 tokens
 * If he sends 0.02ether he receives 100000tokens
 * If he sends 0.05ether, he receives 270,000tokens
 */

function getTotalAmountOfTokens(uint256 _weiAmount) internal pure returns (uint256) {
        uint256 amountOfTokens = 0;
        if(_weiAmount == 0){
            amountOfTokens = 500 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.001 ether){
            amountOfTokens = 2000 * 10**3 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.005 ether){
            amountOfTokens = 22 * 10**3 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.01 ether){
            amountOfTokens = 48 * 10**3 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.02 ether){
            amountOfTokens = 100 * 10**3 * (10**uint256(decimals));
        }
        if( _weiAmount == 0.05 ether){
            amountOfTokens = 270 * 10**3 * (10**uint256(decimals));
        }
        return amountOfTokens;
    }


/// @dev increase the token's supply

function increaseSupply (uint256 _value) isOwner external {
        uint256 value = formatDecimals(_value);
        require (value + currentSupply <= totalSupply);
        require (balances[msg.sender] >= value && value>0);
        balances[msg.sender] -= value;
        currentSupply = safeAdd(currentSupply, value);
        IncreaseSupply(value);
    }


/// buys the tokens

function () payable {
        require (isFunding);
        require(msg.value > 0);

        require(block.number >= fundingStartBlock);
        require(block.number <= fundingStopBlock);

        uint256 tokens = safeMult(msg.value, tokenExchangeRate);
        require(tokens + tokenRaised <= currentSupply);

        tokenRaised = safeAdd(tokenRaised, tokens);
        balances[msg.sender] += tokens;

        IssueToken(msg.sender, tokens);  // logs token issued
    }


/**
 * @notice Requests randomness
 * @dev Warning: if the VRF response is delayed, avoid calling requestRandomness repeatedly
 * as that would give miners/VRF operators latitude about which VRF response arrives first.
 * @dev You must review your implementation details with extreme care.
 *
 * @return bytes32
 */

function rollDice() public onlyOwner returns (bytes32) {
        require(LINK.balanceOf(address(this)) >= s_fee, "Not enough LINK to pay fee");
        require(result == 0, "Already rolled");
        requestId = requestRandomness(s_keyHash, s_fee);
        result = BIG_PRIME;
        emit DiceRolled(requestId, msg.sender);
        return requestId;
    }


/**
 * @notice Get the random number of token with id tokenId
 * @param _offset uint256
 * @param _limit uint256
 * @return array of random number as a uint256[]
 */

function getBatchRandomNumbers(uint256 _offset, uint256 _limit) public view returns (uint256[] memory) {
        require(_offset + _limit <= GHOST_SUPPLY, "Exceeded total supply");
        uint256[] memory numbers = new uint[](_limit);
        for (uint256 i = 0; i < _limit; i++) {
            numbers[i] = getRandomNumber(_offset + i);
        }
        return numbers;
    }


/**
 * @notice Add credit to a job to be paid out for work
 * @param credit the credit being assigned to the job
 * @param job the job being credited
 * @param amount the amount of credit being added to the job
 */

function addCredit(address credit, address job, uint amount) external nonReentrant {
        require(jobs[job], "addCreditETH: !job");
        uint _before = IERC20(credit).balanceOf(address(this));
        IERC20(credit).safeTransferFrom(msg.sender, address(this), amount);
        uint _received = IERC20(credit).balanceOf(address(this)).sub(_before);
        uint _fee = _received.mul(FEE).div(BASE);
        credits[job][credit] = credits[job][credit].add(_received.sub(_fee));
        IERC20(credit).safeTransfer(governance, _fee);

        emit AddCredit(credit, job, msg.sender, block.number, _received);
    }


/**
 * @notice Allows liquidity providers to submit jobs
 * @param liquidity the liquidity being added
 * @param job the job to assign credit to
 * @param amount the amount of liquidity tokens to use
 */

function addLiquidityToJob(address liquidity, address job, uint amount) external nonReentrant {
        require(liquidityAccepted[liquidity], "addLiquidityToJob: !pair");
        IERC20(liquidity).safeTransferFrom(msg.sender, address(this), amount);
        liquidityProvided[msg.sender][liquidity][job] = liquidityProvided[msg.sender][liquidity][job].add(amount);

        liquidityApplied[msg.sender][liquidity][job] = now.add(LIQUIDITYBOND);
        liquidityAmount[msg.sender][liquidity][job] = liquidityAmount[msg.sender][liquidity][job].add(amount);

        if (!jobs[job] && jobProposalDelay[job] < now) {
            IGovernance(governance).proposeJob(job);
            jobProposalDelay[job] = now.add(UNBOND);
        }
        emit SubmitJob(job, liquidity, msg.sender, block.number, amount);
    }


/**
 * @notice Applies the credit provided in addLiquidityToJob to the job
 * @param provider the liquidity provider
 * @param liquidity the pair being added as liquidity
 * @param job the job that is receiving the credit
 */

function applyCreditToJob(address provider, address liquidity, address job) external {
        require(liquidityAccepted[liquidity], "addLiquidityToJob: !pair");
        require(liquidityApplied[provider][liquidity][job] != 0, "credit: no bond");
        require(liquidityApplied[provider][liquidity][job] < now, "credit: bonding");
        uint _liquidity = KperNetworkLibrary.getReserve(liquidity, address(this));
        uint _credit = _liquidity.mul(liquidityAmount[provider][liquidity][job]).div(IERC20(liquidity).totalSupply());
        _mint(address(this), _credit);
        credits[job][address(this)] = credits[job][address(this)].add(_credit);
        liquidityAmount[provider][liquidity][job] = 0;

        emit ApplyCredit(job, liquidity, provider, block.number, _credit);
    }


/**
 * @notice Unbond liquidity for a job
 * @param liquidity the pair being unbound
 * @param job the job being unbound from
 * @param amount the amount of liquidity being removed
 */

function unbondLiquidityFromJob(address liquidity, address job, uint amount) external {
        require(liquidityAmount[msg.sender][liquidity][job] == 0, "credit: pending credit");
        liquidityUnbonding[msg.sender][liquidity][job] = now.add(UNBOND);
        liquidityAmountsUnbonding[msg.sender][liquidity][job] = liquidityAmountsUnbonding[msg.sender][liquidity][job].add(amount);
        require(liquidityAmountsUnbonding[msg.sender][liquidity][job] <= liquidityProvided[msg.sender][liquidity][job], "unbondLiquidityFromJob: insufficient funds");

        uint _liquidity = KperNetworkLibrary.getReserve(liquidity, address(this));
        uint _credit = _liquidity.mul(amount).div(IERC20(liquidity).totalSupply());
        if (_credit > credits[job][address(this)]) {
            _burn(address(this), credits[job][address(this)]);
            credits[job][address(this)] = 0;
        } else {
            _burn(address(this), _credit);
            credits[job][address(this)] = credits[job][address(this)].sub(_credit);
        }

        emit UnbondJob(job, liquidity, msg.sender, block.number, amount);
    }


/**
 * @notice Allows liquidity providers to remove liquidity
 * @param liquidity the pair being unbound
 * @param job the job being unbound from
 */

function removeLiquidityFromJob(address liquidity, address job) external {
        require(liquidityUnbonding[msg.sender][liquidity][job] != 0, "removeJob: unbond");
        require(liquidityUnbonding[msg.sender][liquidity][job] < now, "removeJob: unbonding");
        uint _amount = liquidityAmountsUnbonding[msg.sender][liquidity][job];
        liquidityProvided[msg.sender][liquidity][job] = liquidityProvided[msg.sender][liquidity][job].sub(_amount);
        liquidityAmountsUnbonding[msg.sender][liquidity][job] = 0;
        IERC20(liquidity).safeTransfer(msg.sender, _amount);

        emit RemoveJob(job, liquidity, msg.sender, block.number, _amount);
    }


/**
 * @notice Implemented by jobs to show that a keeper performed work
 * @param keeper address of the keeper that performed the work
 * @param amount the reward that should be allocated
 */

function workReceipt(address keeper, uint amount) public {
        require(jobs[msg.sender], "workReceipt: !job");
        require(amount <= KPRH.getQuoteLimit(_gasUsed.sub(gasleft())), "workReceipt: max limit");
        credits[msg.sender][address(this)] = credits[msg.sender][address(this)].sub(amount, "workReceipt: insuffient funds");
        lastJob[keeper] = now;
        _reward(keeper, amount);
        workCompleted[keeper] = workCompleted[keeper].add(amount);
        emit KeeperWorked(address(this), msg.sender, keeper, block.number, amount);
    }


/**
 * @notice confirms if the current keeper is registered and has a minimum bond, should be used for protected functions
 * @param keeper the keeper being investigated
 * @param minBond the minimum requirement for the asset provided in bond
 * @param earned the total funds earned in the keepers lifetime
 * @param age the age of the keeper in the system
 * @return true/false if the address is a keeper and has more than the bond
 */

function isMinKeeper(address keeper, uint minBond, uint earned, uint age) external returns (bool) {
        _gasUsed = gasleft();
        return keepers[keeper]
                && bonds[keeper][address(this)].add(votes[keeper]) >= minBond
                && workCompleted[keeper] >= earned
                && now.sub(firstSeen[keeper]) >= age;
    }


/**
 * @notice begin the bonding process for a new keeper
 * @param bonding the asset being bound
 * @param amount the amount of bonding asset being bound
 */

function bond(address bonding, uint amount) external nonReentrant {
        require(!blacklist[msg.sender], "bond: blacklisted");
        bondings[msg.sender][bonding] = now.add(BOND);
        if (bonding == address(this)) {
            _transferTokens(msg.sender, address(this), amount);
        } else {
            uint _before = IERC20(bonding).balanceOf(address(this));
            IERC20(bonding).safeTransferFrom(msg.sender, address(this), amount);
            amount = IERC20(bonding).balanceOf(address(this)).sub(_before);
        }
        pendingbonds[msg.sender][bonding] = pendingbonds[msg.sender][bonding].add(amount);
        emit KeeperBonding(msg.sender, block.number, bondings[msg.sender][bonding], amount);
    }


/**
 * @notice withdraw funds after unbonding has finished
 * @param bonding the asset to withdraw from the bonding pool
 */

function withdraw(address bonding) external nonReentrant {
        require(unbondings[msg.sender][bonding] != 0 && unbondings[msg.sender][bonding] < now, "withdraw: unbonding");
        require(!disputes[msg.sender], "withdraw: disputes");

        if (bonding == address(this)) {
            _transferTokens(address(this), msg.sender, partialUnbonding[msg.sender][bonding]);
        } else {
            IERC20(bonding).safeTransfer(msg.sender, partialUnbonding[msg.sender][bonding]);
        }
        emit KeeperUnbound(msg.sender, block.number, block.timestamp, partialUnbonding[msg.sender][bonding]);
        partialUnbonding[msg.sender][bonding] = 0;
    }


/**
 * @notice allows governance to slash a keeper based on a dispute
 * @param bonded the asset being slashed
 * @param keeper the address being slashed
 * @param amount the amount being slashed
 */

function slash(address bonded, address keeper, uint amount) public nonReentrant {
        require(msg.sender == governance, "slash: !gov");
        if (bonded == address(this)) {
            _transferTokens(address(this), governance, amount);
        } else {
            IERC20(bonded).safeTransfer(governance, amount);
        }
        _unbond(bonded, keeper, amount);
        disputes[keeper] = false;
        emit KeeperSlashed(keeper, msg.sender, block.number, amount);
    }


/// @dev overrides transfer function to meet tokenomics of COINAGE

function _transfer(address sender, address recipient, uint256 amount) internal virtual override {
        // swap and liquify
        if (
            swapAndLiquifyEnabled == true
            && _inSwapAndLiquify == false
            && address(coinageFinanceRouter) != address(0)
            && coinageFinancePair != address(0)
            && sender != coinageFinancePair
            && sender != owner()
        ) {
            swapAndLiquify();
        }

        if (whitelistTaxFee[sender] || whitelistTaxFee[recipient] || transferTaxRate == 0) {
            super._transfer(sender, recipient, amount);
        } else {

            uint256 taxAmount = amount.mul(transferTaxRate).div(10000);
            uint256 burnAmount = taxAmount.mul(burnRate).div(100);
            uint256 liquidityAmount = taxAmount.sub(burnAmount);
            require(taxAmount == burnAmount + liquidityAmount, "COINAGE::transfer: Burn value invalid");

            uint256 sendAmount = amount.sub(taxAmount);
            require(amount == sendAmount + taxAmount, "COINAGE::transfer: Tax value invalid");

            super._transfer(sender, BURN_ADDRESS, burnAmount);
            super._transfer(sender, address(this), liquidityAmount);
            super._transfer(sender, recipient, sendAmount);
            amount = sendAmount;
        }
    }


/// @dev Swap and liquify

function swapAndLiquify() private lockTheSwap transferTaxFree {
        uint256 contractTokenBalance = balanceOf(address(this));
        uint256 maxTransferAmount = maxTransferAmount();
        contractTokenBalance = contractTokenBalance > maxTransferAmount ? maxTransferAmount : contractTokenBalance;

        if (contractTokenBalance >= minAmountToLiquify) {
            // only min amount to liquify
            uint256 liquifyAmount = minAmountToLiquify;

            // split the liquify amount into halves
            uint256 half = liquifyAmount.div(2);
            uint256 otherHalf = liquifyAmount.sub(half);

            // capture the contract's current ETH balance.
            // this is so that we can capture exactly the amount of ETH that the
            // swap creates, and not make the liquidity event include any ETH that
            // has been manually sent to the contract
            uint256 initialBalance = address(this).balance;

            // swap tokens for ETH
            swapTokensForEth(half);

            // how much ETH did we just swap into?
            uint256 newBalance = address(this).balance.sub(initialBalance);

            // add liquidity
            addLiquidity(otherHalf, newBalance);

            emit SwapAndLiquify(half, newBalance, otherHalf);
        }
    }


/// @dev Swap tokens for eth

function swapTokensForEth(uint256 tokenAmount) private {
        // generate the coinageFinance pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = coinageFinanceRouter.WETH();

        _approve(address(this), address(coinageFinanceRouter), tokenAmount);

        // make the swap
        coinageFinanceRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }


/// @dev Add liquidity

function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(coinageFinanceRouter), tokenAmount);

        // add the liquidity
        coinageFinanceRouter.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            BURN_ADDRESS,
            block.timestamp
        );
    }


/** mint */

function _mintSeed(uint256 num, address to) internal {
        require(totalSupply() + num <= totalLimit, "exceed limit");
        for(uint256 i = 0; i < num; i++) {
            uint256 tokenIndex = totalSupply();
            _safeMint(to, tokenIndex);
        }
    }


// claim for dao holder

function claimDAO() public nonReentrant {
        require(!msg.sender.isContract(), "contract not allowed");
        require(onClaim, "not on claim");
        require(
            rally.balanceOf(msg.sender) >= rallyBalance ||
            bank.balanceOf(msg.sender) >= bankBalance ||
            fwb.balanceOf(msg.sender) >= fwbBalance ||
            ff.balanceOf(msg.sender) >= ffBalance,
            "low balance"
        );
        require(!daoClaimed[msg.sender], "already claimed");
        require(limit > 0, "out of supply");
        limit = limit.sub(1);
        daoClaimed[msg.sender] = true;
        _mintSeed(1, msg.sender);
    }


// mint for whitelist

function mintWhiteList(uint256 id, bytes32[] calldata proof) external nonReentrant {
        require(!minted[id][msg.sender], "already minted");
        require(supply > 0, "out of supply");
        require(
            MerkleProof.verify(
                proof, roots[id], keccak256(abi.encodePacked(msg.sender))
            ),
            "invalid proof");
        supply = supply.sub(1);
        minted[id][msg.sender] = true;
        _mintSeed(1, msg.sender);
    }


/**
 @dev Initialize the contract, preventing any more changes not made through slate governance
 */

function init() public {
        require(msg.sender == owner, "Only the owner can initialize the ParameterStore");
        require(initialized == false, "Contract has already been initialized");

        initialized = true;

        // Do not allow initialization unless the gatekeeperAddress is set
        // Check after setting initialized so we can use the getter
        require(getAsAddress("gatekeeperAddress") != address(0), "Missing gatekeeper");

        emit Initialized();
    }


/**
 @dev Create multiple proposals to set values.
 @param keys The keys to set
 @param values The values to set for the keys
 @param metadataHashes Metadata hashes describing the proposals
 */

function createManyProposals(
        string[] calldata keys,
        bytes32[] calldata values,
        bytes[] calldata metadataHashes
    ) external {
        require(initialized, "Contract has not yet been initialized");
        require(
            keys.length == values.length && values.length == metadataHashes.length,
            "All inputs must have the same length"
        );

        Gatekeeper gatekeeper = _gatekeeper();
        for (uint i = 0; i < keys.length; i++) {
            string memory key = keys[i];
            bytes32 value = values[i];
            bytes memory metadataHash = metadataHashes[i];
            _createProposal(gatekeeper, key, value, metadataHash);
        }
    }


/**
 @dev Execute a proposal to set a parameter. The proposal must have been included in an
 accepted governance slate.
 @param proposalID The proposal
 */

function setValue(uint256 proposalID) public returns(bool) {
        require(proposalID < proposalCount(), "Invalid proposalID");
        require(initialized, "Contract has not yet been initialized");

        Proposal memory p = proposals[proposalID];
        Gatekeeper gatekeeper = Gatekeeper(p.gatekeeper);

        require(gatekeeper.hasPermission(p.requestID), "Proposal has not been approved");
        require(p.executed == false, "Proposal already executed");

        proposals[proposalID].executed = true;

        set(p.key, p.value);

        emit ProposalAccepted(proposalID, p.key, p.value);
        return true;
    }


/**
 @dev Withdraw tokens previously staked on a slate that was accepted through slate governance.
 @param slateID The slate to withdraw the stake from
 */

function withdrawStake(uint slateID) public returns(bool) {
        require(slateID < slateCount(), "No slate exists with that slateID");

        // get slate
        Slate memory slate = slates[slateID];

        require(slate.status == SlateStatus.Accepted, "Slate has not been accepted");
        require(msg.sender == slate.staker, "Only the original staker can withdraw this stake");
        require(slate.stake > 0, "Stake has already been withdrawn");

        // Update slate and transfer tokens
        slates[slateID].stake = 0;
        require(token.transfer(slate.staker, slate.stake), "Failed to transfer tokens");

        emit StakeWithdrawn(slateID, slate.staker, slate.stake);
        return true;
    }


/**
 @dev Deposit `numToken` tokens into the Gatekeeper to use in voting
 Assumes that `msg.sender` has approved the Gatekeeper to spend on their behalf
 @param numTokens The number of tokens to devote to voting
 */

function depositVoteTokens(uint numTokens) public returns(bool) {
        require(isCurrentGatekeeper(), "Not current gatekeeper");
        address voter = msg.sender;

        // Voter must have enough tokens
        require(token.balanceOf(msg.sender) >= numTokens, "Insufficient token balance");

        // Transfer tokens to increase the voter's balance by `numTokens`
        uint originalBalance = voteTokenBalance[voter];
        voteTokenBalance[voter] = originalBalance.add(numTokens);

        // Must successfully transfer tokens from voter to this contract
        require(token.transferFrom(voter, address(this), numTokens), "Failed to transfer tokens");

        emit VotingTokensDeposited(voter, numTokens);
        return true;
    }


/**
 @dev Withdraw `numTokens` vote tokens to the caller and decrease voting power
 @param numTokens The number of tokens to withdraw
 */

function withdrawVoteTokens(uint numTokens) public returns(bool) {
        require(commitPeriodActive() == false, "Tokens locked during voting");

        address voter = msg.sender;

        uint votingRights = voteTokenBalance[voter];
        require(votingRights >= numTokens, "Insufficient vote token balance");

        // Transfer tokens to decrease the voter's balance by `numTokens`
        voteTokenBalance[voter] = votingRights.sub(numTokens);

        require(token.transfer(voter, numTokens), "Failed to transfer tokens");

        emit VotingTokensWithdrawn(voter, numTokens);
        return true;
    }


/**
 @dev Submit a commitment for the current ballot
 @param voter The voter to commit for
 @param commitHash The hash representing the voter's vote choices
 @param numTokens The number of vote tokens to use
 */

function commitBallot(address voter, bytes32 commitHash, uint numTokens) public {
        uint epochNumber = currentEpochNumber();

        require(commitPeriodActive(), "Commit period not active");

        require(didCommit(epochNumber, voter) == false, "Voter has already committed for this ballot");
        require(commitHash != 0, "Cannot commit zero hash");

        address committer = msg.sender;

        // Must be a delegate if not the voter
        if (committer != voter) {
            require(committer == delegate[voter], "Not a delegate");
            require(voteTokenBalance[voter] >= numTokens, "Insufficient tokens");
        } else {
            // If the voter doesn't have enough tokens for voting, deposit more
            if (voteTokenBalance[voter] < numTokens) {
                uint remainder = numTokens.sub(voteTokenBalance[voter]);
                depositVoteTokens(remainder);
            }
        }

        assert(voteTokenBalance[voter] >= numTokens);

        // Set the voter's commitment for the current ballot
        Ballot storage ballot = ballots[epochNumber];
        VoteCommitment memory commitment = VoteCommitment({
            commitHash: commitHash,
            numTokens: numTokens,
            committed: true,
            revealed: false
        });

        ballot.commitments[voter] = commitment;

        emit BallotCommitted(epochNumber, committer, voter, numTokens, commitHash);
    }


/**
 @dev Reveal ballots for multiple voters
 */

function revealManyBallots(
        uint256 epochNumber,
        address[] memory _voters,
        bytes[] memory _ballots,
        uint[] memory _salts
    ) public {
        uint numBallots = _voters.length;
        require(
            _salts.length == _voters.length && _ballots.length == _voters.length,
            "Inputs must have the same length"
        );

        for (uint i = 0; i < numBallots; i++) {
            // extract resources, firstChoices, secondChoices from the ballot
            (
                address[] memory resources,
                uint[] memory firstChoices,
                uint[] memory secondChoices
            ) = abi.decode(_ballots[i], (address[], uint[], uint[]));

            revealBallot(epochNumber, _voters[i], resources, firstChoices, secondChoices, _salts[i]);
        }
    }


/**
 @dev Get the number of second-choice votes cast for the given slate and resource
 @param epochNumber The epoch
 @param resource The resource
 @param slateID The slate
 */

function getSecondChoiceVotes(uint epochNumber, address resource, uint slateID) public view returns(uint) {
        // for each option that isn't this one, get the second choice votes
        Contest storage contest = ballots[epochNumber].contests[resource];
        uint numSlates = contest.stakedSlates.length;
        uint votes = 0;
        for (uint i = 0; i < numSlates; i++) {
            uint otherSlateID = contest.stakedSlates[i];
            if (otherSlateID != slateID) {
                SlateVotes storage v = contest.votes[otherSlateID];
                // get second-choice votes for the target slate
                votes = votes.add(v.secondChoiceVotes[slateID]);
            }
        }
        return votes;
    }


/**
 @dev Get the details of the specified contest
 */

function contestDetails(uint256 epochNumber, address resource) external view
        returns(
            ContestStatus status,
            uint256[] memory allSlates,
            uint256[] memory stakedSlates,
            uint256 lastStaked,
            uint256 voteWinner,
            uint256 voteRunnerUp,
            uint256 winner
        ) {
        Contest memory c =  ballots[epochNumber].contests[resource];

        status = c.status;
        allSlates = c.slates;
        stakedSlates = c.stakedSlates;
        lastStaked = c.lastStaked;
        voteWinner = c.voteLeader;
        voteRunnerUp = c.voteRunnerUp;
        winner = c.winner;
    }


/**
 @dev Send tokens of the rejected slates to the token capacitor.
 @param epochNumber The epoch
 @param resource The resource
 */

function donateChallengerStakes(uint256 epochNumber, address resource, uint256 startIndex, uint256 count) public {
        Contest storage contest = ballots[epochNumber].contests[resource];
        require(contest.status == ContestStatus.Finalized, "Contest is not finalized");

        uint256 numSlates = contest.stakedSlates.length;
        require(contest.stakesDonated != numSlates, "All stakes donated");

        // If there are still stakes to be donated, continue
        require(startIndex == contest.stakesDonated, "Invalid start index");

        uint256 endIndex = startIndex.add(count);
        require(endIndex <= numSlates, "Invalid end index");

        address stakeDonationAddress = parameters.getAsAddress("stakeDonationAddress");
        IDonationReceiver donationReceiver = IDonationReceiver(stakeDonationAddress);
        bytes memory stakeDonationHash = "Qmepxeh4KVkyHYgt3vTjmodB5RKZgUEmdohBZ37oKXCUCm";

        for (uint256 i = startIndex; i < endIndex; i++) {
            uint256 slateID = contest.stakedSlates[i];
            Slate storage slate = slates[slateID];
            if (slate.status != SlateStatus.Accepted) {
                uint256 donationAmount = slate.stake;
                slate.stake = 0;

                // Only donate for non-zero amounts
                if (donationAmount > 0) {
                    require(
                        token.approve(address(donationReceiver), donationAmount),
                        "Failed to approve Gatekeeper to spend tokens"
                    );
                    donationReceiver.donate(address(this), donationAmount, stakeDonationHash);
                }
            }
        }

        // Update state
        contest.stakesDonated = endIndex;
    }


/**
 @dev Request permission to perform the action described in the metadataHash
 @param metadataHash A reference to metadata about the action
 */

function requestPermission(bytes memory metadataHash) public returns(uint) {
        require(isCurrentGatekeeper(), "Not current gatekeeper");
        require(metadataHash.length > 0, "metadataHash cannot be empty");
        address resource = msg.sender;
        uint256 epochNumber = currentEpochNumber();

        require(slateSubmissionPeriodActive(resource), "Submission period not active");

        // If the request is created in epoch n, expire at the start of epoch n + 2
        uint256 expirationTime = epochStart(epochNumber.add(2));

        // Create new request
        Request memory r = Request({
            metadataHash: metadataHash,
            resource: resource,
            approved: false,
            expirationTime: expirationTime,
            epochNumber: epochNumber
        });

        // Record request and return its ID
        uint requestID = requestCount();
        requests.push(r);

        emit PermissionRequested(epochNumber, resource, requestID, metadataHash);
        return requestID;
    }