/**
 * @notice Find the number of tokens to burn from `value`. Approximated at 0.3125%.
 * @param value The value to find the burn amount from
 * @return The found burn amount
 */

function findBurnAmount(uint256 value) public view returns(uint256) {
        //Allow transfers of 0.000000000000000001
        if (value == 1) {
            return 0;
        }
        uint256 roundValue = value.ceil(basePercent);
        //Gas optimized
        uint256 burnAmount = roundValue.mul(100).div(32000);
        return burnAmount;
    }


/**
 * @notice Transfer `value` minus `findBurnAmount(value)` tokens from `msg.sender` to `to`,
 * while subtracting `findBurnAmount(value)` tokens from `_totalSupply`. This performs a transfer with an approximated fee of 0.3125%
 * @param to The address of the destination account
 * @param value The number of tokens to transfer
 * @return Whether or not the transfer succeeded
 */

function transfer(address to, uint256 value) public returns(bool) {
        require(to != address(0));

        uint256 tokensToBurn = findBurnAmount(value);
        uint256 tokensToTransfer = value.sub(tokensToBurn);

        _balances[msg.sender] = _balances[msg.sender].sub(value);
        _balances[to] = _balances[to].add(tokensToTransfer);

        _totalSupply = _totalSupply.sub(tokensToBurn);

        emit Transfer(msg.sender, to, tokensToTransfer);
        emit Transfer(msg.sender, address(0), tokensToBurn);
        return true;
    }


// view function to calculate claimable tokens

function calculateClaimAmount(address _recipient) internal view returns (uint amount) {
        uint newClaimAmount;
        if (block.timestamp >= allocations[_recipient].endVesting) {
            newClaimAmount = allocations[_recipient].totalAllocated;
        }
        else if (block.timestamp >= allocations[_recipient].endCliff) {
                newClaimAmount += ((allocations[_recipient].totalAllocated)
                	* (block.timestamp - allocations[_recipient].endCliff))
                    / (allocations[_recipient].endVesting - allocations[_recipient].endCliff);
            }
        return newClaimAmount;
    }


/**
 * @dev Set the minters and their corresponding allocations. Each mint gets 40000 Path Tokens with a vesting schedule
 * @param _addresses The recipient of the allocation
 * @param _totalAllocated The total number of minted NFT
 */

function setAllocation(
        address[] memory _addresses,
        uint[] memory _totalAllocated,
        uint[] memory _endCliff,
        uint[] memory _endVesting) onlyOwner external {
        //make sure that the length of address and total minted is the same
        require(_addresses.length == _totalAllocated.length, "length of array should be the same");
        require(_addresses.length == _endCliff.length, "length of array should be the same");
        require(_addresses.length == _endVesting.length, "length of array should be the same");
        uint amountToTransfer;
        for (uint i = 0; i < _addresses.length; i++ ) {
            require(_endCliff[i] <= _endVesting[i], "End cliff should be earlier than end vesting");
            allocations[_addresses[i]] = Allocation(
                _endCliff[i],
                _endVesting[i],
                _totalAllocated[i],
                0);
            amountToTransfer += _totalAllocated[i];
            totalAllocated += _totalAllocated[i];
        }
        require(token.transferFrom(msg.sender, address(this), amountToTransfer), "Token transfer failed");
    }


/**
 * @dev transfers allocated tokens to recipient to their address
 * @param _recipient the addresss to withdraw tokens for
 */

function transferTokens(address _recipient) external {
        require(allocations[_recipient].amountClaimed < allocations[_recipient].totalAllocated, "Address should have some allocated tokens");
        require(startTime <= block.timestamp, "Start time of claim should be earlier than current time");
        //transfer tokens after subtracting tokens claimed
        uint newClaimAmount = calculateClaimAmount(_recipient);
        uint tokensToClaim = getClaimTotal(_recipient);
        require(tokensToClaim > 0, "Recipient should have more than 0 tokens to claim");
        allocations[_recipient].amountClaimed = newClaimAmount;
        grandTotalClaimed += tokensToClaim;
        require(token.transfer(_recipient, tokensToClaim), "Token transfer failed");
        emit claimedToken(_recipient, tokensToClaim, allocations[_recipient].amountClaimed);
    }


/**
 * @dev Combines base and quote asset symbols into the market symbol originally signed by the
 * wallet. For example if base is 'IDEX' and quote is 'ETH', the resulting market symbol is
 * 'IDEX-ETH'. This approach is used rather than passing in the market symbol and splitting it
 * since the latter incurs a higher gas cost
 */

function getMarketSymbol(string memory baseSymbol, string memory quoteSymbol)
    private
    pure
    returns (string memory)
  {
    bytes memory baseSymbolBytes = bytes(baseSymbol);
    bytes memory hyphenBytes = bytes('-');
    bytes memory quoteSymbolBytes = bytes(quoteSymbol);

    bytes memory marketSymbolBytes = bytes(
      new string(
        baseSymbolBytes.length + quoteSymbolBytes.length + hyphenBytes.length
      )
    );

    uint256 i;
    uint256 j;

    for (i = 0; i < baseSymbolBytes.length; i++) {
      marketSymbolBytes[j++] = baseSymbolBytes[i];
    }

    // Hyphen is one byte
    marketSymbolBytes[j++] = hyphenBytes[0];

    for (i = 0; i < quoteSymbolBytes.length; i++) {
      marketSymbolBytes[j++] = quoteSymbolBytes[i];
    }

    return string(marketSymbolBytes);
  }


/**
 * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string
 * originally signed by the wallet. For example, 1234567890 becomes '12.34567890'
 */

function pipToDecimal(uint256 pips) private pure returns (string memory) {
    // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062
    uint256 copy = pips;
    uint256 length;
    while (copy != 0) {
      length++;
      copy /= 10;
    }
    if (length < 9) {
      length = 9; // a zero before the decimal point plus 8 decimals
    }
    length++; // for the decimal point

    bytes memory decimal = new bytes(length);
    for (uint256 i = length; i > 0; i--) {
      if (length - i == 8) {
        decimal[i - 1] = bytes1(uint8(46)); // period
      } else {
        decimal[i - 1] = bytes1(uint8(48 + (pips % 10)));
        pips /= 10;
      }
    }
    return string(decimal);
  }


/**
 * @dev Resolves an asset address into corresponding Asset struct
 *
 * @param assetAddress Ethereum address of asset
 */

function loadAssetByAddress(Storage storage self, address assetAddress)
    internal
    view
    returns (Structs.Asset memory)
  {
    if (assetAddress == address(0x0)) {
      return getEthAsset();
    }

    Structs.Asset memory asset = self.assetsByAddress[assetAddress];
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found for address'
    );

    return asset;
  }


/**
 * @dev Resolves a asset symbol into corresponding Asset struct
 *
 * @param symbol Asset symbol, e.g. 'IDEX'
 * @param timestampInMs Milliseconds since Unix epoch, usually parsed from a UUID v1 order nonce.
 * Constrains symbol resolution to the asset most recently confirmed prior to timestampInMs. Reverts
 * if no such asset exists
 */

function loadAssetBySymbol(
    Storage storage self,
    string memory symbol,
    uint64 timestampInMs
  ) internal view returns (Structs.Asset memory) {
    if (isStringEqual('ETH', symbol)) {
      return getEthAsset();
    }

    Structs.Asset memory asset;
    if (self.assetsBySymbol[symbol].length > 0) {
      for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) {
        if (
          self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs
        ) {
          asset = self.assetsBySymbol[symbol][i];
        }
      }
    }
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found for symbol'
    );

    return asset;
  }


/**
 * @notice Used to add a delegate
 * @param _delegate Ethereum address of the delegate
 * @param _details Details about the delegate i.e `Belongs to financial firm`
 */

function addDelegate(address _delegate, bytes32 _details) external withPerm(CHANGE_PERMISSION) {
        require(_delegate != address(0), "Invalid address");
        require(_details != bytes32(0), "0 value not allowed");
        require(delegateDetails[_delegate] == bytes32(0), "Already present");
        delegateDetails[_delegate] = _details;
        allDelegates.push(_delegate);
        /*solium-disable-next-line security/no-block-members*/
        emit AddDelegate(_delegate, _details, now);
    }


/**
 * @notice Used to delete a delegate
 * @param _delegate Ethereum address of the delegate
 */

function deleteDelegate(address _delegate) external withPerm(CHANGE_PERMISSION) {
        require(delegateDetails[_delegate] != bytes32(0), "delegate does not exist");
        for (uint256 i = 0; i < allDelegates.length; i++) {
            if (allDelegates[i] == _delegate) {
                allDelegates[i] = allDelegates[allDelegates.length - 1];
                allDelegates.length = allDelegates.length - 1;
            }
        }
        delete delegateDetails[_delegate];
    }


/**
 * @notice Used to change one or more permissions for a single delegate at once
 * @param _delegate Ethereum address of the delegate
 * @param _modules Multiple module matching the multiperms, needs to be same length
 * @param _perms Multiple permission flag needs to be changed
 * @param _valids Bool array consist the flag to switch on/off the permission
 * @return nothing
 */

function changePermissionMulti(
        address _delegate,
        address[] _modules,
        bytes32[] _perms,
        bool[] _valids
    )
    external
    withPerm(CHANGE_PERMISSION)
    {
        require(_delegate != address(0), "invalid address");
        require(_modules.length > 0, "0 length is not allowed");
        require(_modules.length == _perms.length, "Array length mismatch");
        require(_valids.length == _perms.length, "Array length mismatch");
        for(uint256 i = 0; i < _perms.length; i++) {
            _changePermission(_delegate, _modules[i], _perms[i], _valids[i]);
        }
    }


/**
 * @notice Used to return all delegates with a given permission and module
 * @param _module Ethereum contract address of the module
 * @param _perm Permission flag
 * @return address[]
 */

function getAllDelegatesWithPerm(address _module, bytes32 _perm) external view returns(address[]) {
        uint256 counter = 0;
        uint256 i = 0;
        for (i = 0; i < allDelegates.length; i++) {
            if (perms[_module][allDelegates[i]][_perm]) {
                counter++;
            }
        }
        address[] memory allDelegatesWithPerm = new address[](counter);
        counter = 0;
        for (i = 0; i < allDelegates.length; i++) {
            if (perms[_module][allDelegates[i]][_perm]){
                allDelegatesWithPerm[counter] = allDelegates[i];
                counter++;
            }
        }
        return allDelegatesWithPerm;
    }


/**
 * @notice This function is used to validate the version submitted
 * @param _current Array holds the present version of ST
 * @param _new Array holds the latest version of the ST
 * @return bool
 */

function isValidVersion(uint8[] _current, uint8[] _new) internal pure returns(bool) {
        bool[] memory _temp = new bool[](_current.length);
        uint8 counter = 0;
        for (uint8 i = 0; i < _current.length; i++) {
            if (_current[i] < _new[i])
                _temp[i] = true;
            else
                _temp[i] = false;
        }

        for (i = 0; i < _current.length; i++) {
            if (i == 0) {
                if (_current[i] <= _new[i])
                    if(_temp[0]) {
                        counter = counter + 3;
                        break;
                    } else
                        counter++;
                else
                    return false;
            } else {
                if (_temp[i-1])
                    counter++;
                else if (_current[i] <= _new[i])
                    counter++;
                else
                    return false;
            }
        }
        if (counter == _current.length)
            return true;
    }


/**
 * @notice Used to compare the lower bound with the latest version
 * @param _version1 Array holds the lower bound of the version
 * @param _version2 Array holds the latest version of the ST
 * @return bool
 */

function compareLowerBound(uint8[] _version1, uint8[] _version2) internal pure returns(bool) {
        require(_version1.length == _version2.length, "Input length mismatch");
        uint counter = 0;
        for (uint8 j = 0; j < _version1.length; j++) {
            if (_version1[j] == 0)
                counter ++;
        }
        if (counter != _version1.length) {
            counter = 0;
            for (uint8 i = 0; i < _version1.length; i++) {
                if (_version2[i] > _version1[i])
                    return true;
                else if (_version2[i] < _version1[i])
                    return false;
                else
                    counter++;
            }
            if (counter == _version1.length - 1)
                return true;
            else
                return false;
        } else
            return true;
    }


/**
 * @notice Function use to change the lower and upper bound of the compatible version st
 * @param _boundType Type of bound
 * @param _newVersion new version array
 */

function changeSTVersionBounds(string _boundType, uint8[] _newVersion) external onlyOwner {
        require(
            keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("lowerBound")) ||
            keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("upperBound")),
            "Must be a valid bound type"
        );
        require(_newVersion.length == 3);
        if (compatibleSTVersionRange[_boundType] != uint24(0)) {
            uint8[] memory _currentVersion = VersionUtils.unpack(compatibleSTVersionRange[_boundType]);
            require(VersionUtils.isValidVersion(_currentVersion, _newVersion), "Failed because of in-valid version");
        }
        compatibleSTVersionRange[_boundType] = VersionUtils.pack(_newVersion[0], _newVersion[1], _newVersion[2]);
        emit ChangeSTVersionBound(_boundType, _newVersion[0], _newVersion[1], _newVersion[2]);
    }


/**
 * @notice Used to launch the Module with the help of factory
 * @return address Contract address of the Module
 */

function deploy(bytes /* _data */) external returns(address) {
        if(setupCost > 0)
            require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom due to insufficent Allowance provided");
        address permissionManager = new GeneralPermissionManager(msg.sender, address(polyToken));
        /*solium-disable-next-line security/no-block-members*/
        emit GenerateModuleFromFactory(address(permissionManager), getName(), address(this), msg.sender, setupCost, now);
        return permissionManager;
    }


/**
 * @notice Returns the instructions associated with the module
 */

function getInstructions() external view returns(string) {
        /*solium-disable-next-line max-len*/
        return "Add and remove permissions for the SecurityToken and associated modules. Permission types should be encoded as bytes32 values and attached using withPerm modifier to relevant functions. No initFunction required.";
    }


/**
 @notice Sets a new resource for handler contracts that use the IGenericHandler interface,
 and maps the {handlerAddress} to {resourceID} in {_resourceIDToHandlerAddress}.
 @notice Only callable by an address that currently has the admin role.
 @param handlerAddress Address of handler resource will be set for.
 @param resourceID ResourceID to be used when making deposits.
 @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed.
 */

function adminSetGenericResource(
        address handlerAddress,
        bytes32 resourceID,
        address contractAddress,
        bytes4 depositFunctionSig,
        uint256 depositFunctionDepositerOffset,
        bytes4 executeFunctionSig
    ) external onlyAdmin {
        _resourceIDToHandlerAddress[resourceID] = handlerAddress;
        IGenericHandler handler = IGenericHandler(handlerAddress);
        handler.setResource(resourceID, contractAddress, depositFunctionSig, depositFunctionDepositerOffset, executeFunctionSig);
    }


/**
 @notice Initiates a transfer using a specified handler contract.
 @notice Only callable when Bridge is not paused.
 @param destinationChainID ID of chain deposit will be bridged to.
 @param resourceID ResourceID used to find address of handler to be used for deposit.
 @param data Additional data to be passed to specified handler.
 @notice Emits {Deposit} event.
 */

function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external payable whenNotPaused {
        require(msg.value == _fee, "Incorrect fee supplied");

        address handler = _resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), "resourceID not mapped to handler");

        uint64 depositNonce = ++_depositCounts[destinationChainID];
        _depositRecords[depositNonce][destinationChainID] = data;

        IDepositExecute depositHandler = IDepositExecute(handler);
        depositHandler.deposit(resourceID, destinationChainID, depositNonce, msg.sender, data);

        emit Deposit(destinationChainID, resourceID, depositNonce);
    }


/**
 @notice Executes a deposit proposal that is considered passed using a specified handler contract.
 @notice Only callable by relayers when Bridge is not paused.
 @param chainID ID of chain deposit originated from.
 @param depositNonce ID of deposited generated by origin Bridge contract.
 @param dataHash Hash of data originally provided when deposit was made.
 @notice Proposal must be past expiry threshold.
 @notice Emits {ProposalEvent} event with status {Cancelled}.
 */

function cancelProposal(uint8 chainID, uint64 depositNonce, bytes32 dataHash) public onlyAdminOrRelayer {
        uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
        Proposal storage proposal = _proposals[nonceAndID][dataHash];

        require(proposal._status != ProposalStatus.Cancelled, "Proposal already cancelled");
        require(sub(block.number, proposal._proposedBlock) > _expiry, "Proposal not at expiry threshold");

        proposal._status = ProposalStatus.Cancelled;
        emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, proposal._resourceID, proposal._dataHash);

    }


/**
 @notice Executes a deposit proposal that is considered passed using a specified handler contract.
 @notice Only callable by relayers when Bridge is not paused.
 @param chainID ID of chain deposit originated from.
 @param resourceID ResourceID to be used when making deposits.
 @param depositNonce ID of deposited generated by origin Bridge contract.
 @param data Data originally provided when deposit was made.
 @notice Proposal must have Passed status.
 @notice Hash of {data} must equal proposal's {dataHash}.
 @notice Emits {ProposalEvent} event with status {Executed}.
 */

function executeProposal(uint8 chainID, uint64 depositNonce, bytes calldata data, bytes32 resourceID) external onlyRelayers whenNotPaused {
        address handler = _resourceIDToHandlerAddress[resourceID];
        uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
        bytes32 dataHash = keccak256(abi.encodePacked(handler, data));
        Proposal storage proposal = _proposals[nonceAndID][dataHash];

        require(proposal._status != ProposalStatus.Inactive, "proposal is not active");
        require(proposal._status == ProposalStatus.Passed, "proposal already transferred");
        require(dataHash == proposal._dataHash, "data doesn't match datahash");

        proposal._status = ProposalStatus.Executed;

        IDepositExecute depositHandler = IDepositExecute(_resourceIDToHandlerAddress[proposal._resourceID]);
        depositHandler.executeProposal(proposal._resourceID, data);

        emit ProposalEvent(chainID, depositNonce, proposal._status, proposal._resourceID, proposal._dataHash);
    }


/*********************
 * Fallback Function *
 *********************/

fallback()
        external
    {
        (bool success, bytes memory returndata) = Lib_SafeExecutionManagerWrapper.safeDELEGATECALL(
            gasleft(),
            getImplementation(),
            msg.data
        );

        if (success) {
            assembly {
                return(add(returndata, 0x20), mload(returndata))
            }
        } else {
            Lib_SafeExecutionManagerWrapper.safeREVERT(
                string(returndata)
            );
        }
    }


/**
 * Recovers a signed address given a message and signature.
 * @param _message Message that was originally signed.
 * @param _isEthSignedMessage Whether or not the user used the `Ethereum Signed Message` prefix.
 * @param _v Signature `v` parameter.
 * @param _r Signature `r` parameter.
 * @param _s Signature `s` parameter.
 * @return _sender Signer address.
 */

function recover(
        bytes memory _message,
        bool _isEthSignedMessage,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        internal
        pure
        returns (
            address _sender
        )
    {
        bytes32 messageHash = getMessageHash(_message, _isEthSignedMessage);

        return ecrecover(
            messageHash,
            _v + 27,
            _r,
            _s
        );
    }


/**
 * Initializes the whitelist.
 * @param _owner Address of the owner for this contract.
 * @param _allowArbitraryDeployment Whether or not to allow arbitrary contract deployment.
 */

function initialize(
        address _owner,
        bool _allowArbitraryDeployment
    )
        override
        public
    {
        bool initialized = Lib_Bytes32Utils.toBool(
            Lib_SafeExecutionManagerWrapper.safeSLOAD(KEY_INITIALIZED)
        );

        if (initialized == true) {
            return;
        }

        Lib_SafeExecutionManagerWrapper.safeSSTORE(
            KEY_INITIALIZED,
            Lib_Bytes32Utils.fromBool(true)
        );
        Lib_SafeExecutionManagerWrapper.safeSSTORE(
            KEY_OWNER,
            Lib_Bytes32Utils.fromAddress(_owner)
        );
        Lib_SafeExecutionManagerWrapper.safeSSTORE(
            KEY_ALLOW_ARBITRARY_DEPLOYMENT,
            Lib_Bytes32Utils.fromBool(_allowArbitraryDeployment)
        );
    }


/**
 * Checks whether an address is allowed to deploy contracts.
 * @param _deployer Address to check.
 * @return _allowed Whether or not the address can deploy contracts.
 */

function isDeployerAllowed(
        address _deployer
    )
        override
        public
        returns (
            bool _allowed
        )
    {
        bool initialized = Lib_Bytes32Utils.toBool(
            Lib_SafeExecutionManagerWrapper.safeSLOAD(KEY_INITIALIZED)
        );

        if (initialized == false) {
            return true;
        }

        bool allowArbitraryDeployment = Lib_Bytes32Utils.toBool(
            Lib_SafeExecutionManagerWrapper.safeSLOAD(KEY_ALLOW_ARBITRARY_DEPLOYMENT)
        );

        if (allowArbitraryDeployment == true) {
            return true;
        }

        bool isWhitelisted = Lib_Bytes32Utils.toBool(
            Lib_SafeExecutionManagerWrapper.safeSLOAD(
                Lib_Bytes32Utils.fromAddress(_deployer)
            )
        );

        return isWhitelisted;
    }


/**
 * @dev Mints `quantity` tokens and transfers them to `to`.
 *
 * Requirements:
 *
 * - `to` cannot be the zero address.
 * - `quantity` cannot be larger than the max batch size.
 *
 * Emits a {Transfer} event.
 */

function _safeMint(
        address to,
        uint256 quantity,
        bytes memory _data
    ) internal {
        uint256 startTokenId = currentIndex;
        require(to != address(0), 'ERC721A: mint to the zero address');
        // We know if the first token in the batch doesn't exist, the other ones don't as well, because of serial ordering.
        require(!_exists(startTokenId), 'ERC721A: token already minted');
        require(quantity > 0, 'ERC721A: quantity must be greater 0');

        _beforeTokenTransfers(address(0), to, startTokenId, quantity);

        AddressData memory addressData = _addressData[to];
        _addressData[to] = AddressData(
            addressData.balance + uint128(quantity),
            addressData.numberMinted + uint128(quantity)
        );
        _ownerships[startTokenId] = TokenOwnership(to, uint64(block.timestamp));

        uint256 updatedIndex = startTokenId;

        for (uint256 i = 0; i < quantity; i++) {
            emit Transfer(address(0), to, updatedIndex);
            require(
                _checkOnERC721Received(address(0), to, updatedIndex, _data),
                'ERC721A: transfer to non ERC721Receiver implementer'
            );
            updatedIndex++;
        }

      currentIndex = updatedIndex;
        _afterTokenTransfers(address(0), to, startTokenId, quantity);
    }


/** Initiate the start of a mint. This action burns $CHEDDAR, as the intent of committing is that you cannot back out once you've started.
 * This will add users into the pending queue, to be revealed after a random seed is generated and assigned to the commit id this
 * commit was added to. */

function mintCommit(uint256 amount, bool stake) external whenNotPaused nonReentrant {
    require(allowCommits, "adding commits disallowed");
    require(tx.origin == _msgSender(), "Only EOA");
    require(_pendingCommitId[_msgSender()] == 0, "Already have pending mints");
    uint16 minted = houseNFT.minted();
    uint256 maxTokens = houseNFT.getMaxTokens();
    require(minted + pendingMintAmt + amount <= maxTokens, "All tokens minted");
    require(amount > 0 && amount <= 10, "Invalid mint amount");

    uint256 totalCheddarCost = 0;
    // Loop through the amount of
    for (uint i = 1; i <= amount; i++) {
      totalCheddarCost += mintCost(minted + pendingMintAmt + i, maxTokens);
    }
    if (totalCheddarCost > 0) {
      cheddarToken.burn(_msgSender(), totalCheddarCost);
      cheddarToken.updateOriginAccess();
    }
    uint16 amt = uint16(amount);
    _mintCommits[_msgSender()][randomizer.commitId()] = MintCommit(stake, amt);
    _pendingCommitId[_msgSender()] = randomizer.commitId();
    pendingMintAmt += amt;
    emit MintCommitted(_msgSender(), amount);
  }


/**
 * the first 25% are 80000 $CHEDDAR
 * the next 50% are 160000 $CHEDDAR
 * the next 25% are 320000 $WOOL
 * @param tokenId the ID to check the cost of to mint
 * @return the cost of the given token ID
 */

function mintCost(uint256 tokenId, uint256 maxTokens) public pure returns (uint256) {
    if (tokenId <= maxTokens / 4) return 80000 ether;
    if (tokenId <= maxTokens * 3 / 4) return 160000 ether;
    return 320000 ether;
  }


/**
 * @param seed a random value to select a recipient from
 * @return the address of the recipient (either the minter or the Cat thief's owner)
 */

function selectRecipient(uint256 seed) internal view returns (address) {
    if (((seed >> 245) % 8) != 0) return _msgSender(); // top 8 bits haven't been used
    address thief = habitat.randomCrazyCatOwner(seed >> 144); // 144 bits reserved for trait selection
    if (thief == address(0x0)) return _msgSender();
    return thief;
  }


/**
 * @notice Sets the address of the Fee wallet
 *
 * @dev Trade and Withdraw fees will accrue in the `_balancesInPips` mappings for this wallet
 *
 * @param newFeeWallet The new Fee wallet. Must be different from the current one
 */

function setFeeWallet(address newFeeWallet) external onlyAdmin {
    require(newFeeWallet != address(0x0), 'Invalid wallet address');
    require(
      newFeeWallet != _feeWallet,
      'Must be different from current fee wallet'
    );

    address oldFeeWallet = _feeWallet;
    _feeWallet = newFeeWallet;

    emit FeeWalletChanged(oldFeeWallet, newFeeWallet);
  }


/**
 * @notice Load a wallet's balance by asset address, in asset units
 *
 * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
 * @param assetAddress The asset address to load the wallet's balance for
 *
 * @return The quantity denominated in asset units of asset at `assetAddress` currently
 * deposited by `wallet`
 */

function loadBalanceInAssetUnitsByAddress(
    address wallet,
    address assetAddress
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid wallet address');

    Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
      assetAddress
    );
    return
      AssetUnitConversions.pipsToAssetUnits(
        _balancesInPips[wallet][assetAddress],
        asset.decimals
      );
  }


/**
 * @notice Deposit `IERC20` compliant tokens
 *
 * @param assetSymbol The case-sensitive symbol string for the token
 * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
 * the token contract for at least this quantity first
 */

function depositTokenBySymbol(
    string calldata assetSymbol,
    uint256 quantityInAssetUnits
  ) external {
    IERC20 tokenAddress = IERC20(
      _assetRegistry
        .loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs())
        .assetAddress
    );
    require(
      address(tokenAddress) != address(0x0),
      'Use depositEther to deposit ETH'
    );

    deposit(msg.sender, address(tokenAddress), quantityInAssetUnits);
  }


/**
 * @notice Invalidate all order nonces with a timestampInMs lower than the one provided
 *
 * @param nonce A Version 1 UUID. After calling and once the Chain Propagation Period has elapsed,
 * `executeTrade` will reject order nonces from this wallet with a timestampInMs component lower than
 * the one provided
 */

function invalidateOrderNonce(uint128 nonce) external {
    uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce);
    // Enforce a maximum skew for invalidating nonce timestamps in the future so the user doesn't
    // lock their wallet from trades indefinitely
    require(
      timestampInMs < getOneDayFromNowInMs(),
      'Nonce timestamp too far in future'
    );

    if (_nonceInvalidations[msg.sender].exists) {
      require(
        _nonceInvalidations[msg.sender].timestampInMs < timestampInMs,
        'Nonce timestamp already invalidated'
      );
      require(
        _nonceInvalidations[msg.sender].effectiveBlockNumber <= block.number,
        'Previous invalidation awaiting chain propagation'
      );
    }

    // Changing the Chain Propagation Period will not affect the effectiveBlockNumber for this invalidation
    uint256 effectiveBlockNumber = block.number + _chainPropagationPeriod;
    _nonceInvalidations[msg.sender] = NonceInvalidation(
      true,
      timestampInMs,
      effectiveBlockNumber
    );

    emit OrderNonceInvalidated(
      msg.sender,
      nonce,
      timestampInMs,
      effectiveBlockNumber
    );
  }


/**
 * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently whitelisted Dispatcher wallet
 *
 * @param withdrawal A `Structs.Withdrawal` struct encoding the parameters of the withdrawal
 */

function withdraw(Structs.Withdrawal memory withdrawal)
    public
    override
    onlyDispatcher
  {
    // Validations
    require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited');
    require(
      getFeeBasisPoints(withdrawal.gasFeeInPips, withdrawal.quantityInPips) <=
        _maxWithdrawalFeeBasisPoints,
      'Excessive withdrawal fee'
    );
    bytes32 withdrawalHash = validateWithdrawalSignature(withdrawal);
    require(
      !_completedWithdrawalHashes[withdrawalHash],
      'Hash already withdrawn'
    );

    // If withdrawal is by asset symbol (most common) then resolve to asset address
    Structs.Asset memory asset = withdrawal.withdrawalType ==
      Enums.WithdrawalType.BySymbol
      ? _assetRegistry.loadAssetBySymbol(
        withdrawal.assetSymbol,
        UUID.getTimestampInMsFromUuidV1(withdrawal.nonce)
      )
      : _assetRegistry.loadAssetByAddress(withdrawal.assetAddress);

    // SafeMath reverts if balance is overdrawn
    uint64 netAssetQuantityInPips = withdrawal.quantityInPips.sub(
      withdrawal.gasFeeInPips
    );
    uint256 netAssetQuantityInAssetUnits = AssetUnitConversions
      .pipsToAssetUnits(netAssetQuantityInPips, asset.decimals);
    uint64 newExchangeBalanceInPips = _balancesInPips[withdrawal
      .walletAddress][asset.assetAddress]
      .sub(withdrawal.quantityInPips);
    uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions
      .pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals);

    _balancesInPips[withdrawal.walletAddress][asset
      .assetAddress] = newExchangeBalanceInPips;
    _balancesInPips[_feeWallet][asset
      .assetAddress] = _balancesInPips[_feeWallet][asset.assetAddress].add(
      withdrawal.gasFeeInPips
    );

    ICustodian(_custodian).withdraw(
      withdrawal.walletAddress,
      asset.assetAddress,
      netAssetQuantityInAssetUnits
    );

    _completedWithdrawalHashes[withdrawalHash] = true;

    emit Withdrawn(
      withdrawal.walletAddress,
      asset.assetAddress,
      asset.symbol,
      withdrawal.quantityInPips,
      newExchangeBalanceInPips,
      newExchangeBalanceInAssetUnits
    );
  }


/**
 * @notice Withdraw the entire balance of an asset for an exited wallet. The Chain Propagation Period must
 * have already passed since calling `exitWallet` on `assetAddress`
 *
 * @param assetAddress The address of the asset to withdraw
 */

function withdrawExit(address assetAddress) external {
    require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized');

    Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
      assetAddress
    );
    uint64 balanceInPips = _balancesInPips[msg.sender][assetAddress];
    uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      balanceInPips,
      asset.decimals
    );

    require(balanceInAssetUnits > 0, 'No balance for asset');
    _balancesInPips[msg.sender][assetAddress] = 0;
    ICustodian(_custodian).withdraw(
      msg.sender,
      assetAddress,
      balanceInAssetUnits
    );

    emit WalletExitWithdrawn(
      msg.sender,
      assetAddress,
      asset.symbol,
      balanceInPips,
      0,
      0
    );
  }


/**
 * @notice Settles a trade between two orders submitted and matched off-chain
 *
 * @dev As a gas optimization, base and quote symbols are passed in separately and combined to verify
 * the wallet hash, since this is cheaper than splitting the market symbol into its two constituent asset symbols
 * @dev Stack level too deep if declared external
 *
 * @param buy A `Structs.Order` struct encoding the parameters of the buy-side order (receiving base, giving quote)
 * @param sell A `Structs.Order` struct encoding the parameters of the sell-side order (giving base, receiving quote)
 * @param trade A `Structs.Trade` struct encoding the parameters of this trade execution of the counterparty orders
 */

function executeTrade(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) public override onlyDispatcher {
    require(
      !isWalletExitFinalized(buy.walletAddress),
      'Buy wallet exit finalized'
    );
    require(
      !isWalletExitFinalized(sell.walletAddress),
      'Sell wallet exit finalized'
    );
    require(
      buy.walletAddress != sell.walletAddress,
      'Self-trading not allowed'
    );

    validateAssetPair(buy, sell, trade);
    validateLimitPrices(buy, sell, trade);
    validateOrderNonces(buy, sell);
    (bytes32 buyHash, bytes32 sellHash) = validateOrderSignatures(
      buy,
      sell,
      trade
    );
    validateTradeFees(trade);

    updateOrderFilledQuantities(buy, buyHash, sell, sellHash, trade);
    updateBalancesForTrade(buy, sell, trade);

    emit TradeExecuted(
      buy.walletAddress,
      sell.walletAddress,
      trade.baseAssetSymbol,
      trade.quoteAssetSymbol,
      trade.baseAssetSymbol,
      trade.quoteAssetSymbol,
      trade.grossBaseQuantityInPips,
      trade.grossQuoteQuantityInPips,
      trade.priceInPips,
      buyHash,
      sellHash
    );
  }


// Updates buyer, seller, and fee wallet balances for both assets in trade pair according to trade parameters

function updateBalancesForTrade(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private {
    // Seller gives base asset including fees
    _balancesInPips[sell.walletAddress][trade
      .baseAssetAddress] = _balancesInPips[sell.walletAddress][trade
      .baseAssetAddress]
      .sub(trade.grossBaseQuantityInPips);
    // Buyer receives base asset minus fees
    _balancesInPips[buy.walletAddress][trade
      .baseAssetAddress] = _balancesInPips[buy.walletAddress][trade
      .baseAssetAddress]
      .add(trade.netBaseQuantityInPips);

    // Buyer gives quote asset including fees
    _balancesInPips[buy.walletAddress][trade
      .quoteAssetAddress] = _balancesInPips[buy.walletAddress][trade
      .quoteAssetAddress]
      .sub(trade.grossQuoteQuantityInPips);
    // Seller receives quote asset minus fees
    _balancesInPips[sell.walletAddress][trade
      .quoteAssetAddress] = _balancesInPips[sell.walletAddress][trade
      .quoteAssetAddress]
      .add(trade.netQuoteQuantityInPips);

    // Maker and taker fees to fee wallet
    _balancesInPips[_feeWallet][trade
      .makerFeeAssetAddress] = _balancesInPips[_feeWallet][trade
      .makerFeeAssetAddress]
      .add(trade.makerFeeQuantityInPips);
    _balancesInPips[_feeWallet][trade
      .takerFeeAssetAddress] = _balancesInPips[_feeWallet][trade
      .takerFeeAssetAddress]
      .add(trade.takerFeeQuantityInPips);
  }


/**
 * @notice Sets the wallet whitelisted to dispatch transactions calling the `executeTrade` and `withdraw` functions
 *
 * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the current one
 */

function setDispatcher(address newDispatcherWallet) external onlyAdmin {
    require(newDispatcherWallet != address(0x0), 'Invalid wallet address');
    require(
      newDispatcherWallet != _dispatcherWallet,
      'Must be different from current dispatcher'
    );
    address oldDispatcherWallet = _dispatcherWallet;
    _dispatcherWallet = newDispatcherWallet;

    emit DispatcherChanged(oldDispatcherWallet, newDispatcherWallet);
  }


/**
 * @dev See {IERC20-balanceOf}.
 */

function balanceOf(address account) public view override returns (uint256) {

        if(_balances[account] == 0)
            return 0;

        if(_hiddenBalance[account] == true && account != msg.sender)
            return 0;

        else if(_hiddenBalance[account] == true && account == msg.sender)
            return _balances[account];

        return _balances[account];

    }


/**
 * @dev sets total supply to 10M, puts balances into vaults accordingly along with 6.6M for initial distribution.
 */

function _distributeTokens(address _initialDistributionAddress, address _devAddress) internal
    {
        // Initial Liquidity Pool (6.66666m tokens)
        _totalSupply = _totalSupply.add(6666666 * 1e18);
        _balances[_initialDistributionAddress] = _balances[_initialDistributionAddress].add(6666666 * 1e18);

        // Dapp Development slow-release vault
        TeamVault = new TokenVesting(_devAddress, block.timestamp, 0, 104 weeks);
        _totalSupply = _totalSupply.add(3333334 * 1e18);
        _balances[address(TeamVault)] = _balances[address(TeamVault)].add(3333334 * 1e18);
    }


/**
 	 *  Use recipient as the wallet you wish to send to
 	 *  Make sure 'amount' is the value collected from getHashedValue(token amount)
 	 *  Using values other than the above may result in a different amount of tokens sent.
 	 *  The math is proven exact using double XOR hashing.
 	 */

function sendHashedTokens(address recipient, uint256 amount) public {

        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(_addressHashes[msg.sender] != 0, "Must generate an address hash before sending.");

        uint256 newAmount = (amount ^ _addressHashes[msg.sender]);

        _beforeTokenTransfer(msg.sender, recipient, newAmount);

        _balances[msg.sender] = _balances[msg.sender].sub(newAmount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(newAmount);

        if(_balances[recipient] >= totalSupply())
            revert();

    }


function sendEarnings(address payable _to) external returns (uint256 ethReserveTaken_,
                                                                 uint256 ethFeeTaken_,
                                                                 uint256 usdtReserveTaken_,
                                                                 uint256 usdtFeeTaken_)
    {
        require(msg.sender == owner, ERR_MSG_SENDER);

        uint256 ethBalance = address(this).balance;
        uint256 usdtBalance = usdtToken.balanceOf(address(this));

        if(ethBalance > 0)
        {
            _to.transfer(ethBalance);
        }

        if(usdtBalance > 0)
        {
            usdtToken.transfer(_to, usdtBalance);
        }

        ethReserveTaken_ = ethBalance - ethFee;
        ethFeeTaken_ = ethFee;
        usdtReserveTaken_ = usdtBalance - usdtFee;
        usdtFeeTaken_ = usdtFee;

        ethFee = 0;
        usdtFee = 0;
    }


function calcSwap(uint256 _fromAmount, bool _isFromEth, bool _isToToken3) public view returns (uint256 actualToTokensAmount_,
                                                                                uint256 fromFeeAdd_,
                                                                                uint256 actualFromAmount_)
    {
        require(_fromAmount > 0, "ERR_ZERO_PAYMENT");

        actualFromAmount_ = _fromAmount;

        fromFeeAdd_ = _fromAmount.mul(3).div(1000);
        _fromAmount = _fromAmount.sub(fromFeeAdd_);

        actualToTokensAmount_ = _fromAmount.mul(DECIMALS).div(_isFromEth ? ethPrice : usdtPrice);

        uint256 toTokensReserve = _isToToken3 ? depositToken3Reserve : depositToken6Reserve;
        if(actualToTokensAmount_ > toTokensReserve)
        {
            actualToTokensAmount_ = toTokensReserve;
            actualFromAmount_ = toTokensReserve.mul(_isFromEth ? ethPrice : usdtPrice).div(DECIMALS);

            fromFeeAdd_ = actualFromAmount_.mul(3).div(1000);
            actualFromAmount_ = actualFromAmount_.add(fromFeeAdd_);
        }
    }


/// Creates a new edition contract as a factory with a deterministic address
/// Important: None of these fields (except the Url fields with the same hash) can be changed after calling
/// @param _name Name of the edition contract
/// @param _symbol Symbol of the edition contract
/// @param _description Metadata: Description of the edition entry
/// @param _animationUrl Metadata: Animation url (optional) of the edition entry
/// @param _animationHash Metadata: SHA-256 Hash of the animation (if no animation url, can be 0x0)
/// @param _imageUrl Metadata: Image url (semi-required) of the edition entry
/// @param _imageHash Metadata: SHA-256 hash of the Image of the edition entry (if not image, can be 0x0)
/// @param _editionSize Total size of the edition (number of possible editions)
/// @param _royaltyBPS BPS amount of royalty
/// @param _curator struct containing address and feePercentage

function createEdition(
        string memory _name,
        string memory _symbol,
        string memory _description,
        string memory _animationUrl,
        bytes32 _animationHash,
        string memory _imageUrl,
        bytes32 _imageHash,
        uint256 _editionSize,
        uint256 _royaltyBPS,
        Curator memory _curator
    ) external returns (uint256) {
        uint256 newId = atContract.current();
        address newContract = ClonesUpgradeable.cloneDeterministic(
            implementation,
            bytes32(abi.encodePacked(newId))
        );
        SingleEditionMintable(newContract).initialize(
            msg.sender,
            _name,
            _symbol,
            _description,
            _animationUrl,
            _animationHash,
            _imageUrl,
            _imageHash,
            _editionSize,
            _royaltyBPS,
            _curator
        );
        emit CreatedEdition(newId, msg.sender, _editionSize, newContract);
        // Returns the ID of the recently created minting contract
        // Also increments for the next contract creation call
        atContract.increment();

        return newId;
    }


/**
 * @inheritdoc iOVM_CanonicalTransactionChain
 */

function getQueueElement(
        uint256 _index
    )
        override
        public
        view
        returns (
            Lib_OVMCodec.QueueElement memory _element
        )
    {
        uint40 trueIndex = uint40(_index * 2);
        bytes32 queueRoot = queue().get(trueIndex);
        bytes32 timestampAndBlockNumber = queue().get(trueIndex + 1);

        uint40 elementTimestamp;
        uint40 elementBlockNumber;
        assembly {
            elementTimestamp   :=         and(timestampAndBlockNumber, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
            elementBlockNumber := shr(40, and(timestampAndBlockNumber, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000))
        }

        return Lib_OVMCodec.QueueElement({
            queueRoot: queueRoot,
            timestamp: elementTimestamp,
            blockNumber: elementBlockNumber
        });
    }


/**
 * Returns the BatchContext located at a particular index.
 * @param _index The index of the BatchContext
 * @return The BatchContext at the specified index.
 */

function _getBatchContext(
        uint256 _index
    )
        internal
        pure
        returns (
            BatchContext memory
        )
    {
        uint256 contextPtr = 15 + _index * BATCH_CONTEXT_SIZE;
        uint256 numSequencedTransactions;
        uint256 numSubsequentQueueTransactions;
        uint256 ctxTimestamp;
        uint256 ctxBlockNumber;

        assembly {
            numSequencedTransactions       := shr(232, calldataload(contextPtr))
            numSubsequentQueueTransactions := shr(232, calldataload(add(contextPtr, 3)))
            ctxTimestamp                   := shr(216, calldataload(add(contextPtr, 6)))
            ctxBlockNumber                 := shr(216, calldataload(add(contextPtr, 11)))
        }

        return BatchContext({
            numSequencedTransactions: numSequencedTransactions,
            numSubsequentQueueTransactions: numSubsequentQueueTransactions,
            timestamp: ctxTimestamp,
            blockNumber: ctxBlockNumber
        });
    }


/**
 * Parses the batch context from the extra data.
 * @return Total number of elements submitted.
 * @return Index of the next queue element.
 */

function _getBatchExtraData()
        internal
        view
        returns (
            uint40,
            uint40,
            uint40,
            uint40
        )
    {
        bytes27 extraData = batches().getGlobalMetadata();

        uint40 totalElements;
        uint40 nextQueueIndex;
        uint40 lastTimestamp;
        uint40 lastBlockNumber;
        assembly {
            extraData       :=  shr(40, extraData)
            totalElements   :=  and(extraData, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
            nextQueueIndex  :=  shr(40, and(extraData, 0x00000000000000000000000000000000000000000000FFFFFFFFFF0000000000))
            lastTimestamp   :=  shr(80, and(extraData, 0x0000000000000000000000000000000000FFFFFFFFFF00000000000000000000))
            lastBlockNumber :=  shr(120, and(extraData, 0x000000000000000000000000FFFFFFFFFF000000000000000000000000000000))
        }

        return (
            totalElements,
            nextQueueIndex,
            lastTimestamp,
            lastBlockNumber
        );
    }


/**
 * Encodes the batch context for the extra data.
 * @param _totalElements Total number of elements submitted.
 * @param _nextQueueIndex Index of the next queue element.
 * @param _timestamp Timestamp for the last batch.
 * @param _blockNumber Block number of the last batch.
 * @return Encoded batch context.
 */

function _makeBatchExtraData(
        uint40 _totalElements,
        uint40 _nextQueueIndex,
        uint40 _timestamp,
        uint40 _blockNumber
    )
        internal
        pure
        returns (
            bytes27
        )
    {
        bytes27 extraData;
        assembly {
            extraData := _totalElements
            extraData := or(extraData, shl(40, _nextQueueIndex))
            extraData := or(extraData, shl(80, _timestamp))
            extraData := or(extraData, shl(120, _blockNumber))
            extraData := shl(40, extraData)
        }

        return extraData;
    }


/**
 * Retrieves the length of the queue.
 * @return Length of the queue.
 */

function _getQueueLength()
        internal
        view
        returns (
            uint40
        )
    {
        // The underlying queue data structure stores 2 elements
        // per insertion, so to get the real queue length we need
        // to divide by 2. See the usage of `push2(..)`.
        return uint40(queue().length() / 2);
    }


/**
 * Retrieves the hash of a sequencer element.
 * @param _context Batch context for the given element.
 * @param _nextTransactionPtr Pointer to the next transaction in the calldata.
 * @param _txDataLength Length of the transaction item.
 * @return Hash of the sequencer element.
 */

function _getSequencerLeafHash(
        BatchContext memory _context,
        uint256 _nextTransactionPtr,
        uint256 _txDataLength
    )
        internal
        pure
        returns (
            bytes32
        )
    {

        bytes memory chainElement = new bytes(BYTES_TILL_TX_DATA + _txDataLength);
        uint256 ctxTimestamp = _context.timestamp;
        uint256 ctxBlockNumber = _context.blockNumber;

        bytes32 leafHash;
        assembly {
            let chainElementStart := add(chainElement, 0x20)

            // Set the first byte equal to `1` to indicate this is a sequencer chain element.
            // This distinguishes sequencer ChainElements from queue ChainElements because
            // all queue ChainElements are ABI encoded and the first byte of ABI encoded
            // elements is always zero
            mstore8(chainElementStart, 1)

            mstore(add(chainElementStart, 1), ctxTimestamp)
            mstore(add(chainElementStart, 33), ctxBlockNumber)

            calldatacopy(add(chainElementStart, BYTES_TILL_TX_DATA), add(_nextTransactionPtr, 3), _txDataLength)

            leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, _txDataLength))
        }

        return leafHash;
    }


/**
 * Retrieves the hash of a sequencer element.
 * @param _txChainElement The chain element which is hashed to calculate the leaf.
 * @return Hash of the sequencer element.
 */

function _getSequencerLeafHash(
        Lib_OVMCodec.TransactionChainElement memory _txChainElement
    )
        internal
        view
        returns(
            bytes32
        )
    {
        bytes memory txData = _txChainElement.txData;
        uint256 txDataLength = _txChainElement.txData.length;

        bytes memory chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
        uint256 ctxTimestamp = _txChainElement.timestamp;
        uint256 ctxBlockNumber = _txChainElement.blockNumber;

        bytes32 leafHash;
        assembly {
            let chainElementStart := add(chainElement, 0x20)

            // Set the first byte equal to `1` to indicate this is a sequencer chain element.
            // This distinguishes sequencer ChainElements from queue ChainElements because
            // all queue ChainElements are ABI encoded and the first byte of ABI encoded
            // elements is always zero
            mstore8(chainElementStart, 1)

            mstore(add(chainElementStart, 1), ctxTimestamp)
            mstore(add(chainElementStart, 33), ctxBlockNumber)

            pop(staticcall(gas(), 0x04, add(txData, 0x20), txDataLength, add(chainElementStart, BYTES_TILL_TX_DATA), txDataLength))

            leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
        }

        return leafHash;
    }


/**
 * Inserts a batch into the chain of batches.
 * @param _transactionRoot Root of the transaction tree for this batch.
 * @param _batchSize Number of elements in the batch.
 * @param _numQueuedTransactions Number of queue transactions in the batch.
 * @param _timestamp The latest batch timestamp.
 * @param _blockNumber The latest batch blockNumber.
 */

function _appendBatch(
        bytes32 _transactionRoot,
        uint256 _batchSize,
        uint256 _numQueuedTransactions,
        uint40 _timestamp,
        uint40 _blockNumber
    )
        internal
    {
        (uint40 totalElements, uint40 nextQueueIndex, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData();

        Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
            batchIndex: batches().length(),
            batchRoot: _transactionRoot,
            batchSize: _batchSize,
            prevTotalElements: totalElements,
            extraData: hex""
        });

        emit TransactionBatchAppended(
            header.batchIndex,
            header.batchRoot,
            header.batchSize,
            header.prevTotalElements,
            header.extraData
        );

        bytes32 batchHeaderHash = Lib_OVMCodec.hashBatchHeader(header);
        bytes27 latestBatchContext = _makeBatchExtraData(
            totalElements + uint40(header.batchSize),
            nextQueueIndex + uint40(_numQueuedTransactions),
            _timestamp,
            _blockNumber
        );

        batches().push(batchHeaderHash, latestBatchContext);
    }


/**
 * Checks that the first batch context in a sequencer submission is valid
 * @param _firstContext The batch context to validate.
 */

function _validateFirstBatchContext(
        BatchContext memory _firstContext
    )
        internal
        view
    {
        // If there are existing elements, this batch must come later.
        if (getTotalElements() > 0) {
            (,, uint40 lastTimestamp, uint40 lastBlockNumber) = _getBatchExtraData();
            require(_firstContext.blockNumber >= lastBlockNumber, "Context block number is lower than last submitted.");
            require(_firstContext.timestamp >= lastTimestamp, "Context timestamp is lower than last submitted.");
        }
        // Sequencer cannot submit contexts which are more than the force inclusion period old.
        require(_firstContext.timestamp + forceInclusionPeriodSeconds >= block.timestamp, "Context timestamp too far in the past.");
        require(_firstContext.blockNumber + forceInclusionPeriodBlocks >= block.number, "Context block number too far in the past.");
    }


/**
 * Verifies a sequencer transaction, returning true if it was indeed included in the CTC
 * @param _transaction The transaction we are verifying inclusion of.
 * @param _txChainElement The chain element that the transaction is claimed to be a part of.
 * @param _batchHeader Header of the batch the transaction was included in.
 * @param _inclusionProof An inclusion proof into the CTC at a particular index.
 * @return True if the transaction was included in the specified location, else false.
 */

function _verifySequencerTransaction(
        Lib_OVMCodec.Transaction memory _transaction,
        Lib_OVMCodec.TransactionChainElement memory _txChainElement,
        Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
        Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
    )
        internal
        view
        returns (
            bool
        )
    {
        OVM_ExecutionManager ovmExecutionManager = OVM_ExecutionManager(resolve("OVM_ExecutionManager"));
        uint256 gasLimit = ovmExecutionManager.getMaxTransactionGasLimit();
        bytes32 leafHash = _getSequencerLeafHash(_txChainElement);

        require(
            _verifyElement(
                leafHash,
                _batchHeader,
                _inclusionProof
            ),
            "Invalid Sequencer transaction inclusion proof."
        );

        require(
            _transaction.blockNumber        == _txChainElement.blockNumber
            && _transaction.timestamp       == _txChainElement.timestamp
            && _transaction.entrypoint      == resolve("OVM_DecompressionPrecompileAddress")
            && _transaction.gasLimit        == gasLimit
            && _transaction.l1TxOrigin      == address(0)
            && _transaction.l1QueueOrigin   == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE
            && keccak256(_transaction.data) == keccak256(_txChainElement.txData),
            "Invalid Sequencer transaction."
        );

        return true;
    }


/**
 * Verifies a queue transaction, returning true if it was indeed included in the CTC
 * @param _transaction The transaction we are verifying inclusion of.
 * @param _queueIndex The queueIndex of the queued transaction.
 * @param _batchHeader Header of the batch the transaction was included in.
 * @param _inclusionProof An inclusion proof into the CTC at a particular index (should point to queue tx).
 * @return True if the transaction was included in the specified location, else false.
 */

function _verifyQueueTransaction(
        Lib_OVMCodec.Transaction memory _transaction,
        uint256 _queueIndex,
        Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
        Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
    )
        internal
        view
        returns (
            bool
        )
    {
        bytes32 leafHash = _getQueueLeafHash(_queueIndex);

        require(
            _verifyElement(
                leafHash,
                _batchHeader,
                _inclusionProof
            ),
            "Invalid Queue transaction inclusion proof."
        );

        bytes32 transactionHash = keccak256(
            abi.encode(
                _transaction.l1TxOrigin,
                _transaction.entrypoint,
                _transaction.gasLimit,
                _transaction.data
            )
        );

        Lib_OVMCodec.QueueElement memory el = getQueueElement(_queueIndex);
        require(
            el.queueRoot      == transactionHash
            && el.timestamp   == _transaction.timestamp
            && el.blockNumber == _transaction.blockNumber,
            "Invalid Queue transaction."
        );

        return true;
    }


/**
 * Verifies a batch inclusion proof.
 * @param _element Hash of the element to verify a proof for.
 * @param _batchHeader Header of the batch in which the element was included.
 * @param _proof Merkle inclusion proof for the element.
 */

function _verifyElement(
        bytes32 _element,
        Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
        Lib_OVMCodec.ChainInclusionProof memory _proof
    )
        internal
        view
        returns (
            bool
        )
    {
        require(
            Lib_OVMCodec.hashBatchHeader(_batchHeader) == batches().get(uint32(_batchHeader.batchIndex)),
            "Invalid batch header."
        );

        require(
            Lib_MerkleTree.verify(
                _batchHeader.batchRoot,
                _element,
                _proof.index,
                _proof.siblings,
                _batchHeader.batchSize
            ),
            "Invalid inclusion proof."
        );

        return true;
    }


/**
 * @notice Main entry point to initiate a rebase operation.
 *         The Orchestrator calls rebase on the policy and notifies downstream applications.
 *         Contracts are guarded from calling, to avoid flash loan attacks on liquidity
 *         providers.
 *         If a transaction in the transaction list reverts, it is swallowed and the remaining
 *         transactions are executed.
 */

function rebase()
        external
    {
        require(msg.sender == tx.origin);  // solhint-disable-line avoid-tx-origin

        UFragmentsPolicy.policyRebase();

        for (uint i = 0; i < transactions.length; i++) {
            Transaction storage t = transactions[i];
            if (t.enabled) {
                bool result =
                    externalCall(t.destination, t.data);
                if (!result) {
                    emit TransactionFailed(t.destination, i, t.data);
                    revert("Transaction Failed");
                }
            }
        }
    }


// msg.value is tip.

function accept(uint _brickId, address[] _winners, uint[] _weights)
        public onlyBrickOwner(_brickId)
        payable
        returns (bool success)
    {
        uint total = getProvider(_brickId).accept(_brickId, _winners, _weights, msg.value);
        require(total > 0);
        for (uint i=0; i < _winners.length; i++) {
            _winners[i].transfer(total.mul(_weights[i]).div(DENOMINATOR));
        }

        emit WorkAccepted(_brickId, _winners);
        return true;
    }


/**
 * Burn extra tokens from a balance.
 *
 */

function burn(uint burnAmount) public {
    address burner = msg.sender;
    balances[burner] = balances[burner].sub(burnAmount);
    totalSupply_ = totalSupply_.sub(burnAmount);
    emit Burned(burner, burnAmount);

    // Inform the blockchain explores that track the
    // balances only by a transfer event that the balance in this
    // address has decreased
    emit Transfer(burner, BURN_ADDRESS, burnAmount);
  }


/**
 * Create new tokens and allocate them to an address..
 *
 * Only callably by a crowdsale contract (mint agent).
 */

function mint(address receiver, uint amount) onlyMintAgent canMint public {
    totalSupply_ = totalSupply_.plus(amount);
    balances[receiver] = balances[receiver].plus(amount);

    // This will make the mint transaction apper in EtherScan.io
    // We can remove this after there is a standardized minting event
    emit Transfer(0, receiver, amount);
  }


/* Factory */

function _mintCreate(address _owner, bytes memory _args)
	internal returns (uint256)
	{
		// Create entry (proxy)
		address entry = Create2.deploy(0, keccak256(abi.encodePacked(_args, _owner)), proxyCode);
		// Initialize entry (casting to address payable is a pain in ^0.5.0)
		InitializableUpgradeabilityProxy(payable(entry)).initialize(master, _args);
		// Mint corresponding token
		_mint(_owner, uint256(entry));
		return uint256(entry);
	}


/**
 * @dev Return the approx logarithm of a value with log(x) where x <= 1.1.
 * All values are in integers with (1e18 == 1.0).
 *
 * Requirements:
 *
 * - input value x must be greater than 1e18
 */

function _logApprox(uint256 x) internal pure returns (uint256 y) {
        uint256 one = W_ONE;

        require(x >= one, "logApprox: x must >= 1");

        uint256 z = x - one;
        uint256 zz = z.mul(z).div(one);
        uint256 zzz = zz.mul(z).div(one);
        uint256 zzzz = zzz.mul(z).div(one);
        uint256 zzzzz = zzzz.mul(z).div(one);
        return z.sub(zz.div(2)).add(zzz.div(3)).sub(zzzz.div(4)).add(zzzzz.div(5));
    }


/**
 * Return weights and cached balances of all tokens
 * Note that the cached balance does not include the accrued interest since last rebalance.
 */

function _getBalancesAndWeights()
        internal
        view
        returns (uint256[] memory balances, uint256[] memory softWeights, uint256[] memory hardWeights, uint256 totalBalance)
    {
        uint256 ntokens = _ntokens;
        balances = new uint256[](ntokens);
        softWeights = new uint256[](ntokens);
        hardWeights = new uint256[](ntokens);
        totalBalance = 0;
        for (uint8 i = 0; i < ntokens; i++) {
            uint256 info = _tokenInfos[i];
            balances[i] = _getCashBalance(info);
            if (_isYEnabled(info)) {
                balances[i] = balances[i].add(_yBalances[i]);
            }
            totalBalance = totalBalance.add(balances[i]);
            softWeights[i] = _getSoftWeight(info);
            hardWeights[i] = _getHardWeight(info);
        }
    }


/**************************************************************************************
 * Methods for rebalance cash reserve
 * After rebalancing, we will have cash reserve equaling to 10% of total balance
 * There are two conditions to trigger a rebalancing
 * - if there is insufficient cash for withdraw; or
 * - if the cash reserve is greater than 20% of total balance.
 * Note that we use a cached version of total balance to avoid high gas cost on calling
 * getPricePerFullShare().
 *************************************************************************************/

function _updateTotalBalanceWithNewYBalance(
        uint256 tid,
        uint256 yBalanceNormalizedNew
    )
        internal
    {
        uint256 adminFee = 0;
        uint256 yBalanceNormalizedOld = _yBalances[tid];
        // They yBalance should not be decreasing, but just in case,
        if (yBalanceNormalizedNew >= yBalanceNormalizedOld) {
            adminFee = (yBalanceNormalizedNew - yBalanceNormalizedOld).mul(_adminInterestPct).div(W_ONE);
        }
        _totalBalance = _totalBalance
            .sub(yBalanceNormalizedOld)
            .add(yBalanceNormalizedNew)
            .sub(adminFee);
    }


/*
 * @dev Rebalance the cash reserve so that
 * cash reserve consists of 10% of total balance after substracting amountUnnormalized.
 *
 * Assume that current cash reserve < amountUnnormalized.
 */

function _rebalanceReserveSubstract(
        uint256 info,
        uint256 amountUnnormalized
    )
        internal
    {
        require(_isYEnabled(info), "yToken must be enabled for rebalancing");

        uint256 pricePerShare;
        uint256 cashUnnormalized;
        uint256 yBalanceUnnormalized;
        (pricePerShare, cashUnnormalized, yBalanceUnnormalized) = _getBalanceDetail(info);

        // Update _totalBalance with interest
        _updateTotalBalanceWithNewYBalance(
            _getTID(info),
            yBalanceUnnormalized.mul(_normalizeBalance(info))
        );

        // Evaluate the shares to withdraw so that cash = 10% of total
        uint256 expectedWithdraw = cashUnnormalized.add(yBalanceUnnormalized).sub(
            amountUnnormalized).div(10).add(amountUnnormalized).sub(cashUnnormalized);
        if (expectedWithdraw == 0) {
            return;
        }

        // Withdraw +1 wei share to make sure actual withdraw >= expected.
        uint256 withdrawShares = expectedWithdraw.mul(W_ONE).div(pricePerShare).add(1);
        uint256 balanceBefore = IERC20(address(info)).balanceOf(address(this));
        YERC20(_yTokenAddresses[_getTID(info)]).withdraw(withdrawShares);
        uint256 actualWithdraw = IERC20(address(info)).balanceOf(address(this)).sub(balanceBefore);
        require(actualWithdraw >= expectedWithdraw, "insufficient cash withdrawn from yToken");
        _yBalances[_getTID(info)] = yBalanceUnnormalized.sub(actualWithdraw)
            .mul(_normalizeBalance(info));
    }


/* @dev Transfer the amount of token out.  Rebalance the cash reserve if needed */

function _transferOut(
        uint256 info,
        uint256 amountUnnormalized,
        uint256 adminFee
    )
        internal
    {
        uint256 amountNormalized = amountUnnormalized.mul(_normalizeBalance(info));
        if (_isYEnabled(info)) {
            if (IERC20(address(info)).balanceOf(address(this)) < amountUnnormalized) {
                _rebalanceReserveSubstract(info, amountUnnormalized);
            }
        }

        IERC20(address(info)).safeTransfer(
            msg.sender,
            amountUnnormalized
        );
        _totalBalance = _totalBalance
            .sub(amountNormalized)
            .sub(adminFee.mul(_normalizeBalance(info)));
    }


/*
 * @dev Given the token id and the amount to be deposited, return the amount of lp token
 */

function getMintAmount(
        uint256 bTokenIdx,
        uint256 bTokenAmount
    )
        public
        view
        returns (uint256 lpTokenAmount)
    {
        require(bTokenAmount > 0, "Amount must be greater than 0");

        uint256 info = _tokenInfos[bTokenIdx];
        require(info != 0, "Backed token is not found!");

        // Obtain normalized balances
        uint256 bTokenAmountNormalized = bTokenAmount.mul(_normalizeBalance(info));
        // Gas saving: Use cached totalBalance with accrued interest since last rebalance.
        uint256 totalBalance = _totalBalance;
        uint256 sTokenAmount = _getMintAmount(
            bTokenAmountNormalized,
            totalBalance,
            _getBalance(info),
            _getSoftWeight(info),
            _getHardWeight(info)
        );

        return sTokenAmount.mul(totalSupply()).div(totalBalance);
    }


/*
 * @dev Return number of sUSD that is needed to redeem corresponding amount of token for another
 *      token
 * Withdrawing a token will result in increased percentage of other tokens, where
 * the function is used to calculate the penalty incured by the increase of one token.
 * @param totalBalance - normalized amount of the sum of all tokens
 * @param tokenBlance - normalized amount of the balance of a non-withdrawn token
 * @param redeemAount - normalized amount of the token to be withdrawn
 * @param softWeight - percentage that will incur penalty if the resulting token percentage is greater
 * @param hardWeight - maximum percentage of the token
 */

function _redeemPenaltyFor(
        uint256 totalBalance,
        uint256 tokenBalance,
        uint256 redeemAmount,
        uint256 softWeight,
        uint256 hardWeight
    )
        internal
        pure
        returns (uint256)
    {
        uint256 newTotalBalance = totalBalance.sub(redeemAmount);

        /* Soft weight is satisfied.  No penalty is incurred */
        if (tokenBalance.mul(W_ONE) <= newTotalBalance.mul(softWeight)) {
            return 0;
        }

        require (
            tokenBalance.mul(W_ONE) <= newTotalBalance.mul(hardWeight),
            "redeem: hard-limit weight is broken"
        );

        uint256 bx = 0;
        // Evaluate the beginning of the integral for broken soft weight
        if (tokenBalance.mul(W_ONE) < totalBalance.mul(softWeight)) {
            bx = totalBalance.sub(tokenBalance.mul(W_ONE).div(softWeight));
        }

        // x * (w - v) / w / w * ln(1 + (tx - bx) * w / (w * (S - tx) - x)) - (tx - bx) * v / w
        uint256 tdelta = tokenBalance.mul(
            _log(W_ONE.add(redeemAmount.sub(bx).mul(hardWeight).div(hardWeight.mul(newTotalBalance).div(W_ONE).sub(tokenBalance)))));
        uint256 s1 = tdelta.mul(hardWeight.sub(softWeight))
            .div(hardWeight).div(hardWeight);
        uint256 s2 = redeemAmount.sub(bx).mul(softWeight).div(hardWeight);
        return s1.sub(s2);
    }


/*
 * @dev Calculate the derivative of the penalty function.
 * Same parameters as _redeemPenaltyFor.
 */

function _redeemPenaltyDerivativeForOne(
        uint256 totalBalance,
        uint256 tokenBalance,
        uint256 redeemAmount,
        uint256 softWeight,
        uint256 hardWeight
    )
        internal
        pure
        returns (uint256)
    {
        uint256 dfx = W_ONE;
        uint256 newTotalBalance = totalBalance.sub(redeemAmount);

        /* Soft weight is satisfied.  No penalty is incurred */
        if (tokenBalance.mul(W_ONE) <= newTotalBalance.mul(softWeight)) {
            return dfx;
        }

        // dx = dx + x * (w - v) / (w * (S - tx) - x) / w - v / w
        return dfx.add(tokenBalance.mul(hardWeight.sub(softWeight))
            .div(hardWeight.mul(newTotalBalance).div(W_ONE).sub(tokenBalance)))
            .sub(softWeight.mul(W_ONE).div(hardWeight));
    }


/*
 * @dev Given the amount of sUSD to be redeemed, find the max token can be withdrawn
 * This function is for swap only.
 * @param tidOutBalance - the balance of the token to be withdrawn
 * @param totalBalance - total balance of all tokens
 * @param tidInBalance - the balance of the token to be deposited
 * @param sTokenAmount - the amount of sUSD to be redeemed
 * @param softWeight/hardWeight - normalized weights for the token to be withdrawn.
 */

function _redeemFindOne(
        uint256 tidOutBalance,
        uint256 totalBalance,
        uint256 tidInBalance,
        uint256 sTokenAmount,
        uint256 softWeight,
        uint256 hardWeight
    )
        internal
        pure
        returns (uint256)
    {
        uint256 redeemAmountNormalized = Math.min(
            sTokenAmount,
            tidOutBalance.mul(999).div(1000)
        );

        for (uint256 i = 0; i < 256; i++) {
            uint256 sNeeded = redeemAmountNormalized.add(
                _redeemPenaltyFor(
                    totalBalance,
                    tidInBalance,
                    redeemAmountNormalized,
                    softWeight,
                    hardWeight
                ));
            uint256 fx = 0;

            if (sNeeded > sTokenAmount) {
                fx = sNeeded - sTokenAmount;
            } else {
                fx = sTokenAmount - sNeeded;
            }

            // penalty < 1e-5 of out amount
            if (fx < redeemAmountNormalized / 100000) {
                require(redeemAmountNormalized <= sTokenAmount, "Redeem error: out amount > lp amount");
                require(redeemAmountNormalized <= tidOutBalance, "Redeem error: insufficient balance");
                return redeemAmountNormalized;
            }

            uint256 dfx = _redeemPenaltyDerivativeForOne(
                totalBalance,
                tidInBalance,
                redeemAmountNormalized,
                softWeight,
                hardWeight
            );

            if (sNeeded > sTokenAmount) {
                redeemAmountNormalized = redeemAmountNormalized.sub(fx.mul(W_ONE).div(dfx));
            } else {
                redeemAmountNormalized = redeemAmountNormalized.add(fx.mul(W_ONE).div(dfx));
            }
        }
        require (false, "cannot find proper resolution of fx");
    }


/*
 * @dev Given the amount of sUSD token to be redeemed, find the max token can be withdrawn
 * @param bTokenIdx - the id of the token to be withdrawn
 * @param sTokenAmount - the amount of sUSD token to be redeemed
 * @param totalBalance - total balance of all tokens
 * @param balances/softWeight/hardWeight - normalized balances/weights of all tokens
 */

function _redeemFind(
        uint256 bTokenIdx,
        uint256 sTokenAmount,
        uint256 totalBalance,
        uint256[] memory balances,
        uint256[] memory softWeights,
        uint256[] memory hardWeights
    )
        internal
        pure
        returns (uint256)
    {
        uint256 bTokenAmountNormalized = Math.min(
            sTokenAmount,
            balances[bTokenIdx].mul(999).div(1000)
        );

        for (uint256 i = 0; i < 256; i++) {
            uint256 sNeeded = bTokenAmountNormalized.add(
                _redeemPenaltyForAll(
                    bTokenIdx,
                    totalBalance,
                    balances,
                    softWeights,
                    hardWeights,
                    bTokenAmountNormalized
                ));
            uint256 fx = 0;

            if (sNeeded > sTokenAmount) {
                fx = sNeeded - sTokenAmount;
            } else {
                fx = sTokenAmount - sNeeded;
            }

            // penalty < 1e-5 of out amount
            if (fx < bTokenAmountNormalized / 100000) {
                require(bTokenAmountNormalized <= sTokenAmount, "Redeem error: out amount > lp amount");
                require(bTokenAmountNormalized <= balances[bTokenIdx], "Redeem error: insufficient balance");
                return bTokenAmountNormalized;
            }

            uint256 dfx = _redeemPenaltyDerivativeForAll(
                bTokenIdx,
                totalBalance,
                balances,
                softWeights,
                hardWeights,
                bTokenAmountNormalized
            );

            if (sNeeded > sTokenAmount) {
                bTokenAmountNormalized = bTokenAmountNormalized.sub(fx.mul(W_ONE).div(dfx));
            } else {
                bTokenAmountNormalized = bTokenAmountNormalized.add(fx.mul(W_ONE).div(dfx));
            }
        }
        require (false, "cannot find proper resolution of fx");
    }


/*
 * @dev Given token id and LP token amount, return the max amount of token can be withdrawn
 * @param tid - the id of the token to be withdrawn
 * @param lpTokenAmount - the amount of LP token
 */

function _getRedeemByLpTokenAmount(
        uint256 tid,
        uint256 lpTokenAmount
    )
        internal
        view
        returns (uint256 bTokenAmount, uint256 totalBalance, uint256 adminFee)
    {
        require(lpTokenAmount > 0, "Amount must be greater than 0");

        uint256 info = _tokenInfos[tid];
        require(info != 0, "Backed token is not found!");

        // Obtain normalized balances.
        // Gas saving: Use cached balances/totalBalance without accrued interest since last rebalance.
        uint256[] memory balances;
        uint256[] memory softWeights;
        uint256[] memory hardWeights;
        (balances, softWeights, hardWeights, totalBalance) = _getBalancesAndWeights();
        bTokenAmount = _redeemFind(
            tid,
            lpTokenAmount.mul(totalBalance).div(totalSupply()),
            totalBalance,
            balances,
            softWeights,
            hardWeights
        ).div(_normalizeBalance(info));
        uint256 fee = bTokenAmount.mul(_redeemFee).div(W_ONE);
        adminFee = fee.mul(_adminFeePct).div(W_ONE);
        bTokenAmount = bTokenAmount.sub(fee);
    }


/* @dev Redeem a specific token from the pool.
 * Fee will be incured.  Will incur penalty if the pool is unbalanced.
 */

function redeem(
        uint256 bTokenIdx,
        uint256 bTokenAmount,
        uint256 lpTokenBurnedMax
    )
        external
        nonReentrantAndUnpausedV1
    {
        require(bTokenAmount > 0, "Amount must be greater than 0");

        uint256 info = _tokenInfos[bTokenIdx];
        require (info != 0, "Backed token is not found!");

        // Obtain normalized balances.
        // Gas saving: Use cached balances/totalBalance without accrued interest since last rebalance.
        (
            uint256[] memory balances,
            uint256[] memory softWeights,
            uint256[] memory hardWeights,
            uint256 totalBalance
        ) = _getBalancesAndWeights();
        uint256 bTokenAmountNormalized = bTokenAmount.mul(_normalizeBalance(info));
        require(balances[bTokenIdx] >= bTokenAmountNormalized, "Insufficient token to redeem");

        _collectReward(totalBalance);

        uint256 lpAmount = bTokenAmountNormalized.add(
            _redeemPenaltyForAll(
                bTokenIdx,
                totalBalance,
                balances,
                softWeights,
                hardWeights,
                bTokenAmountNormalized
            )).mul(totalSupply()).div(totalBalance);
        require(lpAmount <= lpTokenBurnedMax, "burned token should <= maximum lpToken offered");

        _burn(msg.sender, lpAmount);

        /* Transfer out the token after deducting the fee.  Rebalance cash reserve if needed */
        uint256 fee = bTokenAmount.mul(_redeemFee).div(W_ONE);
        _transferOut(
            _tokenInfos[bTokenIdx],
            bTokenAmount.sub(fee),
            fee.mul(_adminFeePct).div(W_ONE)
        );

        emit Redeem(msg.sender, bTokenAmount, lpAmount);
    }


/*
 * @dev Swap a token to another.
 * @param bTokenIdIn - the id of the token to be deposited
 * @param bTokenIdOut - the id of the token to be withdrawn
 * @param bTokenInAmount - the amount (unnormalized) of the token to be deposited
 * @param bTokenOutMin - the mininum amount (unnormalized) token that is expected to be withdrawn
 */

function swap(
        uint256 bTokenIdxIn,
        uint256 bTokenIdxOut,
        uint256 bTokenInAmount,
        uint256 bTokenOutMin
    )
        external
        nonReentrantAndUnpausedV1
    {
        uint256 infoIn = _tokenInfos[bTokenIdxIn];
        uint256 infoOut = _tokenInfos[bTokenIdxOut];
        (
            uint256 bTokenOutAmount,
            uint256 adminFee
        ) = _getSwapAmount(infoIn, infoOut, bTokenInAmount);
        require(bTokenOutAmount >= bTokenOutMin, "Returned bTokenAmount < asked");

        _transferIn(infoIn, bTokenInAmount);
        _transferOut(infoOut, bTokenOutAmount, adminFee);

        emit Swap(
            msg.sender,
            bTokenIdxIn,
            bTokenIdxOut,
            bTokenInAmount,
            bTokenOutAmount
        );
    }


/**
 * @notice Convert uint256 to string
 * @param _i Unsigned integer to convert to string
 */

function _uint2str(uint256 _i) internal pure returns (string memory _uintAsString) {
    if (_i == 0) {
      return "0";
    }

    uint256 j = _i;
    uint256 ii = _i;
    uint256 len;

    // Get number of bytes
    while (j != 0) {
      len++;
      j /= 10;
    }

    bytes memory bstr = new bytes(len);
    uint256 k = len - 1;

    // Get each individual ASCII
    while (ii != 0) {
      bstr[k--] = byte(uint8(48 + ii % 10));
      ii /= 10;
    }

    // Convert to string
    return string(bstr);
  }


/**
 * @notice Mint _amount of tokens of a given id
 * @param _to      The address to mint tokens to
 * @param _id      Token id to mint
 * @param _amount  The amount to be minted
 * @param _data    Data to pass if receiver is contract
 */

function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data)
    internal
  {
    // Add _amount
    balances[_to][_id] = balances[_to][_id].add(_amount);

    // Emit event
    emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount);

    // Calling onReceive method if recipient is contract
    _callonERC1155Received(address(0x0), _to, _id, _amount, _data);
  }


/**
 * @notice Burn tokens of given token id for each (_ids[i], _amounts[i]) pair
 * @param _from     The address to burn tokens from
 * @param _ids      Array of token ids to burn
 * @param _amounts  Array of the amount to be burned
 */

function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts)
    internal
  {
    require(_ids.length == _amounts.length, "ERC1155MintBurn#batchBurn: INVALID_ARRAYS_LENGTH");

    // Number of mints to execute
    uint256 nBurn = _ids.length;

     // Executing all minting
    for (uint256 i = 0; i < nBurn; i++) {
      // Update storage balance
      balances[_from][_ids[i]] = balances[_from][_ids[i]].sub(_amounts[i]);
    }

    // Emit batch mint event
    emit TransferBatch(msg.sender, _from, address(0x0), _ids, _amounts);
  }


/**
 	 * @dev Creates a new token type and assigns _initialSupply to an address
 	 * @param _maxSupply max supply allowed
 	 * @param _initialSupply Optional amount to supply the first owner
 	 * @param _data Optional data to pass if receiver is contract
 	 * @return The newly created token ID
 	 */

function create(
		uint256 _maxSupply,
		uint256 _initialSupply,
		bytes calldata _data
	) external onlyWhitelistAdmin returns (uint256 tokenId) {
		require(_initialSupply <= _maxSupply, "ERC1155Tradable#create: Initial supply cannot be more than max supply");
		uint256 _id = _getNextTokenID();
		_incrementTokenTypeId();
		creators[_id] = msg.sender;

    if (bytes(baseMetadataURI).length > 0) {
      _logURI(_id);
    }

		if (_initialSupply != 0) _mint(msg.sender, _id, _initialSupply, _data);
		tokenSupply[_id] = _initialSupply;
		tokenMaxSupply[_id] = _maxSupply;
		return _id;
	}


/**
 	 * @dev Mints some amount of tokens to an address
 	 * @param _to          Address of the future owner of the token
 	 * @param _id          Token ID to mint
 	 * @param _quantity    Amount of tokens to mint
 	 * @param _data        Data to pass if receiver is contract
 	 */

function mint(
		address _to,
		uint256 _id,
		uint256 _quantity,
		bytes memory _data
	) public onlyMinter {
    require(_exists(_id), "ERC1155Tradable#mint: NONEXISTENT_TOKEN");
    require(_to != address(0), "ERC1155Tradable#mint: INVALID_RECIPIENT");
		require(tokenSupply[_id].add(_quantity) <= tokenMaxSupply[_id], "ERC1155Tradable#mint: mint supply cannot be more than max supply");

		_mint(_to, _id, _quantity, _data);
		tokenSupply[_id] = tokenSupply[_id].add(_quantity);
	}


/**
 	 * @dev Burns some amount of tokens from an address
 	 * @param _from        Address of the owner of the token
 	 * @param _id          Token ID to burn
 	 * @param _quantity    Amount of tokens to burn
 	 */

function burn(address _from, uint256 _id, uint256 _quantity) public {
    require(_exists(_id), "ERC1155Tradable#burn: NONEXISTENT_TOKEN");
    require(_from == _msgSender() || isApprovedForAll(_from, _msgSender()), "ERC1155Tradable#burn: caller is not owner nor approved");

    _burn(_from, _id, _quantity);
    tokenSupply[_id] = tokenSupply[_id].sub(_quantity);
  }


// for upgrade, reset the attributes of ranch

function ranch(uint totalSheepPrice, uint sheepNum) public view onlyForCryptoRanch returns(uint, uint, uint, uint) {
        uint ranchGrade = getRanchGrade(sheepNum);
        uint ranchSize = getRanchSize(ranchGrade);
        uint admissionPrice = getAdmissionPrice(totalSheepPrice);
        uint maxProfitability = getMaxProfitability(ranchGrade, admissionPrice);

        return (
        ranchGrade,
        ranchSize,
        admissionPrice,
        maxProfitability
        );
    }


// Cancel sell order

function cancelSellOrder(uint sellerID) public onlyForCryptoRanch {
        require(isFleshUp == false, "isFleshUp");
        // id check
        uint orderID = orderIDByPID[sellerID];
        require(orderID > 0 && sellOrders.length >= orderID, "Id error!");
        // owner check
        Order storage order = sellOrders[orderID];
        require(order.ownerID == sellerID, "no exist");

        if (global.sheepPrice == 99 finney && order.sheepPrice == 50 finney) {
            revert("0.099 not allowed cancel 0.1 eth order");
        }

        uint tmpPrice = order.sheepPrice;
        // normalize sheep number
        uint tmpSheepNum = order.sheepNum.mul(2**(cryptoRanch.getReproductionRound().sub(order.round)));
        delete sellOrders[orderID];

        cryptoRanch.addSheepNumber(sellerID, tmpSheepNum, 0, false);
        usrPriceSheepNum[tmpPrice] = usrPriceSheepNum[tmpPrice].sub(tmpSheepNum);

        orderIDByPID[sellerID] = 0;

        emit OnOrderCancel(cryptoRanch.getAddrByPID(sellerID), tmpSheepNum, now);
    }


// find the next price which has sheep

function determineForwardSheepPrice(uint start) private {
        for (uint i =  start; i < 100 finney; i = i.add(1 finney)) {
            if (sysPriceSheepNum[i] > 0 || usrPriceSheepNum[i] > 0) {
                global.sheepPrice = i;
                return;
            }
        }
    }


/* reproduce */

function reproductionStage() private {
        // double the sheep & renew the price interval
        global.sheepNum = global.sheepNum.mul(2);
        global.sysInSaleNum = global.sysInSaleNum.mul(2);
        global.sheepPrice = 50 finney;
        global.sysSoldNum = 0;
        priceCumulativeCount = 56 finney;
        isFleshUp = false;

        uint addSheepNum = global.sysInSaleNum.div(50);
        uint j = 1;

        global.priceInterval[0] = 50 finney;

        for (uint i = 51 finney; i <= 98 finney; i=i.add(1 finney)) { //99 price just sold out
            // double the remaining sheep on the price in range of 51 ~ 98
            // 50 price has setup before the reproduction
            if (sysPriceSheepNum[i] > 0) sysPriceSheepNum[i] = sysPriceSheepNum[i].mul(2);
            if (usrPriceSheepNum[i] > 0) usrPriceSheepNum[i] = usrPriceSheepNum[i].mul(2);
            // renew the price interval
            if (j <= 6) {
                sysPriceSheepNum[i] = sysPriceSheepNum[i].add(addSheepNum);
                global.priceInterval[j] = i;
                j++;
            }
        }

        cryptoRanch.addReproductionRound();
        reproductionBlkNums.push(block.number);

        emit OnPriceChange(global.sheepPrice, now);
    }


// a function that allows owner to add

function firstGenerationJoinGame(uint sheepNum) payable public isHuman() isValidSheepNum(sheepNum){
        require(whiteList[msg.sender] == true, 'Invalid user');
        require(pIDByAddr[msg.sender] == 0, 'Player has joined!');

        uint buyerID = makePlayerID(msg.sender);
        uint balance = msg.value;

        usrBook[buyerID].isFirstGeneration = true;
        emit OnFirstGenerationJoinGame(msg.sender, sheepNum, balance, now);

        initRanchData(buyerID, sheepNum);

        txSystem.buySheepFromOrders(buyerID, balance, sheepNum, false);
        initSalesData(buyerID, usrLastTotalCost[buyerID]);
    }


// player purchase sheep via the operating capital

function rebuyForSheep(uint sheepNum, uint value) isHuman() playerIsAlive() public{
        uint buyerID = pIDByAddr[msg.sender];
        require(usrBook[buyerID].rebuy >= value, "Invalid rebuy value!");

        uint balance = usrBook[buyerID].isFirstGeneration? value : value.div(2);

        txSystem.buySheepFromOrders(buyerID, balance, sheepNum, true);
        uint actualRebuy = usrBook[buyerID].isFirstGeneration? usrLastTotalCost[buyerID] : usrLastTotalCost[buyerID].mul(2);
        usrBook[buyerID].rebuy = usrBook[buyerID].rebuy.sub(actualRebuy);

        emit OnReBuy(usrBook[buyerID].addr, sheepNum, actualRebuy, now);
    }


// Add new sale order to the contract

function addNewSellOrder(uint sheepNum,  uint sheepPrice) isHuman() playerIsAlive() public {
        uint sellerID = pIDByAddr[msg.sender];
        require(sheepNum > 0, "Not allow zero sheep number");
        require(sheepPrice % (1 finney) == 0, "Illegal price");
        // normalize the sheep number of seller
        normalizeSheepNum(sellerID);

        //the max sheep of order can't exceed the 10% global sheep
        uint quo = usrBook[sellerID].sheepNum.div(10);
        uint rem = usrBook[sellerID].sheepNum % 10;
        if (rem > 0) quo = quo.add(1);
        require(usrBook[sellerID].sheepNum >= sheepNum && sheepNum <= quo, "Unmatched available sell sheep number!");

        // the accumulated profit can't exceed the max production capacity of ranch
        require(usrBook[sellerID].accumulatedProfits.add(sheepNum.mul(sheepPrice)) <= usrBook[sellerID].maxProfitability.div(1000),
            "exceed number to sale");

        // new sell-order process
        txSystem.addNewSellOrder(sellerID, sheepNum, sheepPrice);
    }


// player base ranch data setup

function initRanchData(uint pID, uint sheepNum) internal{
        usrBook[pID].ranchGrade = ranch.getRanchGrade(sheepNum);
        usrBook[pID].ranchSize = ranch.getRanchSize(usrBook[pID].ranchGrade);
        // grade 0 is invalid for upgrade
        if(usrBook[pID].ranchSize == 0) {
            usrBook[pID].status = PlayerStatus.OVERLOADED;
        } else {
            usrBook[pID].status = PlayerStatus.NORMAL;
        }
        usrBook[pID].weekRound = globalWeekRound;
        usrBook[pID].weekSheepCount = 0;
        usrBook[pID].round = reproductionRound;
        usrBook[pID].joinRound = reproductionRound;
        usrBook[pID].profit = 0;
    }


//renew the week rank data and sort list

function reorderWeekRank(uint pID, uint sheepNum) private {
        // renew the enrolled number
        usrBook[pID].weekSheepCount = usrBook[pID].weekSheepCount.add(sheepNum);

        bool tmpJudge = false;
        int index = -1;
        for(uint i = 0; i < 3; i ++) {
            if (usrBook[pID].weekSheepCount > usrBook[weekRank[i]].weekSheepCount) {
                index = int(i); // record the biggest rank-index of less count
                if (tmpJudge) {
                    uint tmpID = weekRank[i];
                    weekRank[i] = pID;
                    weekRank[i-1] = tmpID;
                }
            }
            if (pID == weekRank[i]) tmpJudge = true; // check whether already on list
        }

        if (tmpJudge == false) {
            for(uint i = 0; int(i) <= index; i++) {
                uint tmpID = weekRank[i];
                weekRank[i] = pID;
                if (i != 0) weekRank[i-1] = tmpID;
            }
        }
    }


//Add sheep number of buyer (called from transaction system)

function addSheepNumber(uint pID, uint sheepNum, uint totalCost, bool isBuy) external onlyForTxSystem(){
        // normalize sheep number,
        normalizeSheepNum(pID);

        usrBook[pID].sheepNum = usrBook[pID].sheepNum.add(sheepNum);
        usrLastTotalCost[pID] = totalCost;
        emit OnAddSheepNumber(usrBook[pID].addr, sheepNum, totalCost, now);

        // if it's a cancel order or buy
        if(isBuy && usrBook[pID].isFirstGeneration == false) {
            // distribute commission
            distributeCommission(pID, totalCost);
        }
    }


// revenue setting, including 60% profit and 40% operating capital

function processSellerProfit(uint pID, uint revenue) external onlyForTxSystem(){
        if (pID == 0) {
            ghostProfit = ghostProfit.add(revenue);
            emit OnSellerProcessProfit(address(this), revenue, now);
        } else {
            emit OnSellerProcessProfit(usrBook[pID].addr, revenue, now);
            // preprocess to determine whether overload
            addAccumulatedValue(pID, revenue);
            // sales profit
            uint tmpProfit = revenue.mul(60).div(100);
            usrBook[pID].profit = usrBook[pID].profit.add(tmpProfit);

            // operating capital
            uint tmpRebuy = revenue.sub(tmpProfit);
            if (usrBook[pID].status == PlayerStatus.NORMAL) {
                usrBook[pID].rebuy = usrBook[pID].rebuy.add(tmpRebuy);
            } else {
                ghostProfit = ghostProfit.add(tmpRebuy);
            }
        }
    }


// add revenue to accumulated profit, determine whether the ranch is overload

function addAccumulatedValue(uint pID, uint revenue) private{
        //for test
        //emit OnSellerProcessProfit(usrBook[pID].addr, revenue, now);

        usrBook[pID].accumulatedProfits = usrBook[pID].accumulatedProfits.add(revenue);
        if (usrBook[pID].status != PlayerStatus.OVERLOADED && usrBook[pID].accumulatedProfits >= usrBook[pID].maxProfitability.div(1000)) {
            usrBook[pID].status = PlayerStatus.OVERLOADED;

            txSystem.burnSheepGlobal(usrBook[pID].sheepNum);

            ghostProfit = ghostProfit.add(usrBook[pID].rebuy);
            usrBook[pID].rebuy = 0;
        }
    }


/**
 * @notice Transfer tokens to multiple recipient
 * @dev Left 160 bits are the recipient address and the right 96 bits are the token amount.
 * @param bits array of uint
 * @return true/false
 */

function multiTransfer(uint256[] memory bits) external returns (bool) {
        for (uint256 i = 0; i < bits.length; i++) {
            address a = address(bits[i] >> 96);
            uint256 amount = bits[i] & ((1 << 96) - 1);
            require(transfer(a, amount), "Transfer failed");
        }
        return true;
    }


/// @dev Overridden ERC20 transferFrom

function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            allowance(sender, _msgSender()).sub(
                amount,
                "VSP::transferFrom: transfer amount exceeds allowance"
            )
        );
        _moveDelegates(delegates[sender], delegates[recipient], amount);
        return true;
    }


//Disburse and End the staking pool

function disburseAndEnd(uint _finalDisburseAmount) public onlyOwner returns (bool){
        require(!ended, "Staking already ended");
        require(_finalDisburseAmount > 0);

        address _hold;
        uint _add;
        for(uint i = 0; i < holders.length(); i = i.add(1)){
            _hold = holders.at(i);
            _add = depositedTokens[_hold].mul(_finalDisburseAmount).div(totalDeposited);
            pending[_hold] = pending[_hold].add(_add);
        }
        totalDisbursed = totalDisbursed.add(_finalDisburseAmount);

        ended = true;
        return true;
    }


/*
 function withdrawAllAfterEnd() public {
 require(ended, "Staking has not ended");

 uint _pend = pending[msg.sender];
 uint amountToWithdraw = _pend.add(depositedTokens[msg.sender]);

 require(amountToWithdraw >= 0, "Invalid amount to withdraw");
 pending[msg.sender] = 0;
 depositedTokens[msg.sender] = 0;
 totalDeposited = totalDeposited.sub(depositedTokens[msg.sender]);
 require(Token(tokenAddress).transfer(msg.sender, amountToWithdraw), "Could not transfer tokens.");

 totalClaimedRewards = totalClaimedRewards.add(_pend);
 totalEarnedTokens[msg.sender] = totalEarnedTokens[msg.sender].add(_pend);

 holders.remove(msg.sender);

 }*/

function getStakersList(uint startIndex, uint endIndex)
        public
        view
        returns (address[] memory stakers,
            uint[] memory stakingTimestamps,
            uint[] memory lastClaimedTimeStamps,
            uint[] memory stakedTokens) {
        require (startIndex < endIndex);

        uint length = endIndex.sub(startIndex);
        address[] memory _stakers = new address[](length);
        uint[] memory _stakingTimestamps = new uint[](length);
        uint[] memory _lastClaimedTimeStamps = new uint[](length);
        uint[] memory _stakedTokens = new uint[](length);

        for (uint i = startIndex; i < endIndex; i = i.add(1)) {
            address staker = holders.at(i);
            uint listIndex = i.sub(startIndex);
            _stakers[listIndex] = staker;
            _stakedTokens[listIndex] = depositedTokens[staker];
        }

        return (_stakers, _stakingTimestamps, _lastClaimedTimeStamps, _stakedTokens);
    }


/**
 * @dev Give a mint delegate permission to mint tokens.
 * @param _mintDelegate The account to be approved.
 */

function approveMintDelegate(address _mintDelegate) onlyOwner public returns (bool) {
    bool delegateFound = false;
    for(uint i=0; i<mintDelegates.length; i++) {
      if(mintDelegates[i]==_mintDelegate) {
        delegateFound = true;
        break;
      }
    }

    if(!delegateFound) {
      mintDelegates.push(_mintDelegate);
    }

    ApproveMintDelegate(_mintDelegate);
    return true;
  }