/**
 * @dev returns the expected target amount of converting one reserve to another along with the fee
 *
 * @param _sourceToken contract address of the source reserve token
 * @param _targetToken contract address of the target reserve token
 * @param _amount      amount of tokens received from the user
 *
 * @return expected target amount
 * @return expected fee
 */

function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount)
        public
        view
        active
        validReserve(_sourceToken)
        validReserve(_targetToken)
        returns (uint256, uint256)
    {
        // validate input
        require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");

        uint256 amount = IBancorFormula(addressOf(BANCOR_FORMULA)).crossReserveTargetAmount(
            reserveBalance(_sourceToken),
            reserves[_sourceToken].weight,
            reserveBalance(_targetToken),
            reserves[_targetToken].weight,
            _amount
        );

        // return the amount minus the conversion fee and the conversion fee
        uint256 fee = calculateFee(amount);
        return (amount - fee, fee);
    }


/**
 * @dev converts a specific amount of source tokens to target tokens
 * can only be called by the bancor network contract
 *
 * @param _sourceToken source ERC20 token
 * @param _targetToken target ERC20 token
 * @param _amount      amount of tokens to convert (in units of the source token)
 * @param _trader      address of the caller who executed the conversion
 * @param _beneficiary wallet to receive the conversion result
 *
 * @return amount of tokens received (in units of the target token)
 */

function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
        internal
        returns (uint256)
    {
        // get expected target amount and fee
        (uint256 amount, uint256 fee) = targetAmountAndFee(_sourceToken, _targetToken, _amount);

        // ensure that the trade gives something in return
        require(amount != 0, "ERR_ZERO_TARGET_AMOUNT");

        // ensure that the trade won't deplete the reserve balance
        uint256 targetReserveBalance = reserveBalance(_targetToken);
        assert(amount < targetReserveBalance);

        // ensure that the input amount was already deposited
        if (_sourceToken == ETH_RESERVE_ADDRESS)
            require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
        else
            require(msg.value == 0 && _sourceToken.balanceOf(this).sub(reserveBalance(_sourceToken)) >= _amount, "ERR_INVALID_AMOUNT");

        // sync the reserve balances
        syncReserveBalance(_sourceToken);
        reserves[_targetToken].balance = reserves[_targetToken].balance.sub(amount);

        // transfer funds to the beneficiary in the to reserve token
        if (_targetToken == ETH_RESERVE_ADDRESS)
            _beneficiary.transfer(amount);
        else
            safeTransfer(_targetToken, _beneficiary, amount);

        // dispatch the conversion event
        dispatchConversionEvent(_sourceToken, _targetToken, _trader, _amount, amount, fee);

        // dispatch rate updates
        dispatchRateEvents(_sourceToken, _targetToken);

        return amount;
    }


/**
 * @dev decreases the pool's liquidity and burns the caller's shares in the pool
 * note that prior to version 28, you should use 'liquidate' instead
 *
 * @param _amount                  token amount
 * @param _reserveTokens           address of each reserve token
 * @param _reserveMinReturnAmounts minimum return-amount of each reserve token
 */

function removeLiquidity(uint256 _amount, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts)
        public
        protected
        active
    {
        // verify the user input
        verifyLiquidityInput(_reserveTokens, _reserveMinReturnAmounts, _amount);

        // get the total supply BEFORE destroying the user tokens
        uint256 totalSupply = ISmartToken(anchor).totalSupply();

        // destroy the user tokens
        ISmartToken(anchor).destroy(msg.sender, _amount);

        // transfer to the user an equivalent amount of each one of the reserve tokens
        removeLiquidityFromPool(_reserveTokens, _reserveMinReturnAmounts, totalSupply, _amount);
    }


/**
 * @dev decreases the pool's liquidity and burns the caller's shares in the pool
 * for example, if the holder sells 10% of the supply,
 * then they will receive 10% of each reserve token balance in return
 * note that starting from version 28, you should use 'removeLiquidity' instead
 *
 * @param _amount  amount to liquidate (in the pool token)
 */

function liquidate(uint256 _amount) public protected {
        require(_amount > 0, "ERR_ZERO_AMOUNT");

        uint256 totalSupply = ISmartToken(anchor).totalSupply();
        ISmartToken(anchor).destroy(msg.sender, _amount);

        uint256[] memory reserveMinReturnAmounts = new uint256[](reserveTokens.length);
        for (uint256 i = 0; i < reserveMinReturnAmounts.length; i++)
            reserveMinReturnAmounts[i] = 1;

        removeLiquidityFromPool(reserveTokens, reserveMinReturnAmounts, totalSupply, _amount);
    }


/**
 * @dev verifies that a given array of tokens is identical to the converter's array of reserve tokens
 * we take this input in order to allow specifying the corresponding reserve amounts in any order
 *
 * @param _reserveTokens   array of reserve tokens
 * @param _reserveAmounts  array of reserve amounts
 * @param _amount          token amount
 */

function verifyLiquidityInput(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _amount) private view {
        uint256 i;
        uint256 j;

        uint256 length = reserveTokens.length;
        require(length == _reserveTokens.length, "ERR_INVALID_RESERVE");
        require(length == _reserveAmounts.length, "ERR_INVALID_AMOUNT");

        for (i = 0; i < length; i++) {
            // verify that every input reserve token is included in the reserve tokens
            require(reserves[_reserveTokens[i]].isSet, "ERR_INVALID_RESERVE");
            for (j = 0; j < length; j++) {
                if (reserveTokens[i] == _reserveTokens[j])
                    break;
            }
            // verify that every reserve token is included in the input reserve tokens
            require(j < length, "ERR_INVALID_RESERVE");
            // verify that every input reserve token amount is larger than zero
            require(_reserveAmounts[i] > 0, "ERR_INVALID_AMOUNT");
        }

        // verify that the input token amount is larger than zero
        require(_amount > 0, "ERR_ZERO_AMOUNT");
    }


/**
 * @dev adds liquidity (reserve) to the pool when it's empty
 *
 * @param _reserveTokens   address of each reserve token
 * @param _reserveAmounts  amount of each reserve token
 */

function addLiquidityToEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts)
        private
        returns (uint256)
    {
        // calculate the geometric-mean of the reserve amounts approved by the user
        uint256 amount = geometricMean(_reserveAmounts);

        // transfer each one of the reserve amounts from the user to the pool
        for (uint256 i = 0; i < _reserveTokens.length; i++) {
            if (_reserveTokens[i] != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
                safeTransferFrom(_reserveTokens[i], msg.sender, this, _reserveAmounts[i]);

            reserves[_reserveTokens[i]].balance = _reserveAmounts[i];

            emit LiquidityAdded(msg.sender, _reserveTokens[i], _reserveAmounts[i], _reserveAmounts[i], amount);

            // dispatch the `TokenRateUpdate` event for the pool token
            uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
            dispatchPoolTokenRateEvent(amount, _reserveTokens[i], _reserveAmounts[i], reserveWeight);
        }

        return amount;
    }


/**
 * @dev removes liquidity (reserve) from the pool
 *
 * @param _reserveTokens           address of each reserve token
 * @param _reserveMinReturnAmounts minimum return-amount of each reserve token
 * @param _totalSupply             token total supply
 * @param _amount                  token amount
 */

function removeLiquidityFromPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts, uint256 _totalSupply, uint256 _amount)
        private
    {
        syncReserveBalances();

        IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
        uint256 newPoolTokenSupply = _totalSupply.sub(_amount);

        for (uint256 i = 0; i < _reserveTokens.length; i++) {
            IERC20Token reserveToken = _reserveTokens[i];
            uint256 rsvBalance = reserves[reserveToken].balance;
            uint256 reserveAmount = formula.liquidateReserveAmount(_totalSupply, rsvBalance, reserveRatio, _amount);
            require(reserveAmount >= _reserveMinReturnAmounts[i], "ERR_ZERO_TARGET_AMOUNT");

            uint256 newReserveBalance = rsvBalance.sub(reserveAmount);
            reserves[reserveToken].balance = newReserveBalance;

            // transfer each one of the reserve amounts from the pool to the user
            if (reserveToken == ETH_RESERVE_ADDRESS)
                msg.sender.transfer(reserveAmount);
            else
                safeTransfer(reserveToken, msg.sender, reserveAmount);

            emit LiquidityRemoved(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);

            // dispatch the `TokenRateUpdate` event for the pool token
            uint32 reserveWeight = reserves[reserveToken].weight;
            dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
        }
    }


/**
 * @dev calculates the average number of decimal digits in a given list of values
 *
 * @param _values  list of values (each of which assumed positive)
 * @return the average number of decimal digits in the given list of values
 */

function geometricMean(uint256[] memory _values) public pure returns (uint256) {
        uint256 numOfDigits = 0;
        uint256 length = _values.length;
        for (uint256 i = 0; i < length; i++)
            numOfDigits += decimalLength(_values[i]);
        return uint256(10) ** (roundDiv(numOfDigits, length) - 1);
    }


/**
 * @dev dispatches rate events for both reserves / pool tokens
 * only used to circumvent the `stack too deep` compiler error
 *
 * @param _sourceToken address of the source reserve token
 * @param _targetToken address of the target reserve token
 */

function dispatchRateEvents(IERC20Token _sourceToken, IERC20Token _targetToken) private {
        uint256 poolTokenSupply = ISmartToken(anchor).totalSupply();
        uint256 sourceReserveBalance = reserveBalance(_sourceToken);
        uint256 targetReserveBalance = reserveBalance(_targetToken);
        uint32 sourceReserveWeight = reserves[_sourceToken].weight;
        uint32 targetReserveWeight = reserves[_targetToken].weight;

        // dispatch token rate update event
        uint256 rateN = targetReserveBalance.mul(sourceReserveWeight);
        uint256 rateD = sourceReserveBalance.mul(targetReserveWeight);
        emit TokenRateUpdate(_sourceToken, _targetToken, rateN, rateD);

        // dispatch the `TokenRateUpdate` event for the pool token
        dispatchPoolTokenRateEvent(poolTokenSupply, _sourceToken, sourceReserveBalance, sourceReserveWeight);
        dispatchPoolTokenRateEvent(poolTokenSupply, _targetToken, targetReserveBalance, targetReserveWeight);

        // dispatch price data update events (deprecated events)
        emit PriceDataUpdate(_sourceToken, poolTokenSupply, sourceReserveBalance, sourceReserveWeight);
        emit PriceDataUpdate(_targetToken, poolTokenSupply, targetReserveBalance, targetReserveWeight);
    }


//backend mint

function mintInternal(address wallet, uint amount) internal {
        uint currentTokenSupply = _tokenIdCounter.current();
        require((currentTokenSupply + amount) <= maxSupply, "Not enough tokens left");
            for(uint i = 0; i< amount; i++){
            currentTokenSupply++;
            _safeMint(wallet, currentTokenSupply);
            _tokenIdCounter.increment();
        }
        }


/** Used to determine if a staked token is owned. Used to allow game logic to occur outside this contract.
 * This might not be necessary if the training mechanic is in this contract instead */

function ownsToken(uint256 tokenId) external view returns (bool) {
    uint64 lastTokenWrite = wndNFT.getTokenWriteBlock(tokenId);
    // Must check this, as getTokenTraits will be allowed since this contract is an admin
    require(lastTokenWrite < block.number, "hmmmm what doing?");
    IWnD.WizardDragon memory s = wndNFT.getTokenTraits(tokenId);
    if(s.isWizard) {
        return tower[tokenId].owner == tx.origin || training[tokenId].owner == tx.origin;
    }
    uint8 rank = _rankForDragon(tokenId);
    return flight[rank][flightIndices[tokenId]].owner == tx.origin;
  }


/**
 * adds Wizards and Dragons to the Tower and Flight
 * @param tokenIds the IDs of the Wizards and Dragons to stake
 */

function addManyToTowerAndFlight(address tokenOwner, uint16[] calldata tokenIds) external override nonReentrant {
    require(admins[_msgSender()], "Only admins can stake");
    for (uint i = 0; i < tokenIds.length; i++) {
      if (wndNFT.ownerOf(tokenIds[i]) != address(this)) { // a mint + stake will send directly to the staking contract
        require(wndNFT.ownerOf(tokenIds[i]) == tokenOwner, "You don't own this token");
        wndNFT.transferFrom(tokenOwner, address(this), tokenIds[i]);
      } else if (tokenIds[i] == 0) {
        continue; // there may be gaps in the array for stolen tokens
      }

      if (wndNFT.isWizard(tokenIds[i]))
        _addWizardToTower(tokenOwner, tokenIds[i]);
      else
        _addDragonToFlight(tokenOwner, tokenIds[i]);
    }
  }


/**
 * adds Wizards and Dragons to the Tower and Flight
 * @param seed the seed for random logic
 * @param tokenIds the IDs of the Wizards and Dragons to stake
 */

function addManyToTrainingAndFlight(uint256 seed, address tokenOwner, uint16[] calldata tokenIds) external override nonReentrant {
    require(admins[_msgSender()], "Only admins can stake");
    for (uint i = 0; i < tokenIds.length; i++) {
        require(wndNFT.ownerOf(tokenIds[i]) == tokenOwner, "You don't own this token");
        seed = uint256(keccak256(abi.encodePacked(
            tx.origin,
            seed
        )));
        address recipient = selectRecipient(seed, tokenOwner);
        if(tokenIds[i] <= 15000) {
            // Don't allow gen0 tokens from being stolen
            recipient = tokenOwner;
        }
        if(recipient != tokenOwner) { // stolen
            wndNFT.transferFrom(tokenOwner, recipient, tokenIds[i]);
            continue;
        }
        wndNFT.transferFrom(tokenOwner, address(this), tokenIds[i]);
        if (wndNFT.isWizard(tokenIds[i])) {
            _addWizardToTraining(recipient, tokenIds[i]);
        }
        else {
            _addDragonToFlight(recipient, tokenIds[i]);
        }
    }
  }


/**
 * adds a single Dragon to the Flight
 * @param account the address of the staker
 * @param tokenId the ID of the Dragon to add to the Flight
 */

function _addDragonToFlight(address account, uint256 tokenId) internal {
    uint8 rank = _rankForDragon(tokenId);
    totalRankStaked += rank; // Portion of earnings ranges from 8 to 5
    flightIndices[tokenId] = flight[rank].length; // Store the location of the dragon in the Flight
    flight[rank].push(StakeDragon({
      owner: account,
      tokenId: uint16(tokenId),
      lastClaimTime: uint80(block.timestamp),
      value: gpPerRank
    })); // Add the dragon to the Flight
    _deposits[account].dragons.add(tokenId);
    emit TokenStaked(account, tokenId, false, true);
  }


/**
 * realize $GP earnings and optionally unstake tokens from the Tower / Flight
 * to unstake a Wizard it will require it has 2 days worth of $GP unclaimed
 * @param tokenIds the IDs of the tokens to claim earnings from
 * @param unstake whether or not to unstake ALL of the tokens listed in tokenIds
 */

function claimManyFromTowerAndFlight(address tokenOwner, uint16[] calldata tokenIds, bool unstake) external override whenNotPaused _updateEarnings nonReentrant {
    require(admins[_msgSender()], "Only admins can stake");
    uint256 owed = 0;
    uint16 owedRunes = 0;
    for (uint i = 0; i < tokenIds.length; i++) {
      if (wndNFT.isWizard(tokenIds[i])) {
        owed += _claimWizardFromTower(tokenIds[i], unstake, tokenOwner);
      }
      else {
        (uint256 gpOwed, uint16 runes) = _claimDragonFromFlight(tokenIds[i], unstake, tokenOwner);
        owed += gpOwed;
        owedRunes += runes;
      }
    }
    gpToken.updateOriginAccess();
    if(owed > 0) {
        gpToken.mint(tokenOwner, owed);
    }
    if(owedRunes > 0 && curMagicRunesEmitted + owedRunes <= MAX_MAGIC_RUNE_EMISSION) {
        curMagicRunesEmitted += owedRunes;
        alter.mint(magicRune, owedRunes, tokenOwner);
    }
  }


/** Get the tax percent owed to dragons. If the address doesn't contain a 1:10 ratio of whips to staked wizards,
 *  they are subject to an untrained tax */

function getTaxPercent(address addr) internal returns (uint256) {
      if(_deposits[addr].towerWizards.length() <= 10) {
          //
          return alter.balanceOf(addr, whip) >= 1 ? GP_CLAIM_TAX_PERCENTAGE : GP_CLAIM_TAX_PERCENTAGE_UNTRAINED;
      }
      return alter.balanceOf(addr, whip) >= _deposits[addr].towerWizards.length() / 10 ? GP_CLAIM_TAX_PERCENTAGE : GP_CLAIM_TAX_PERCENTAGE_UNTRAINED;
  }


/**
 * chooses a random Dragon thief when a newly minted token is stolen
 * @param seed a random value to choose a Dragon from
 * @return the owner of the randomly selected Dragon thief
 */

function randomDragonOwner(uint256 seed) public view override returns (address) {
    if (totalRankStaked == 0) {
      return address(0x0);
    }
    uint256 bucket = (seed & 0xFFFFFFFF) % totalRankStaked; // choose a value from 0 to total rank staked
    uint256 cumulative;
    seed >>= 32;
    // loop through each bucket of Dragons with the same rank score
    for (uint i = MAX_RANK - 3; i <= MAX_RANK; i++) {
      cumulative += flight[i].length * i;
      // if the value is not inside of that bucket, keep going
      if (bucket >= cumulative) continue;
      // get the address of a random Dragon with that rank score
      return flight[i][seed % flight[i].length].owner;
    }
    return address(0x0);
  }


/**
 * add $GP to claimable pot for the Flight
 * @param amount $GP to add to the pot
 */

function _payDragonTax(uint256 amount) internal {
    if (totalRankStaked == 0) { // if there's no staked dragons
      unaccountedRewards += amount; // keep track of $GP due to dragons
      return;
    }
    // makes sure to include any unaccounted $GP
    gpPerRank += (amount + unaccountedRewards) / totalRankStaked;
    unaccountedRewards = 0;
  }


// Overrides

function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721)
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return unrevealedURL;
        }

        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json"))
                : "";
    }


/// @dev send ETH from this contract to its creator

function sendETHToCreator(uint256 _amount) external {
        require(msg.sender == creator, "Sender must be creator");
        // we use `call` here following the recommendation from
        // https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/
        (bool success,  ) = creator.call{value: _amount}("");
        require(success, "Transfer failed");
    }


// to caclulate the amounts for recipient and distributer after fees have been applied

function calculateFeesBeforeSend(
        address sender,
        address recipient,
        uint256 amount
    ) public view returns (uint256, uint256) {

        if(UniSwapReciever[recipient] && UniSwapSaleEnds){
            uint256 timeNow = block.timestamp;
            uint256 sfee = 5; //5% fee default if time not met in loop.

            uint256 datediff = timeNow - UniSwapSaleEndsTime;

            datediff = datediff.div(oneDay);
            datediff = datediff.mod(turning);

            if(datediff == 0) { sfee = 30; }
            else if(datediff == 1) { sfee = 25; }
            else if(datediff == 2) { sfee = 20; }
            else if(datediff == 3) { sfee = 15; }
            else if(datediff == 4) { sfee = 10; }
            else if(datediff == 5) { sfee = 5; }

            sfee = amount.mul(sfee).div(100);
            return (amount.sub(sfee), sfee);
        }

        if (feelessSender[sender] || feelessReciever[recipient]) {
            return (amount, 0);
        }

        // calculate fees and amounts
        uint256 fee = amount.mul(txFee).div(1000);
        return (amount.sub(fee), fee);
    }


/* @notice          Convert boolean value into bytes
 *  @param b         The boolean value
 *  @return          Converted bytes array
 */

function WriteBool(bool b) internal pure returns (bytes memory) {
        bytes memory buff;
        assembly{
            buff := mload(0x40)
            mstore(buff, 1)
            switch iszero(b)
            case 1 {
                mstore(add(buff, 0x20), shl(248, 0x00))
                // mstore8(add(buff, 0x20), 0x00)
            }
            default {
                mstore(add(buff, 0x20), shl(248, 0x01))
                // mstore8(add(buff, 0x20), 0x01)
            }
            mstore(0x40, add(buff, 0x21))
        }
        return buff;
    }


/* @notice          Convert uint8 value into bytes
 *  @param v         The uint8 value
 *  @return          Converted bytes array
 */

function WriteUint8(uint8 v) internal pure returns (bytes memory) {
        bytes memory buff;
        assembly{
            buff := mload(0x40)
            mstore(buff, 1)
            mstore(add(buff, 0x20), shl(248, v))
            // mstore(add(buff, 0x20), byte(0x1f, v))
            mstore(0x40, add(buff, 0x21))
        }
        return buff;
    }


/* @notice          Convert uint16 value into bytes
 *  @param v         The uint16 value
 *  @return          Converted bytes array
 */

function WriteUint16(uint16 v) internal pure returns (bytes memory) {
        bytes memory buff;

        assembly{
            buff := mload(0x40)
            let byteLen := 0x02
            mstore(buff, byteLen)
            for {
                let mindex := 0x00
                let vindex := 0x1f
            } lt(mindex, byteLen) {
                mindex := add(mindex, 0x01)
                vindex := sub(vindex, 0x01)
            }{
                mstore8(add(add(buff, 0x20), mindex), byte(vindex, v))
            }
            mstore(0x40, add(buff, 0x22))
        }
        return buff;
    }


/* @notice          Convert limited uint256 value into bytes
 *  @param v         The uint256 value
 *  @return          Converted bytes array
 */

function WriteUint255(uint256 v) internal pure returns (bytes memory) {
        require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds uint255 range");
        bytes memory buff;

        assembly{
            buff := mload(0x40)
            let byteLen := 0x20
            mstore(buff, byteLen)
            for {
                let mindex := 0x00
                let vindex := 0x1f
            } lt(mindex, byteLen) {
                mindex := add(mindex, 0x01)
                vindex := sub(vindex, 0x01)
            }{
                mstore8(add(add(buff, 0x20), mindex), byte(vindex, v))
            }
            mstore(0x40, add(buff, 0x40))
        }
        return buff;
    }


/* @notice      Convert the bytes array to bytes32 type, the bytes array length must be 32
 *  @param _bs   Source bytes array
 *  @return      bytes32
 */

function bytesToBytes32(bytes memory _bs) internal pure returns (bytes32 value) {
        require(_bs.length == 32, "bytes length is not 32.");
        assembly {
            // load 32 bytes from memory starting from position _bs + 0x20 since the first 0x20 bytes stores _bs length
            value := mload(add(_bs, 0x20))
        }
    }


/* @notice      Convert bytes to uint256
 *  @param _b    Source bytes should have length of 32
 *  @return      uint256
 */

function bytesToUint256(bytes memory _bs) internal pure returns (uint256 value) {
        require(_bs.length == 32, "bytes length is not 32.");
        assembly {
            // load 32 bytes from memory starting from position _bs + 32
            value := mload(add(_bs, 0x20))
        }
        require(value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range");
    }


/* @notice      Convert uint256 to bytes
 *  @param _b    uint256 that needs to be converted
 *  @return      bytes
 */

function uint256ToBytes(uint256 _value) internal pure returns (bytes memory bs) {
        require(_value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range");
        assembly {
            // Get a location of some free memory and store it in result as
            // Solidity does for memory variables.
            bs := mload(0x40)
            // Put 0x20 at the first word, the length of bytes for uint256 value
            mstore(bs, 0x20)
            //In the next word, put value in bytes format to the next 32 bytes
            mstore(add(bs, 0x20), _value)
            // Update the free-memory pointer by padding our last write location to 32 bytes
            mstore(0x40, add(bs, 0x40))
        }
    }


/* @notice      Convert bytes to address
 *  @param _bs   Source bytes: bytes length must be 20
 *  @return      Converted address from source bytes
 */

function bytesToAddress(bytes memory _bs) internal pure returns (address addr)
    {
        require(_bs.length == 20, "bytes length does not match address");
        assembly {
            // for _bs, first word store _bs.length, second word store _bs.value
            // load 32 bytes from mem[_bs+20], convert it into Uint160, meaning we take last 20 bytes as addr (address).
            addr := mload(add(_bs, 0x14))
        }

    }


/* @notice      Convert address to bytes
 *  @param _addr Address need to be converted
 *  @return      Converted bytes from address
 */

function addressToBytes(address _addr) internal pure returns (bytes memory bs){
        assembly {
            // Get a location of some free memory and store it in result as
            // Solidity does for memory variables.
            bs := mload(0x40)
            // Put 20 (address byte length) at the first word, the length of bytes for uint256 value
            mstore(bs, 0x14)
            // logical shift left _a by 12 bytes, change _a from right-aligned to left-aligned
            mstore(add(bs, 0x20), shl(96, _addr))
            // Update the free-memory pointer by padding our last write location to 32 bytes
            mstore(0x40, add(bs, 0x40))
       }
    }


/* @notice              Check if the elements number of _signers within _keepers array is no less than _m
 *  @param _keepers      The array consists of serveral address
 *  @param _signers      Some specific addresses to be looked into
 *  @param _m            The number requirement paramter
 *  @return              True means containment, false meansdo do not contain.
 */

function containMAddresses(address[] memory _keepers, address[] memory _signers, uint _m) internal pure returns (bool){
        uint m = 0;
        for(uint i = 0; i < _signers.length; i++){
            for (uint j = 0; j < _keepers.length; j++) {
                if (_signers[i] == _keepers[j]) {
                    m++;
                    delete _keepers[j];
                }
            }
        }
        return m >= _m;
    }


/* @notice              TODO
 *  @param key
 *  @return
 */

function compressMCPubKey(bytes memory key) internal pure returns (bytes memory newkey) {
         require(key.length >= 67, "key lenggh is too short");
         newkey = slice(key, 0, 35);
         if (uint8(key[66]) % 2 == 0){
             newkey[2] = byte(0x02);
         } else {
             newkey[2] = byte(0x03);
         }
         return newkey;
    }


/**
 @notice allow approved address to deposit an asset for OHM
 @param _amount uint
 @param _token address
 @param _profit uint
 @return send_ uint
 */

function deposit( uint _amount, address _token, uint _profit ) external returns ( uint send_ ) {
        require( isReserveToken[ _token ] || isLiquidityToken[ _token ], "Not accepted" );
        IERC20( _token ).safeTransferFrom( msg.sender, address(this), _amount );

        if ( isReserveToken[ _token ] ) {
            require( isReserveDepositor[ msg.sender ], "Not approved" );
        } else {
            require( isLiquidityDepositor[ msg.sender ], "Not approved" );
        }

        uint value = valueOf(_token, _amount);
        // mint OHM needed and store amount of rewards for distribution
        send_ = value.sub( _profit );
        IERC20Mintable( OHM ).mint( msg.sender, send_ );

        totalReserves = totalReserves.add( value );
        emit ReservesUpdated( totalReserves );

        emit Deposit( _token, _amount, value );
    }


/**
 @notice allow approved address to burn OHM for reserves
 @param _amount uint
 @param _token address
 */

function withdraw( uint _amount, address _token ) external {
        require( isReserveToken[ _token ], "Not accepted" ); // Only reserves can be used for redemptions
        require( isReserveSpender[ msg.sender ] == true, "Not approved" );

        uint value = valueOf( _token, _amount );
        IOHMERC20( OHM ).burnFrom( msg.sender, value );

        totalReserves = totalReserves.sub( value );
        emit ReservesUpdated( totalReserves );

        IERC20( _token ).safeTransfer( msg.sender, _amount );

        emit Withdrawal( _token, _amount, value );
    }


/**
 @notice allow approved address to borrow reserves
 @param _amount uint
 @param _token address
 */

function incurDebt( uint _amount, address _token ) external {
        require( isDebtor[ msg.sender ], "Not approved" );
        require( isReserveToken[ _token ], "Not accepted" );

        uint value = valueOf( _token, _amount );

        uint maximumDebt = IERC20( sOHM ).balanceOf( msg.sender ); // Can only borrow against sOHM held
        uint availableDebt = maximumDebt.sub( debtorBalance[ msg.sender ] );
        require( value <= availableDebt, "Exceeds debt limit" );

        debtorBalance[ msg.sender ] = debtorBalance[ msg.sender ].add( value );
        totalDebt = totalDebt.add( value );

        totalReserves = totalReserves.sub( value );
        emit ReservesUpdated( totalReserves );

        IERC20( _token ).transfer( msg.sender, _amount );

        emit CreateDebt( msg.sender, _token, _amount, value );
    }


/**
 @notice allow approved address to repay borrowed reserves with OHM
 @param _amount uint
 */

function repayDebtWithOHM( uint _amount ) external {
        require( isDebtor[ msg.sender ], "Not approved" );

        IOHMERC20( OHM ).burnFrom( msg.sender, _amount );

        debtorBalance[ msg.sender ] = debtorBalance[ msg.sender ].sub( _amount );
        totalDebt = totalDebt.sub( _amount );

        emit RepayDebt( msg.sender, OHM, _amount, _amount );
    }


/**
 @notice allow approved address to withdraw assets
 @param _token address
 @param _amount uint
 */

function manage( address _token, uint _amount ) external {
        if( isLiquidityToken[ _token ] ) {
            require( isLiquidityManager[ msg.sender ], "Not approved" );
        } else {
            require( isReserveManager[ msg.sender ], "Not approved" );
        }

        uint value = valueOf(_token, _amount);
        require( value <= excessReserves(), "Insufficient reserves" );

        totalReserves = totalReserves.sub( value );
        emit ReservesUpdated( totalReserves );

        IERC20( _token ).safeTransfer( msg.sender, _amount );

        emit ReservesManaged( _token, _amount );
    }


/**
 @notice takes inventory of all tracked assets
 @notice always consolidate to recognized reserves before audit
 */

function auditReserves() external onlyManager() {
        uint reserves;
        for( uint i = 0; i < reserveTokens.length; i++ ) {
            reserves = reserves.add (
                valueOf( reserveTokens[ i ], IERC20( reserveTokens[ i ] ).balanceOf( address(this) ) )
            );
        }
        for( uint i = 0; i < liquidityTokens.length; i++ ) {
            reserves = reserves.add (
                valueOf( liquidityTokens[ i ], IERC20( liquidityTokens[ i ] ).balanceOf( address(this) ) )
            );
        }
        totalReserves = reserves;
        emit ReservesUpdated( reserves );
        emit ReservesAudited( reserves );
    }


/**
 @notice returns OHM valuation of asset
 @param _token address
 @param _amount uint
 @return value_ uint
 */

function valueOf( address _token, uint _amount ) public view returns ( uint value_ ) {
        if ( isReserveToken[ _token ] ) {
            // convert amount to match OHM decimals
            value_ = _amount.mul( 10 ** IERC20( OHM ).decimals() ).div( 10 ** IERC20( _token ).decimals() );
        } else if ( isLiquidityToken[ _token ] ) {
            value_ = IBondCalculator( bondCalculator[ _token ] ).valuation( _token, _amount );
        }
    }


/**
 @notice checks requirements and returns altered structs
 @param queue_ mapping( address => uint )
 @param status_ mapping( address => bool )
 @param _address address
 @return bool
 */

function requirements(
        mapping( address => uint ) storage queue_,
        mapping( address => bool ) storage status_,
        address _address
    ) internal view returns ( bool ) {
        if ( !status_[ _address ] ) {
            require( queue_[ _address ] != 0, "Must queue" );
            require( queue_[ _address ] <= block.number, "Queue not expired" );
            return true;
        } return false;
    }


/**
 @notice checks array to ensure against duplicate
 @param _list address[]
 @param _token address
 @return bool
 */

function listContains( address[] storage _list, address _token ) internal view returns ( bool ) {
        for( uint i = 0; i < _list.length; i++ ) {
            if( _list[ i ] == _token ) {
                return true;
            }
        }
        return false;
    }


/**
 	* @dev Set circuitBreak to freeze/unfreeze all handlers
 	* @param _emergency The boolean status of circuitBreaker (on/off)
 	* @return true (TODO: validate results)
 	*/

function setCircuitBreaker(bool _emergency) onlyBreaker external override returns (bool)
	{
		for (uint256 handlerID = 0; handlerID < tokenHandlerLength; handlerID++)
		{
			proxyContractInterface tokenHandler = proxyContractInterface(dataStorageInstance.getTokenHandlerAddr(handlerID));

			// use delegate call via handler proxy
			// for token handlers
			bytes memory callData = abi.encodeWithSelector(
				marketHandlerInterface
				.setCircuitBreaker.selector,
				_emergency
			);

			tokenHandler.handlerProxy(callData);
			tokenHandler.siProxy(callData);
		}

		liquidationManagerInterface liquidationManager = liquidationManagerInterface(dataStorageInstance.getLiquidationManagerAddr());
		liquidationManager.setCircuitBreaker(_emergency);
		emergency = _emergency;
		return true;
	}


/**
 	* @dev Reward the user (msg.sender) with the reward token after calculating interest.
 	* @return true (TODO: validate results)
 	*/

function interestUpdateReward() external override returns (bool)
	{
		uint256 thisBlock = block.number;
		uint256 interestRewardUpdated = dataStorageInstance.getInterestRewardUpdated();
		uint256 delta = thisBlock - interestRewardUpdated;
		if (delta == 0)
		{
			return false;
		}

		dataStorageInstance.setInterestRewardUpdated(thisBlock);
		for (uint256 handlerID; handlerID < tokenHandlerLength; handlerID++)
		{
			proxyContractInterface tokenHandler = proxyContractInterface(dataStorageInstance.getTokenHandlerAddr(handlerID));
			bytes memory data;
			(, data) = tokenHandler.handlerProxy(
				abi.encodeWithSelector(
					marketHandlerInterface
					.checkFirstAction.selector
				)
			);
		}

		/* transfer reward tokens */
		return _rewardTransfer(msg.sender, delta.mul(dataStorageInstance.getInterestUpdateRewardPerblock()));
	}


/* TODO: comment */

function _claimHandlerRewardAmount(uint256 handlerID, address payable userAddr) internal returns (uint256) {
		bytes memory data;

		proxyContractInterface tokenHandler = proxyContractInterface(dataStorageInstance.getTokenHandlerAddr(handlerID));
		tokenHandler.siProxy(
			abi.encodeWithSelector(
				SIInterface
				.updateRewardLane.selector,
				userAddr
			)
		);

		/* Claim reward for a token handler */
		(, data) = tokenHandler.siProxy(
			abi.encodeWithSelector(
				SIInterface.claimRewardAmountUser.selector,
				userAddr
			)
		);
		return abi.decode(data, (uint256));
	}


/**
 	* @dev Transfer reward tokens to a user
 	* @param userAddr The address of recipient
 	* @param _amount The amount of the reward token
 	* @return true (TODO: validate results)
 	*/

function _rewardTransfer(address payable userAddr, uint256 _amount) internal returns (bool)
	{
		IERC20 _rewardERC20 = rewardErc20Instance;

		if(address(_rewardERC20) != address(0x0)) {
			uint256 beforeBalance = _rewardERC20.balanceOf(userAddr);
			_rewardERC20.transfer(userAddr, _amount);
			require(_amount == _rewardERC20.balanceOf(userAddr).sub(beforeBalance), REWARD_TRANSFER);
			return true;
		}
	}


/**
 	* @dev Epilogue of _determineRewardParams for code-size savings
 	* @param _dataStorage interface of Manager Data Storage
 	* @param userAddr User Address for Reward token transfer
 	* @param _delta The inactive period (delta) since the last action happens
 	* @param _globalRewardPerBlock Reward per block
 	* @param _globalRewardDecrement Rewards decrement for a block
 	* @param _globalRewardTotalAmount Total amount of rewards
 	* @return true (TODO: validate results)
 	*/

function _epilogueOfDetermineRewardParams(
		managerDataStorageInterfaceForManager _dataStorage,
		address payable userAddr,
		uint256 _delta,
		uint256 _globalRewardPerBlock,
		uint256 _globalRewardDecrement,
		uint256 _globalRewardTotalAmount
	) internal returns (bool) {
    // Set the reward model parameters
    _dataStorage.setGlobalRewardPerBlock(_globalRewardPerBlock);
		_dataStorage.setGlobalRewardDecrement(_globalRewardDecrement);
		_dataStorage.setGlobalRewardTotalAmount(_globalRewardTotalAmount);

		uint256 rewardAmount = _delta.mul(_dataStorage.getRewardParamUpdateRewardPerBlock());
		/* To incentivze the update operation, the operator get paid with the
		reward token */
		_rewardTransfer(userAddr, rewardAmount);
		return true;
	}


/**
 	* @dev Update rewards paramters of token handlers.
 	* @param userAddr The address of operator
 	* @return true (TODO: validate results)
 	*/

function _calcRewardParams(address payable userAddr) internal returns (bool)
	{
		uint256 handlerLength = tokenHandlerLength;
		bytes memory data;
		uint256[] memory handlerAlphaRateBaseAsset = new uint256[](handlerLength);
		uint256[] memory chainAlphaRateBaseAsset;
		uint256 handlerID;
		uint256 alphaRateBaseGlobalAssetSum;
		for (handlerID; handlerID < handlerLength; handlerID++)
		{
			handlerAlphaRateBaseAsset[handlerID] = _getAlphaBaseAsset(handlerID);
			alphaRateBaseGlobalAssetSum = alphaRateBaseGlobalAssetSum.add(handlerAlphaRateBaseAsset[handlerID]);
		}

		chainAlphaRateBaseAsset = observer.getAlphaBaseAsset();
		handlerID = 0;
		for (;handlerID < chainAlphaRateBaseAsset.length; handlerID++) {
			alphaRateBaseGlobalAssetSum = alphaRateBaseGlobalAssetSum.add(chainAlphaRateBaseAsset[handlerID]);
		}

		handlerID = 0;
		uint256 globalRewardPerBlocks = dataStorageInstance.getGlobalRewardPerBlock();

		for (handlerID; handlerID < handlerLength; handlerID++)
		{
			proxyContractInterface tokenHandler = proxyContractInterface(dataStorageInstance.getTokenHandlerAddr(handlerID));
			(, data) = tokenHandler.siProxy(
				abi.encodeWithSelector(
					SIInterface
					.updateRewardLane.selector,
					userAddr
				)
			);

			/* Update reward parameter for the token handler */
			uint256 rewardPerBlocks = globalRewardPerBlocks
								.unifiedMul(
								handlerAlphaRateBaseAsset[handlerID]
								.unifiedDiv(alphaRateBaseGlobalAssetSum)
								);
			data = abi.encodeWithSelector(
				SIInterface.updateRewardPerBlockLogic.selector,
				rewardPerBlocks
			);
			(, data) = tokenHandler.siProxy(data);

			emit HandlerRewardUpdate(handlerID, handlerAlphaRateBaseAsset[handlerID], rewardPerBlocks);
		}

		handlerID = 0;
		for (;handlerID < chainAlphaRateBaseAsset.length; handlerID++) {
			uint256 rewardPerBlocks = chainAlphaRateBaseAsset[handlerID]
										.unifiedDiv(alphaRateBaseGlobalAssetSum)
										.unifiedMul(globalRewardPerBlocks);

			observer.setChainGlobalRewardPerblock(
				handlerID,
				rewardPerBlocks
			);
			emit ChainRewardUpdate(handlerID, chainAlphaRateBaseAsset[handlerID], rewardPerBlocks);
		}

		return true;
	}


/**
 	* @dev Calculate the alpha-score for the handler (in USD price)
 	* @param _handlerID The handler ID
 	* @return The alpha-score of the handler
 	*/

function _getAlphaBaseAsset(uint256 _handlerID) internal view returns (uint256)
	{
		bytes memory data;
		proxyContractInterface tokenHandler = proxyContractInterface(dataStorageInstance.getTokenHandlerAddr(_handlerID));

    // TODO merge call
		(, data) = tokenHandler.handlerViewProxy(
			abi.encodeWithSelector(
				marketHandlerInterface
				.getDepositTotalAmount.selector
			)
		);
		uint256 _depositAmount = abi.decode(data, (uint256));

		(, data) = tokenHandler.handlerViewProxy(
			abi.encodeWithSelector(
				marketHandlerInterface
				.getBorrowTotalAmount.selector
			)
		);
		uint256 _borrowAmount = abi.decode(data, (uint256));

		return _calcAlphaBaseAmount(
              dataStorageInstance.getAlphaRate(),
              _depositAmount,
              _borrowAmount
            )
            .unifiedMul(_getTokenHandlerPrice(_handlerID));
	}


/// @dev Calculate the wallet address for the given owner and Switcheo TradeHub address
/// @param _ownerAddress the Ethereum address which the user has control over, i.e. can sign msgs with
/// @param _swthAddress the hex value of the user's Switcheo TradeHub address
/// @param _bytecodeHash the hash of the wallet contract's bytecode
/// @return the wallet address

function getWalletAddress(
        address _ownerAddress,
        bytes calldata _swthAddress,
        bytes32 _bytecodeHash
    )
        external
        view
        returns (address)
    {
        bytes32 salt = _getSalt(
            _ownerAddress,
            _swthAddress
        );

        bytes32 data = keccak256(
            abi.encodePacked(bytes1(0xff), address(this), salt, _bytecodeHash)
        );

        return address(bytes20(data << 96));
    }


/// @dev Add a contract as an extension
/// @param _argsBz the serialized ExtensionTxArgs
/// @param _fromChainId the originating chainId
/// @return true if success

function addExtension(
        bytes calldata _argsBz,
        bytes calldata /* _fromContractAddr */,
        uint64 _fromChainId
    )
        external
        onlyManagerContract
        nonReentrant
        returns (bool)
    {
        require(_fromChainId == counterpartChainId, "Invalid chain ID");

        ExtensionTxArgs memory args = _deserializeExtensionTxArgs(_argsBz);
        address extensionAddress = Utils.bytesToAddress(args.extensionAddress);
        extensions[extensionAddress] = true;

        return true;
    }


/// @dev Remove a contract from the extensions mapping
/// @param _argsBz the serialized ExtensionTxArgs
/// @param _fromChainId the originating chainId
/// @return true if success

function removeExtension(
        bytes calldata _argsBz,
        bytes calldata /* _fromContractAddr */,
        uint64 _fromChainId
    )
        external
        onlyManagerContract
        nonReentrant
        returns (bool)
    {
        require(_fromChainId == counterpartChainId, "Invalid chain ID");

        ExtensionTxArgs memory args = _deserializeExtensionTxArgs(_argsBz);
        address extensionAddress = Utils.bytesToAddress(args.extensionAddress);
        extensions[extensionAddress] = false;

        return true;
    }


/// @dev Marks an asset as registered by mapping the asset's address to
/// the specified _fromContractAddr and assetHash on Switcheo TradeHub
/// @param _argsBz the serialized RegisterAssetTxArgs
/// @param _fromContractAddr the associated contract address on Switcheo TradeHub
/// @param _fromChainId the originating chainId
/// @return true if success

function registerAsset(
        bytes calldata _argsBz,
        bytes calldata _fromContractAddr,
        uint64 _fromChainId
    )
        external
        onlyManagerContract
        nonReentrant
        returns (bool)
    {
        require(_fromChainId == counterpartChainId, "Invalid chain ID");

        RegisterAssetTxArgs memory args = _deserializeRegisterAssetTxArgs(_argsBz);
        _markAssetAsRegistered(
            Utils.bytesToAddress(args.nativeAssetHash),
            _fromContractAddr,
            args.assetHash
        );

        return true;
    }


/// @dev Performs a deposit from a Wallet contract
/// @param _walletAddress address of the wallet contract, the wallet contract
/// does not receive ETH in this call, but _walletAddress still needs to be payable
/// since the wallet contract can receive ETH, there would be compile errors otherwise
/// @param _assetHash the asset to deposit
/// @param _targetProxyHash the associated proxy hash on Switcheo TradeHub
/// @param _toAssetHash the associated asset hash on Switcheo TradeHub
/// @param _feeAddress the hex version of the Switcheo TradeHub address to send the fee to
/// @param _values[0]: amount, the number of tokens to deposit
/// @param _values[1]: feeAmount, the number of tokens to be used as fees
/// @param _values[2]: nonce, to prevent replay attacks
/// @param _values[3]: callAmount, some tokens may burn an amount before transfer
/// so we allow a callAmount to support these tokens
/// @param _v: the v value of the wallet owner's signature
/// @param _rs: the r, s values of the wallet owner's signature

function lockFromWallet(
        address payable _walletAddress,
        address _assetHash,
        bytes calldata _targetProxyHash,
        bytes calldata _toAssetHash,
        bytes calldata _feeAddress,
        uint256[] calldata _values,
        uint8 _v,
        bytes32[] calldata _rs
    )
        external
        nonReentrant
        returns (bool)
    {
        require(wallets[_walletAddress], "Invalid wallet address");

        Wallet wallet = Wallet(_walletAddress);
        _validateLockFromWallet(
            wallet.owner(),
            _assetHash,
            _targetProxyHash,
            _toAssetHash,
            _feeAddress,
            _values,
            _v,
            _rs
        );

        // it is very important that this function validates the success of a transfer correctly
        // since, once this line is passed, the deposit is assumed to be successful
        // which will eventually result in the specified amount of tokens being minted for the
        // wallet.swthAddress on Switcheo TradeHub
        _transferInFromWallet(_walletAddress, _assetHash, _values[0], _values[3]);

        _lock(
            _assetHash,
            _targetProxyHash,
            _toAssetHash,
            wallet.swthAddress(),
            _values[0],
            _values[1],
            _feeAddress
        );

        return true;
    }


/// @dev Performs a deposit
/// @param _assetHash the asset to deposit
/// @param _targetProxyHash the associated proxy hash on Switcheo TradeHub
/// @param _toAddress the hex version of the Switcheo TradeHub address to deposit to
/// @param _toAssetHash the associated asset hash on Switcheo TradeHub
/// @param _feeAddress the hex version of the Switcheo TradeHub address to send the fee to
/// @param _values[0]: amount, the number of tokens to deposit
/// @param _values[1]: feeAmount, the number of tokens to be used as fees
/// @param _values[2]: callAmount, some tokens may burn an amount before transfer
/// so we allow a callAmount to support these tokens

function lock(
        address _assetHash,
        bytes calldata _targetProxyHash,
        bytes calldata _toAddress,
        bytes calldata _toAssetHash,
        bytes calldata _feeAddress,
        uint256[] calldata _values
    )
        external
        payable
        nonReentrant
        returns (bool)
    {

        // it is very important that this function validates the success of a transfer correctly
        // since, once this line is passed, the deposit is assumed to be successful
        // which will eventually result in the specified amount of tokens being minted for the
        // _toAddress on Switcheo TradeHub
        _transferIn(_assetHash, _values[0], _values[2]);

        _lock(
            _assetHash,
            _targetProxyHash,
            _toAssetHash,
            _toAddress,
            _values[0],
            _values[1],
            _feeAddress
        );

        return true;
    }


/// @dev Performs a withdrawal that was initiated on Switcheo TradeHub
/// @param _argsBz the serialized TransferTxArgs
/// @param _fromContractAddr the associated contract address on Switcheo TradeHub
/// @param _fromChainId the originating chainId
/// @return true if success

function unlock(
        bytes calldata _argsBz,
        bytes calldata _fromContractAddr,
        uint64 _fromChainId
    )
        external
        onlyManagerContract
        nonReentrant
        returns (bool)
    {
        require(_fromChainId == counterpartChainId, "Invalid chain ID");

        TransferTxArgs memory args = _deserializeTransferTxArgs(_argsBz);
        require(args.fromAssetHash.length > 0, "Invalid fromAssetHash");
        require(args.toAssetHash.length == 20, "Invalid toAssetHash");

        address toAssetHash = Utils.bytesToAddress(args.toAssetHash);
        address toAddress = Utils.bytesToAddress(args.toAddress);

        _validateAssetRegistration(toAssetHash, _fromContractAddr, args.fromAssetHash);
        _transferOut(toAddress, toAssetHash, args.amount);

        emit UnlockEvent(toAssetHash, toAddress, args.amount, _argsBz);
        return true;
    }


/// @dev Performs a transfer of funds, this is only callable by approved extension contracts
/// the `nonReentrant` guard is intentionally not added to this function, to allow for more flexibility.
/// The calling contract should be secure and have its own `nonReentrant` guard as needed.
/// @param _receivingAddress the address to transfer to
/// @param _assetHash the asset to transfer
/// @param _amount the amount to transfer
/// @return true if success

function extensionTransfer(
        address _receivingAddress,
        address _assetHash,
        uint256 _amount
    )
        external
        returns (bool)
    {
        require(
            extensions[msg.sender] == true,
            "Invalid extension"
        );

        if (_assetHash == ETH_ASSET_HASH) {
            // we use `call` here since the _receivingAddress could be a contract
            // see https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/
            // for more info
            (bool success,  ) = _receivingAddress.call{value: _amount}("");
            require(success, "Transfer failed");
            return true;
        }

        ERC20 token = ERC20(_assetHash);
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(
                token.approve.selector,
                _receivingAddress,
                _amount
            )
        );

        return true;
    }


/// @dev Marks an asset as registered by associating it to a specified Switcheo TradeHub proxy and asset hash
/// @param _assetHash the address of the asset to mark
/// @param _proxyAddress the associated proxy address on Switcheo TradeHub
/// @param _toAssetHash the associated asset hash on Switcheo TradeHub

function _markAssetAsRegistered(
        address _assetHash,
        bytes memory _proxyAddress,
        bytes memory _toAssetHash
    )
        private
    {
        require(_proxyAddress.length == 20, "Invalid proxyAddress");
        require(
            registry[_assetHash] == bytes32(0),
            "Asset already registered"
        );

        bytes32 value = keccak256(abi.encodePacked(
            _proxyAddress,
            _toAssetHash
        ));

        registry[_assetHash] = value;
    }


/// @dev Validates that an asset's registration matches the given params
/// @param _assetHash the address of the asset to check
/// @param _proxyAddress the expected proxy address on Switcheo TradeHub
/// @param _toAssetHash the expected asset hash on Switcheo TradeHub

function _validateAssetRegistration(
        address _assetHash,
        bytes memory _proxyAddress,
        bytes memory _toAssetHash
    )
        private
        view
    {
        require(_proxyAddress.length == 20, "Invalid proxyAddress");
        bytes32 value = keccak256(abi.encodePacked(
            _proxyAddress,
            _toAssetHash
        ));
        require(registry[_assetHash] == value, "Asset not registered");
    }


/// @dev validates the asset registration and calls the CCM contract

function _lock(
        address _fromAssetHash,
        bytes memory _targetProxyHash,
        bytes memory _toAssetHash,
        bytes memory _toAddress,
        uint256 _amount,
        uint256 _feeAmount,
        bytes memory _feeAddress
    )
        private
    {
        require(_targetProxyHash.length == 20, "Invalid targetProxyHash");
        require(_toAssetHash.length > 0, "Empty toAssetHash");
        require(_toAddress.length > 0, "Empty toAddress");
        require(_amount > 0, "Amount must be more than zero");
        require(_feeAmount < _amount, "Fee amount cannot be greater than amount");

        _validateAssetRegistration(_fromAssetHash, _targetProxyHash, _toAssetHash);

        TransferTxArgs memory txArgs = TransferTxArgs({
            fromAssetHash: Utils.addressToBytes(_fromAssetHash),
            toAssetHash: _toAssetHash,
            toAddress: _toAddress,
            amount: _amount,
            feeAmount: _feeAmount,
            feeAddress: _feeAddress,
            fromAddress: abi.encodePacked(msg.sender),
            nonce: _getNextNonce()
        });

        bytes memory txData = _serializeTransferTxArgs(txArgs);
        CCM ccm = _getCcm();
        require(
            ccm.crossChain(counterpartChainId, _targetProxyHash, "unlock", txData),
            "EthCrossChainManager crossChain executed error!"
        );

        emit LockEvent(_fromAssetHash, msg.sender, counterpartChainId, _toAssetHash, _toAddress, txData);
    }


/// @dev validate the signature for lockFromWallet

function _validateLockFromWallet(
        address _walletOwner,
        address _assetHash,
        bytes memory _targetProxyHash,
        bytes memory _toAssetHash,
        bytes memory _feeAddress,
        uint256[] memory _values,
        uint8 _v,
        bytes32[] memory _rs
    )
        private
    {
        bytes32 message = keccak256(abi.encodePacked(
            "sendTokens",
            _assetHash,
            _targetProxyHash,
            _toAssetHash,
            _feeAddress,
            _values[0],
            _values[1],
            _values[2]
        ));

        require(seenMessages[message] == false, "Message already seen");
        seenMessages[message] = true;
        _validateSignature(message, _walletOwner, _v, _rs[0], _rs[1]);
    }


/// @dev transfers funds from a Wallet contract into this contract
/// the difference between this contract's before and after balance must equal _amount
/// this is assumed to be sufficient in ensuring that the expected amount
/// of funds were transferred in

function _transferInFromWallet(
        address payable _walletAddress,
        address _assetHash,
        uint256 _amount,
        uint256 _callAmount
    )
        private
    {
        Wallet wallet = Wallet(_walletAddress);
        if (_assetHash == ETH_ASSET_HASH) {
            uint256 before = address(this).balance;

            wallet.sendETHToCreator(_callAmount);

            uint256 transferred = address(this).balance.sub(before);
            require(transferred == _amount, "ETH transferred does not match the expected amount");
            return;
        }

        ERC20 token = ERC20(_assetHash);
        uint256 before = token.balanceOf(address(this));

        wallet.sendERC20ToCreator(_assetHash, _callAmount);

        uint256 transferred = token.balanceOf(address(this)).sub(before);
        require(transferred == _amount, "Tokens transferred does not match the expected amount");
    }


/// @dev transfers funds from an address into this contract
/// for ETH transfers, we only check that msg.value == _amount, and _callAmount is ignored
/// for token transfers, the difference between this contract's before and after balance must equal _amount
/// these checks are assumed to be sufficient in ensuring that the expected amount
/// of funds were transferred in

function _transferIn(
        address _assetHash,
        uint256 _amount,
        uint256 _callAmount
    )
        private
    {
        if (_assetHash == ETH_ASSET_HASH) {
            require(msg.value == _amount, "ETH transferred does not match the expected amount");
            return;
        }

        ERC20 token = ERC20(_assetHash);
        uint256 before = token.balanceOf(address(this));
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(
                token.transferFrom.selector,
                msg.sender,
                address(this),
                _callAmount
            )
        );
        uint256 transferred = token.balanceOf(address(this)).sub(before);
        require(transferred == _amount, "Tokens transferred does not match the expected amount");
    }


/// @dev transfers funds from this contract to the _toAddress

function _transferOut(
        address _toAddress,
        address _assetHash,
        uint256 _amount
    )
        private
    {
        if (_assetHash == ETH_ASSET_HASH) {
            // we use `call` here since the _receivingAddress could be a contract
            // see https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/
            // for more info
            (bool success,  ) = _toAddress.call{value: _amount}("");
            require(success, "Transfer failed");
            return;
        }

        ERC20 token = ERC20(_assetHash);
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(
                token.transfer.selector,
                _toAddress,
                _amount
            )
        );
    }


/// @dev validates a signature against the specified user address

function _validateSignature(
        bytes32 _message,
        address _user,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        private
        pure
    {
        bytes32 prefixedMessage = keccak256(abi.encodePacked(
            "\x19Ethereum Signed Message:\n32",
            _message
        ));

        require(
            _user == ecrecover(prefixedMessage, _v, _r, _s),
            "Invalid signature"
        );
    }


/**
 * @dev Mint function
 */

function mint(
        uint8 _quantity,
        uint256 _fromTimestamp,
        uint256 _toTimestamp,
        uint8 _maxQuantity,
        bytes calldata _signature
    ) external payable callerIsUser {
        bytes32 messageHash = generateMessageHash(msg.sender, _fromTimestamp, _toTimestamp, _maxQuantity);
        address recoveredWallet = ECDSA.recover(messageHash, _signature);

        require(recoveredWallet == signerAddress, "Invalid signature for the caller");
        require(block.timestamp >= _fromTimestamp, "Too early to mint");
        require(block.timestamp <= _toTimestamp, "The signature has expired");

        QueensAndKingsAvatars qakContract = QueensAndKingsAvatars(avatarContractAddress);
        uint16 tmpTotalSupply = qakContract.totalSupply();

        uint256 tokensLeft = totalAvatars - tmpTotalSupply;
        require(tokensLeft > 0, "No tokens left to be minted");

        if (_quantity + mintsPerUser[msg.sender] > _maxQuantity) {
            _quantity = _maxQuantity - mintsPerUser[msg.sender];
        }

        if (_quantity > tokensLeft) {
            _quantity = uint8(tokensLeft);
        }

        uint256 totalMintPrice = mintPrice * _quantity;
        require(msg.value >= totalMintPrice, "Not enough Ether provided to mint");

        if (msg.value > totalMintPrice) {
            payable(msg.sender).transfer(msg.value - totalMintPrice);
        }

        require(_quantity != 0, "Address mint limit reached");

        mintsPerUser[msg.sender] += _quantity;

        for (uint16 i; i < _quantity; i++) {
            uint16 _tokenId = _getTokenToBeMinted(tmpTotalSupply);
            qakContract.mint(_tokenId, msg.sender);
            tmpTotalSupply++;
        }
    }


/**
 * @dev mint to address
 */

function mintTo(address[] memory _addresses) external {
        require(msg.sender == sAddress, "Caller is not allowed to mint");
        require(_addresses.length > 0, "At least one token should be minted");
        require(sMintedTokens + _addresses.length <= sMintLimit, "Mint limit reached");

        QueensAndKingsAvatars qakContract = QueensAndKingsAvatars(avatarContractAddress);
        uint16 tmpTotalSupply = qakContract.totalSupply();

        sMintedTokens += uint16(_addresses.length);
        for (uint256 i; i < _addresses.length; i++) {
            qakContract.mint(_getTokenToBeMinted(tmpTotalSupply), _addresses[i]);
            tmpTotalSupply++;
        }
    }


/**
 * @dev Returns a random available token to be minted
 */

function _getTokenToBeMinted(uint16 _totalMintedTokens) private returns (uint16) {
        uint16 maxIndex = totalAvatars + sMintLimit - _totalMintedTokens;
        uint16 random = _getRandomNumber(maxIndex, _totalMintedTokens);

        uint16 tokenId = tokenMatrix[random];
        if (tokenMatrix[random] == 0) {
            tokenId = random;
        }

        tokenMatrix[maxIndex - 1] == 0 ? tokenMatrix[random] = maxIndex - 1 : tokenMatrix[random] = tokenMatrix[
            maxIndex - 1
        ];

        // IDs start from 1 instead of 0
        return tokenId + 1;
    }


/**
 * @dev Generates a pseudo-random number.
 */

function _getRandomNumber(uint16 _upper, uint16 _totalMintedTokens) private view returns (uint16) {
        uint16 random = uint16(
            uint256(
                keccak256(
                    abi.encodePacked(
                        _totalMintedTokens,
                        blockhash(block.number - 1),
                        block.coinbase,
                        block.difficulty,
                        msg.sender
                    )
                )
            )
        );

        return (random % _upper);
    }


/**
 * ROYALTY FUNCTIONS
 */

function getRoyalties(uint256) external view returns (address payable[] memory recipients, uint256[] memory bps) {
        if (_royaltyRecipient != address(0x0)) {
            recipients = new address payable[](1);
            recipients[0] = _royaltyRecipient;
            bps = new uint256[](1);
            bps[0] = _royaltyBps;
        }
        return (recipients, bps);
    }


/**
 * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
 *
 * Emits an {Upgraded} event.
 */

function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
        address oldImplementation = _getImplementation();

        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0 || forceCall) {
            Address.functionDelegateCall(newImplementation, data);
        }

        // Perform rollback test if not already in progress
        StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
        if (!rollbackTesting.value) {
            // Trigger rollback using upgradeTo from the new implementation
            rollbackTesting.value = true;
            Address.functionDelegateCall(
                newImplementation,
                abi.encodeWithSignature(
                    "upgradeTo(address)",
                    oldImplementation
                )
            );
            rollbackTesting.value = false;
            // Check rollback was effective
            require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
            // Finally reset to the new implementation and log the upgrade
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
        }
    }


/**
 * @dev update the default token Uri.
 *
 * @param _before token uri before part.
 * @param _after token uri after part.
 *
 * Returns
 * - bool
 */

function updateDefaultUri(string memory _before, string memory _after) external onlyOwner returns (bool){
        beforeUri = _before; // update the before uri for Badbeat
        afterUri = _after; // update the after uri for Badbeat
        return true;
    }


/**
 * @dev creating new NFT on immutable x.
 *
 * @param tokens  token details.
 * @param signature signature to verify.
 *
 * Returns
 * - bool
 *
 * Emits a {TransferEth} event.
 */

function buyNFT(tokenDetails calldata tokens, bytes calldata signature) public payable returns(bool){
        uint256 value = msg.value;
        require(buyOnOff == true, "NFT minting is stopped");

        bool status = SignatureCheckerUpgradeable.isValidSignatureNow(owner(), tokens.signKey, signature);
        require(status == true, "You cannot mint NFT or a not whitelisted address");

        uint256 payableValue1 = uint256(30) * (msg.value)/ 100;
        payable(payoutAddress1).transfer(payableValue1);

        uint256 payableValue2 = msg.value - payableValue1;
        payable(payoutAddress2).transfer(payableValue2);
        emit TransferEth(msg.sender,payoutAddress1, payableValue1, payoutAddress2,
        payableValue2, tokens.noOfTokens,tokens.catId,value);

        return true;
    }


/**
 * @dev mintinf function by only IMX.
 *
 * @param user  msg.sender.
 * @param quantity number of token amount.
 * @param mintingBlob bytes32
 *
 * Returns
 * - bool
 *
 * Emits a {AssetMinted} event.
 */

function mintFor(
        address user,
        uint256 quantity,
        bytes calldata mintingBlob
    ) external onlyIMX {
        require(quantity == 1, "Mintable: invalid quantity");
        (uint256 id, bytes memory blueprint) = Minting.split(mintingBlob);
        _mintFor(user, id, blueprint);
        blueprints[id] = blueprint;
        emit AssetMinted(user, id, blueprint);
    }


/**
 ============================================
 Internal Functions
 @dev functions that can be called by inside contract
 ============================================
 */

function calculateRewards(uint128 _mintingAmount, uint128 remainingBoost, uint128 remainingSupply) internal pure returns (uint128, uint128) {
        uint128 boostRewards;
        uint128 finalMintingReward;
        uint128 removedBoost;

        unchecked {
            if (remainingBoost > _mintingAmount * 2) {
                boostRewards = _mintingAmount * 2;
                removedBoost = boostRewards;
            } else if (remainingBoost > 0) {
                boostRewards = _mintingAmount + remainingBoost;
                removedBoost = remainingBoost;
            } else {
                boostRewards = _mintingAmount;
                removedBoost = 0;
            }

            if (remainingSupply < _mintingAmount) {
                finalMintingReward = remainingSupply;
            } else {
                if (remainingSupply < boostRewards) {
                    finalMintingReward = remainingSupply;
                } else {
                    finalMintingReward = boostRewards;
                }
            }
        }

        return (finalMintingReward, removedBoost);
    }


/**
 * @dev Constructor function - Set the inbest token address
 * @param _startTime The time when InbestDistribution goes live
 * @param _companyWallet The wallet to allocate Company tokens
 */

function InbestDistribution(uint256 _startTime, address _companyWallet) public {
    require(_companyWallet != address(0));
    require(_startTime >= now);
    require(AVAILABLE_TOTAL_SUPPLY == AVAILABLE_PRESALE_SUPPLY.add(AVAILABLE_COMPANY_SUPPLY));
    startTime = _startTime;
    companyWallet = _companyWallet;
    IBST = new InbestToken();
    require(AVAILABLE_TOTAL_SUPPLY == IBST.totalSupply()); //To verify that totalSupply is correct

    // Allocate Company Supply
    uint256 tokensToAllocate = AVAILABLE_COMPANY_SUPPLY;
    AVAILABLE_COMPANY_SUPPLY = 0;
    allocations[companyWallet] = Allocation(uint8(AllocationType.COMPANY), 0, 0, tokensToAllocate, 0);
    AVAILABLE_TOTAL_SUPPLY = AVAILABLE_TOTAL_SUPPLY.sub(tokensToAllocate);
    LogNewAllocation(companyWallet, AllocationType.COMPANY, tokensToAllocate, grandTotalAllocated());
  }


/**
 * @dev Allow the owner or admins of the contract to assign a new allocation
 * @param _recipient The recipient of the allocation
 * @param _totalAllocated The total amount of IBST tokens available to the receipient (after vesting and cliff)
 */

function setAllocation (address _recipient, uint256 _totalAllocated) public onlyOwnerOrAdmin {
    require(_recipient != address(0));
    require(startTime > now); //Allocations are allowed only before starTime
    require(AVAILABLE_PRESALE_SUPPLY >= _totalAllocated); //Current allocation must be less than remaining presale supply
    require(allocations[_recipient].totalAllocated == 0 && _totalAllocated > 0); // Must be the first and only allocation for this recipient
    require(_recipient != companyWallet); // Receipient of presale allocation can't be company wallet

    // Allocate
    AVAILABLE_PRESALE_SUPPLY = AVAILABLE_PRESALE_SUPPLY.sub(_totalAllocated);
    allocations[_recipient] = Allocation(uint8(AllocationType.PRESALE), startTime.add(CLIFF), startTime.add(CLIFF).add(VESTING), _totalAllocated, 0);
    AVAILABLE_TOTAL_SUPPLY = AVAILABLE_TOTAL_SUPPLY.sub(_totalAllocated);
    LogNewAllocation(_recipient, AllocationType.PRESALE, _totalAllocated, grandTotalAllocated());
  }


/**
 * @dev Transfer a recipients available allocation to their address
 * @param _recipient The address to withdraw tokens for
 */

function transferTokens (address _recipient) public {
   require(_recipient != address(0));
   require(now >= startTime); //Tokens can't be transfered until start date
   require(_recipient != companyWallet); // Tokens allocated to COMPANY can't be withdrawn.
   require(now >= allocations[_recipient].endCliff); // Cliff period must be ended
   // Receipient can't claim more IBST tokens than allocated
   require(allocations[_recipient].amountClaimed < allocations[_recipient].totalAllocated);

   uint256 newAmountClaimed;
   if (allocations[_recipient].endVesting > now) {
     // Transfer available amount based on vesting schedule and allocation
     newAmountClaimed = allocations[_recipient].totalAllocated.mul(now.sub(allocations[_recipient].endCliff)).div(allocations[_recipient].endVesting.sub(allocations[_recipient].endCliff));
   } else {
     // Transfer total allocated (minus previously claimed tokens)
     newAmountClaimed = allocations[_recipient].totalAllocated;
   }

   //Transfer
   uint256 tokensToTransfer = newAmountClaimed.sub(allocations[_recipient].amountClaimed);
   allocations[_recipient].amountClaimed = newAmountClaimed;
   require(IBST.transfer(_recipient, tokensToTransfer));
   grandTotalClaimed = grandTotalClaimed.add(tokensToTransfer);
   LogIBSTClaimed(_recipient, allocations[_recipient].allocationType, tokensToTransfer, newAmountClaimed, grandTotalClaimed);
 }


/**
 * @dev Transfer IBST tokens from Company allocation to reicipient address - Only owner and admins can execute
 * @param _recipient The address to transfer tokens for
 * @param _tokensToTransfer The amount of IBST tokens to transfer
 */

function manualContribution(address _recipient, uint256 _tokensToTransfer) public onlyOwnerOrAdmin {
   require(_recipient != address(0));
   require(_recipient != companyWallet); // Company can't withdraw tokens for itself
   require(_tokensToTransfer > 0); // The amount must be valid
   require(now >= startTime); // Tokens cant't be transfered until start date
   //Company can't trasnfer more tokens than allocated
   require(allocations[companyWallet].amountClaimed.add(_tokensToTransfer) <= allocations[companyWallet].totalAllocated);

   //Transfer
   allocations[companyWallet].amountClaimed = allocations[companyWallet].amountClaimed.add(_tokensToTransfer);
   require(IBST.transfer(_recipient, _tokensToTransfer));
   grandTotalClaimed = grandTotalClaimed.add(_tokensToTransfer);
   LogIBSTClaimed(_recipient, uint8(AllocationType.COMPANY), _tokensToTransfer, allocations[companyWallet].amountClaimed, grandTotalClaimed);
 }


/**
 * @dev Mint edition to a wallet
 * @param _to wallet receiving the edition(s).
 * @param _mintAmount number of editions to mint.
 */

function mint(address _to, uint256 _mintAmount) public payable {
        require(!paused, "Minting is Paused.");
        require(_mintAmount <= maxMintAmount, "Only 10 DieHardDinos are Allowed Per Transaction");
        require(
            tokenId + _mintAmount <= maxSupply,
            "Dinosphere is at Max Capacity"
        );

        if(freeMint[_to] < _mintAmount)
            require(
                msg.value >= cost * _mintAmount,
                "Uh Oh Not Enough ETH Paid"
            );
        else
            freeMint[_to] -= _mintAmount;

        for (uint256 i = 0; i < _mintAmount; i++) {
            tokenId++;
            _mint(_to, tokenId, 1, "");
            totalSupply++;
        }
    }


/// @dev Allows anyone to execute a confirmed transaction or ether withdraws until daily limit is reached.
/// @param transactionId Transaction ID.

function executeTransaction(uint transactionId)
        public
        ownerExists(msg.sender)
        confirmed(transactionId, msg.sender)
        notExecuted(transactionId)
    {
        Transaction storage txn = transactions[transactionId];
        bool _confirmed = isConfirmed(transactionId);
        if (_confirmed || txn.data.length == 0 && isUnderLimit(txn.value)) {
            txn.executed = true;
            if (!_confirmed)
                spentToday += txn.value;
            if (txn.destination.call.value(txn.value)(txn.data))
                Execution(transactionId);
            else {
                ExecutionFailure(transactionId);
                txn.executed = false;
                if (!_confirmed)
                    spentToday -= txn.value;
            }
        }
    }


/// @dev Returns if amount is within daily limit and resets spentToday after one day.
/// @param amount Amount to withdraw.
/// @return Returns if amount is under daily limit.

function isUnderLimit(uint amount)
        internal
        returns (bool)
    {
        if (now > lastDay + 24 hours) {
            lastDay = now;
            spentToday = 0;
        }
        if (spentToday + amount > dailyLimit || spentToday + amount < spentToday)
            return false;
        return true;
    }


/**
 * @notice Modify auction parameters
 * @param parameter The name of the parameter modified
 * @param data New value for the parameter
 */

function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
        require(data > 0, "StakedTokenAuctionHouse/null-data");
        require(contractEnabled == 1, "StakedTokenAuctionHouse/contract-not-enabled");

        if (parameter == "bidIncrease") {
          require(data > ONE, "StakedTokenAuctionHouse/invalid-bid-increase");
          bidIncrease = data;
        }
        else if (parameter == "bidDuration") bidDuration = uint48(data);
        else if (parameter == "totalAuctionLength") totalAuctionLength = uint48(data);
        else if (parameter == "minBidDecrease") {
          require(data < ONE, "StakedTokenAuctionHouse/invalid-min-bid-decrease");
          minBidDecrease = data;
        }
        else if (parameter == "minBid") {
          minBid = data;
        }
        else revert("StakedTokenAuctionHouse/modify-unrecognized-param");
        emit ModifyParameters(parameter, data);
    }


/**
 * @notice Modify an address parameter
 * @param parameter The name of the oracle contract modified
 * @param addr New contract address
 */

function modifyParameters(bytes32 parameter, address addr) external isAuthorized {
        require(addr != address(0), "StakedTokenAuctionHouse/null-addr");
        require(contractEnabled == 1, "StakedTokenAuctionHouse/contract-not-enabled");
        if (parameter == "accountingEngine") accountingEngine = addr;
        else if (parameter == "tokenBurner") tokenBurner = addr;
        else revert("StakedTokenAuctionHouse/modify-unrecognized-param");
        emit ModifyParameters(parameter, addr);
    }


/**
 * @notice Start a new staked token auction
 * @param amountToSell Amount of staked tokens to sell (wad)
 */

function startAuction(
        uint256 amountToSell,
        uint256 systemCoinsRequested
    ) external isAuthorized returns (uint256 id) {
        require(contractEnabled == 1, "StakedTokenAuctionHouse/contract-not-enabled");
        require(auctionsStarted < uint256(-1), "StakedTokenAuctionHouse/overflow");
        require(accountingEngine != address(0), "StakedTokenAuctionHouse/null-accounting-engine");
        require(both(amountToSell > 0, systemCoinsRequested > 0), "StakedTokenAuctionHouse/null-amounts");
        require(systemCoinsRequested <= uint256(-1) / ONE, "StakedTokenAuctionHouse/large-sys-coin-request");

        id = ++auctionsStarted;

        bids[id].amountToSell     = amountToSell;
        bids[id].bidAmount        = systemCoinsRequested;
        bids[id].highBidder       = address(0);
        bids[id].auctionDeadline  = addUint48(uint48(now), totalAuctionLength);

        activeStakedTokenAuctions = addUint256(activeStakedTokenAuctions, 1);

        // get staked tokens
        require(stakedToken.transferFrom(msg.sender, address(this), amountToSell), "StakedTokenAuctionHouse/cannot-transfer-staked-tokens");

        emit StartAuction(
          id, auctionsStarted, amountToSell, systemCoinsRequested, accountingEngine, bids[id].auctionDeadline, activeStakedTokenAuctions
        );
    }


/**
 * @notice Restart an auction if no bids were submitted for it
 * @param id ID of the auction to restart
 */

function restartAuction(uint256 id) external {
        require(id <= auctionsStarted, "StakedTokenAuctionHouse/auction-never-started");
        require(bids[id].auctionDeadline < now, "StakedTokenAuctionHouse/not-finished");
        require(bids[id].bidExpiry == 0, "StakedTokenAuctionHouse/bid-already-placed");

        uint256 newMinBid        = multiply(minBidDecrease, bids[id].bidAmount) / ONE;
        newMinBid                = (newMinBid < minBid) ? minBid : newMinBid;

        bids[id].bidAmount       = newMinBid;
        bids[id].auctionDeadline = addUint48(uint48(now), totalAuctionLength);

        emit RestartAuction(id, newMinBid, bids[id].auctionDeadline);
    }


/**
 * @notice Submit a higher system coin bid for the same amount of staked tokens
 * @param id ID of the auction you want to submit the bid for
 * @param amountToBuy Amount of staked tokens to buy (wad)
 * @param bid New bid submitted (rad)
 */

function increaseBidSize(uint256 id, uint256 amountToBuy, uint256 bid) external {
        require(contractEnabled == 1, "StakedTokenAuctionHouse/contract-not-enabled");
        require(bids[id].bidExpiry > now || bids[id].bidExpiry == 0, "StakedTokenAuctionHouse/bid-already-expired");
        require(bids[id].auctionDeadline > now, "StakedTokenAuctionHouse/auction-already-expired");

        require(amountToBuy == bids[id].amountToSell, "StakedTokenAuctionHouse/not-matching-amount-bought");
        require(bid > bids[id].bidAmount, "StakedTokenAuctionHouse/bid-not-higher");
        require(multiply(bid, ONE) > multiply(bidIncrease, bids[id].bidAmount), "StakedTokenAuctionHouse/insufficient-increase");

        if (bids[id].highBidder == msg.sender) {
            safeEngine.transferInternalCoins(msg.sender, address(this), subtract(bid, bids[id].bidAmount));
        } else {
            safeEngine.transferInternalCoins(msg.sender, address(this), bid);
            if (bids[id].highBidder != address(0)) // not first bid
                safeEngine.transferInternalCoins(address(this), bids[id].highBidder, bids[id].bidAmount);

            bids[id].highBidder = msg.sender;
        }

        bids[id].bidAmount  = bid;
        bids[id].bidExpiry  = addUint48(uint48(now), bidDuration);

        emit IncreaseBidSize(id, msg.sender, amountToBuy, bid, bids[id].bidExpiry);
    }


/**
 * @notice Settle/finish an auction
 * @param id ID of the auction to settle
 */

function settleAuction(uint256 id) external {
        require(contractEnabled == 1, "StakedTokenAuctionHouse/not-live");
        require(both(bids[id].bidExpiry != 0, either(bids[id].bidExpiry < now, bids[id].auctionDeadline < now)), "StakedTokenAuctionHouse/not-finished");

        // get the bid, the amount to sell and the high bidder
        uint256 bid          = bids[id].bidAmount;
        uint256 amountToSell = bids[id].amountToSell;
        address highBidder   = bids[id].highBidder;

        // clear storage
        activeStakedTokenAuctions = subtract(activeStakedTokenAuctions, 1);
        delete bids[id];

        // transfer the surplus to the accounting engine
        safeEngine.transferInternalCoins(address(this), accountingEngine, bid);

        // clear as much bad debt as possible
        uint256 totalOnAuctionDebt = AccountingEngineLike(accountingEngine).totalOnAuctionDebt();
        AccountingEngineLike(accountingEngine).cancelAuctionedDebtWithSurplus(minimum(bid, totalOnAuctionDebt));

        // transfer staked tokens to the high bidder
        require(stakedToken.transfer(highBidder, amountToSell), "StakedTokenAuctionHouse/failed-transfer");

        emit SettleAuction(id, activeStakedTokenAuctions);
    }


/**
 * @notice Terminate an auction prematurely
 * @param id ID of the auction to terminate
 */

function terminateAuctionPrematurely(uint256 id) external {
        require(contractEnabled == 0, "StakedTokenAuctionHouse/contract-still-enabled");
        require(bids[id].highBidder != address(0), "StakedTokenAuctionHouse/high-bidder-not-set");

        // decrease amount of active auctions
        activeStakedTokenAuctions = subtract(activeStakedTokenAuctions, 1);

        // send the system coin bid back to the high bidder in case there was at least one bid
        safeEngine.transferInternalCoins(address(this), bids[id].highBidder, bids[id].bidAmount);

        // send the staked tokens to the token burner
        require(stakedToken.transfer(tokenBurner, bids[id].amountToSell), "StakedTokenAuctionHouse/failed-transfer");

        emit TerminateAuctionPrematurely(id, msg.sender, bids[id].highBidder, bids[id].bidAmount, activeStakedTokenAuctions);
        delete bids[id];
    }


// ------------------------------------------------------------------------
// 1 YAR Tokens per 1 ETH
// ------------------------------------------------------------------------

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


/**
 * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
 * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
 */

function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );

        emit Transfer(address(0), to, tokenId);

    }


/**
 * @param A The input array to search
 * @param a The address to remove
 */

function removeStorage(address[] storage A, address a)
        internal
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("Address not in array.");
        } else {
            uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
            if (index != lastIndex) { A[index] = A[lastIndex]; }
            A.pop();
        }
    }


/**
 * Returns the combination of the two arrays
 * @param A The first array
 * @param B The second array
 * @return Returns A extended by B
 */

function extend(address[] memory A, address[] memory B) internal pure returns (address[] memory) {
        uint256 aLength = A.length;
        uint256 bLength = B.length;
        address[] memory newAddresses = new address[](aLength + bLength);
        for (uint256 i = 0; i < aLength; i++) {
            newAddresses[i] = A[i];
        }
        for (uint256 j = 0; j < bLength; j++) {
            newAddresses[aLength + j] = B[j];
        }
        return newAddresses;
    }


/**
 * Instructs the SetToken to transfer the ERC20 token to a recipient.
 * The new SetToken balance must equal the existing balance less the quantity transferred
 *
 * @param _setToken        SetToken instance to invoke
 * @param _token           ERC20 token to transfer
 * @param _to              The recipient account
 * @param _quantity        The quantity to transfer
 */

function strictInvokeTransfer(
        ISetToken _setToken,
        address _token,
        address _to,
        uint256 _quantity
    )
        internal
    {
        if (_quantity > 0) {
            // Retrieve current balance of token for the SetToken
            uint256 existingBalance = IERC20(_token).balanceOf(address(_setToken));

            Invoke.invokeTransfer(_setToken, _token, _to, _quantity);

            // Get new balance of transferred token for SetToken
            uint256 newBalance = IERC20(_token).balanceOf(address(_setToken));

            // Verify only the transfer quantity is subtracted
            require(
                newBalance == existingBalance.sub(_quantity),
                "Invalid post transfer balance"
            );
        }
    }


/**
 * @dev Performs the power on a specified value, reverts on overflow.
 */

function safePower(
        uint256 a,
        uint256 pow
    )
        internal
        pure
        returns (uint256)
    {
        require(a > 0, "Value must be positive");

        uint256 result = 1;
        for (uint256 i = 0; i < pow; i++){
            uint256 previousResult = result;

            // Using safemath multiplication prevents overflows
            result = previousResult.mul(a);
        }

        return result;
    }


/**
 * Calculates the new default position unit and performs the edit with the new unit
 *
 * @param _setToken                 Address of the SetToken
 * @param _component                Address of the component
 * @param _setTotalSupply           Current SetToken supply
 * @param _componentPreviousBalance Pre-action component balance
 * @return                          Current component balance
 * @return                          Previous position unit
 * @return                          New position unit
 */

function calculateAndEditDefaultPosition(
        ISetToken _setToken,
        address _component,
        uint256 _setTotalSupply,
        uint256 _componentPreviousBalance
    )
        internal
        returns(uint256, uint256, uint256)
    {
        uint256 currentBalance = IERC20(_component).balanceOf(address(_setToken));
        uint256 positionUnit = _setToken.getDefaultPositionRealUnit(_component).toUint256();

        uint256 newTokenUnit;
        if (currentBalance > 0) {
            newTokenUnit = calculateDefaultEditPositionUnit(
                _setTotalSupply,
                _componentPreviousBalance,
                currentBalance,
                positionUnit
            );
        } else {
            newTokenUnit = 0;
        }

        editDefaultPosition(_setToken, _component, newTokenUnit);

        return (currentBalance, positionUnit, newTokenUnit);
    }


/**
 * ONLY MANAGER: Remove stored component from array. Checks to see if there's a duplicate component and
 * that module has not been previously used. Calls remove component which will remove first instance
 * of component in the array and set used to true to make sure removeComponent() cannot be called again.
 */

function removeComponent() external onlyManagerAndValidSet(setToken) {
        require(!used, "Module has been used");

        address[] memory components = setToken.getComponents();
        uint256 componentCount;
        for (uint256 i = 0; i < components.length; i++) {
            if (component == components[i]) {
                componentCount = componentCount.add(1);
            }
        }
        require(componentCount > 0, "Component does not have duplicate");

        setToken.removeComponent(component);

        used = true;
    }


// called by factory to set interest rate redirection for Aave tokens

function claim(address _token, address _to) external {
        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
        require(_token != token0 && _token != token1, 'UniswapV2: FORBIDDEN'); // sufficient check
        _safeTransfer(_token, _to, IERC20(_token).balanceOf(address(this)));
    }


// Withdraw staked tokens
// First withdraws unlocked tokens, then earned tokens. Withdrawing earned tokens
// incurs a 50% penalty which will be burnt

function withdrawEarning(uint256 amount) public {
        require(amount > 0, "Cannot withdraw 0");
        Balances storage bal = userBalances[msg.sender];
        uint256 penaltyAmount = 0;

        uint256 remaining = amount;
        bal.earned = bal.earned.sub(remaining);
        for (uint i = 0; ; i++) {
            uint256 earnedAmount = _userEarnings[msg.sender][i].amount;
            if (earnedAmount == 0) {
                continue;
            }
            if (penaltyAmount == 0 && _userEarnings[msg.sender][i].unlockTime > block.timestamp) {
                penaltyAmount = remaining;
                require(bal.earned >= remaining, "Insufficient balance after penalty");
                bal.earned = bal.earned.sub(remaining);
                if (bal.earned == 0) {
                    delete _userEarnings[msg.sender];
                    break;
                }
                remaining = remaining.mul(2);
            }
            if (remaining <= earnedAmount) {
                _userEarnings[msg.sender][i].amount = earnedAmount.sub(remaining);
                break;
            } else {
                delete _userEarnings[msg.sender][i];
                remaining = remaining.sub(earnedAmount);
            }
        }

        wasabi.safeTransfer(msg.sender, amount);

        accumulatedPenalty = accumulatedPenalty + penaltyAmount;

        emit EarningWithdraw(msg.sender, amount, penaltyAmount);
    }


// Final balance received and penalty balance paid by user upon calling exit

function withdrawableEarning(
        address user
    )
        public
        view
        returns (uint256 amount, uint256 penaltyAmount, uint256 amountWithoutPenalty)
    {
        Balances storage bal = userBalances[user];

        if (bal.earned > 0) {
            uint256 length = _userEarnings[user].length;
            for (uint i = 0; i < length; i++) {
                uint256 earnedAmount = _userEarnings[user][i].amount;
                if (earnedAmount == 0) {
                    continue;
                }
                if (_userEarnings[user][i].unlockTime > block.timestamp) {
                    break;
                }
                amountWithoutPenalty = amountWithoutPenalty.add(earnedAmount);
            }

            if (bal.earned.sub(amountWithoutPenalty) % 2 == 0) {
                penaltyAmount = bal.earned.sub(amountWithoutPenalty).div(2);
            } else {
                penaltyAmount = bal.earned.sub(amountWithoutPenalty).div(2) + 1;
            }
        }
        amount = bal.earned.sub(penaltyAmount);

        return (amount, penaltyAmount, amountWithoutPenalty);
    }


// Returns a boolean if this oracle can provide data for the requested pair, used during FNFT creation
// If the oracle does not exist, this will revert the transaction

function getPairHasOracle(address asset, address compareTo) public view override returns (bool) {
        //First, check if standard ERC20 with decimals() method
        uint decimals = ERC20(asset).decimals();
        uint price = oracle.assetToAsset(asset, 10**decimals, compareTo, TWAP_PERIOD);
        return true;
    }


// @dev Presale Mint
// @param tokenCount The tokens a user wants to purchase
// @param presaleMaxMint The max tokens a user can mint from the presale
// @param factionId Community: 0 and Theos: 1
// @param sig Server side signature authorizing user to use the presale

function mintPresale(
        uint256 tokenCount,
        uint256 presaleMaxMint,
        uint256 factionId,
        bytes memory sig
    ) external nonReentrant payable {
        require(factionId == 0 || factionId == 1, "Faction is not valid");
        require(_isPresaleActive, "Presale not active");
        require(!_isSaleActive, "Cannot mint while main sale is active");
        require(tokenCount > 0, "Must mint at least 1 token");
        require(tokenCount <= presaleMaxMint, "Token count exceeds limit");
        require((MINT_PRICE * tokenCount) == msg.value, "ETH sent does not match required payment");
        require(presaleMaxMint <= 6, "The max that a random user can mint in the presale is 6");
        require(!presaleClaimed[msg.sender], "Already minted in the presale with this address");

        // Verify signature
        bytes32 message = getPresaleSigningHash(msg.sender, tokenCount, presaleMaxMint, factionId).toEthSignedMessageHash();
        require(ECDSA.recover(message, sig) == signVerifier, "Permission to call this function failed");

        presaleClaimed[msg.sender] = true;

        // Mint
        _handleFactionMint(
            tokenCount,
            factionId,
            msg.sender,
            COMMUNITY_MINT_SUPPLY,
            THEOS_MINT_SUPPLY
        );
    }


// @dev Main sale mint
// @param tokensCount The tokens a user wants to purchase
// @param factionId Community: 0 and Theos: 1

function mint(uint256 tokenCount, uint256 factionId)
    external nonReentrant payable {
        require(factionId == 0 || factionId == 1, "Faction is not valid");
        require(_isSaleActive, "Sale not active");
        require(tokenCount > 0, "Must mint at least 1 token");
        require(tokenCount <= maxMintPerTransaction, "Token count exceeds limit");
        require((MINT_PRICE * tokenCount) == msg.value, "ETH sent does not match required payment");

        _handleFactionMint(
            tokenCount,
            factionId,
            msg.sender,
            COMMUNITY_MINT_SUPPLY,
            THEOS_MINT_SUPPLY
        );
    }


// @dev Private mint function reserved for company.
// 44 Community and 44 Theos are reserved.
// @param recipient The user receiving the tokens
// @param tokenCount The number of tokens to distribute
// @param factionId Community: 0 and Theos: 1

function mintToAddress(address recipient, uint256 tokenCount, uint256 factionId)
    external onlyOwner {
        require(isSaleFinished(), "Sale has not concluded");
        require(factionId == 0 || factionId == 1, "Faction does not exist");
        require(tokenCount > 0, "You can only mint more than 0 tokens");

        _handleFactionMint(
            tokenCount,
            factionId,
            recipient,
            COMMUNITY_MINT_SUPPLY + COMMUNITY_STAFF_SUPPLY,
            THEOS_MINT_SUPPLY + THEOS_STAFF_SUPPLY
        );
    }


/**
 * @param _tokenId uint256 ID of new token
 * @param _payoutPercentage uint256 payout percentage (divisible by 10)
 */

function createPromoListing(uint256 _tokenId, uint256 _startingPrice, uint256 _payoutPercentage) onlyOwner() public {
     uint256 countryId = _tokenId % COUNTRY_IDX;
     address countryOwner;
     uint256 price;
     (countryOwner,,price,,) = countryContract.getCountryData(countryId);
     require (countryOwner != address(0));

     if (_startingPrice == 0) {
       if (price >= thirdCap) _startingPrice = price.div(80);
       else if (price >= secondCap) _startingPrice = price.div(75);
       else _startingPrice = 0.002 ether;
     }

     createListing(_tokenId, _startingPrice, _payoutPercentage, countryOwner);
   }


/**
 * @dev createListing Adds new ERC721 Token
 * @param _tokenId uint256 ID of new token
 * @param _payoutPercentage uint256 payout percentage (divisible by 10)
 * @param _owner address of new owner
 */

function createListing(uint256 _tokenId, uint256 _startingPrice, uint256 _payoutPercentage, address _owner) onlyOwner() public {

    // make sure price > 0
    require(_startingPrice > 0);
    // make sure token hasn't been used yet
    require(cityData[_tokenId].price == 0);

    // create new token
    City storage newCity = cityData[_tokenId];

    newCity.owner = _owner;
    newCity.price = _startingPrice;
    newCity.lastPrice = 0;
    newCity.payout = _payoutPercentage;

    // store city in storage
    listedCities.push(_tokenId);

    // mint new token
    _mint(_owner, _tokenId);
  }


/**
 * @dev Determines next price of token
 * @param _price uint256 ID of current price
 */

function getNextPrice (uint256 _price) private view returns (uint256 _nextPrice) {
    if (_price < firstCap) {
      return _price.mul(200).div(94);
    } else if (_price < secondCap) {
      return _price.mul(135).div(95);
    } else if (_price < thirdCap) {
      return _price.mul(118).div(96);
    } else {
      return _price.mul(115).div(97);
    }
  }