/**
 * @dev Transfer the amount of tokens on behalf of the address 'from' to the address 'to'.
 * @param from Token holder (or 'address(0)' to set from to 'msg.sender').
 * @param to Token recipient.
 * @param value Number of tokens to transfer.
 * @param data Information attached to the transfer, and intended for the token holder ('from').
 */

function transferFromWithData(address from, address to, uint256 value, bytes calldata data) external override virtual {
    require( _isOperator(msg.sender, from)
      || (value <= _allowed[from][msg.sender]), "53"); // 0x53	insufficient allowance

    if(_allowed[from][msg.sender] >= value) {
      _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
    } else {
      _allowed[from][msg.sender] = 0;
    }

    _transferByDefaultPartitions(msg.sender, from, to, value, data);
  }


/**
 * @dev Transfer tokens from a specific partition through an operator.
 * @param partition Name of the partition.
 * @param from Token holder.
 * @param to Token recipient.
 * @param value Number of tokens to transfer.
 * @param data Information attached to the transfer. [CAN CONTAIN THE DESTINATION PARTITION]
 * @param operatorData Information attached to the transfer, by the operator.
 * @return Destination partition.
 */

function operatorTransferByPartition(
    bytes32 partition,
    address from,
    address to,
    uint256 value,
    bytes calldata data,
    bytes calldata operatorData
  )
    external
    override
    returns (bytes32)
  {
    //We want to check if the msg.sender is an authorized operator for `from`
    //(msg.sender == from OR msg.sender is authorized by from OR msg.sender is a controller if this token is controlable)
    //OR
    //We want to check if msg.sender is an `allowed` operator/spender for `from`
    require(_isOperatorForPartition(partition, msg.sender, from)
      || (value <= _allowedByPartition[partition][from][msg.sender]), "53"); // 0x53	insufficient allowance

    if(_allowedByPartition[partition][from][msg.sender] >= value) {
      _allowedByPartition[partition][from][msg.sender] = _allowedByPartition[partition][from][msg.sender].sub(value);
    } else {
      _allowedByPartition[partition][from][msg.sender] = 0;
    }

    return _transferByPartition(partition, msg.sender, from, to, value, data, operatorData);
  }


/**
 * @dev Redeem the amount of tokens on behalf of the address from.
 * @param from Token holder whose tokens will be redeemed (or address(0) to set from to msg.sender).
 * @param value Number of tokens to redeem.
 * @param data Information attached to the redemption.
 */

function redeemFrom(address from, uint256 value, bytes calldata data)
    external
    override
    virtual
  {
    require(_isOperator(msg.sender, from)
      || (value <= _allowed[from][msg.sender]), "53"); // 0x53	insufficient allowance

    if(_allowed[from][msg.sender] >= value) {
      _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
    } else {
      _allowed[from][msg.sender] = 0;
    }

    _redeemByDefaultPartitions(msg.sender, from, value, data);
  }


/**
 * @dev Redeem tokens of a specific partition.
 * @param partition Name of the partition.
 * @param tokenHolder Address for which we want to redeem tokens.
 * @param value Number of tokens redeemed
 * @param operatorData Information attached to the redemption, by the operator.
 */

function operatorRedeemByPartition(bytes32 partition, address tokenHolder, uint256 value, bytes calldata operatorData)
    external
    override
  {
    require(_isOperatorForPartition(partition, msg.sender, tokenHolder) || value <= _allowedByPartition[partition][tokenHolder][msg.sender], "58"); // 0x58	invalid operator (transfer agent)

    if(_allowedByPartition[partition][tokenHolder][msg.sender] >= value) {
      _allowedByPartition[partition][tokenHolder][msg.sender] = _allowedByPartition[partition][tokenHolder][msg.sender].sub(value);
    } else {
      _allowedByPartition[partition][tokenHolder][msg.sender] = 0;
    }

    _redeemByPartition(partition, msg.sender, tokenHolder, value, "", operatorData);
  }


/**
 * @dev Perform the transfer of tokens.
 * @param from Token holder.
 * @param to Token recipient.
 * @param value Number of tokens to transfer.
 */

function _transferWithData(
    address from,
    address to,
    uint256 value
  )
    internal
    isNotMigratedToken
  {
    require(_isMultiple(value), "50"); // 0x50	transfer failure
    require(to != address(0), "57"); // 0x57	invalid receiver
    require(_balances[from] >= value, "52"); // 0x52	insufficient balance

    _balances[from] = _balances[from].sub(value);
    _balances[to] = _balances[to].add(value);

    emit Transfer(from, to, value); // ERC20 retrocompatibility
  }


/**
 * @dev Transfer tokens from default partitions.
 * Function used for ERC20 retrocompatibility.
 * @param operator The address performing the transfer.
 * @param from Token holder.
 * @param to Token recipient.
 * @param value Number of tokens to transfer.
 * @param data Information attached to the transfer, and intended for the token holder ('from') [CAN CONTAIN THE DESTINATION PARTITION].
 */

function _transferByDefaultPartitions(
    address operator,
    address from,
    address to,
    uint256 value,
    bytes memory data
  )
    internal
  {
    require(_defaultPartitions.length != 0, "55"); // // 0x55	funds locked (lockup period)

    uint256 _remainingValue = value;
    uint256 _localBalance;

    for (uint i = 0; i < _defaultPartitions.length; i++) {
      _localBalance = _balanceOfByPartition[from][_defaultPartitions[i]];
      if(_remainingValue <= _localBalance) {
        _transferByPartition(_defaultPartitions[i], operator, from, to, _remainingValue, data, "");
        _remainingValue = 0;
        break;
      } else if (_localBalance != 0) {
        _transferByPartition(_defaultPartitions[i], operator, from, to, _localBalance, data, "");
        _remainingValue = _remainingValue - _localBalance;
      }
    }

    require(_remainingValue == 0, "52"); // 0x52	insufficient balance
  }


/**
 * @dev Retrieve the destination partition from the 'data' field.
 * By convention, a partition change is requested ONLY when 'data' starts
 * with the flag: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 * When the flag is detected, the destination tranche is extracted from the
 * 32 bytes following the flag.
 * @param fromPartition Partition of the tokens to transfer.
 * @param data Information attached to the transfer. [CAN CONTAIN THE DESTINATION PARTITION]
 * @return toPartition Destination partition.
 */

function _getDestinationPartition(bytes32 fromPartition, bytes memory data) internal pure returns(bytes32 toPartition) {
    bytes32 changePartitionFlag = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
    bytes32 flag;
    assembly {
      flag := mload(add(data, 32))
    }
    if(flag == changePartitionFlag) {
      assembly {
        toPartition := mload(add(data, 64))
      }
    } else {
      toPartition = fromPartition;
    }
  }


/**
 * @dev Remove a token from a specific partition.
 * @param from Token holder.
 * @param partition Name of the partition.
 * @param value Number of tokens to transfer.
 */

function _removeTokenFromPartition(address from, bytes32 partition, uint256 value) internal {
    _balanceOfByPartition[from][partition] = _balanceOfByPartition[from][partition].sub(value);
    _totalSupplyByPartition[partition] = _totalSupplyByPartition[partition].sub(value);

    // If the total supply is zero, finds and deletes the partition.
    if(_totalSupplyByPartition[partition] == 0) {
      uint256 index1 = _indexOfTotalPartitions[partition];
      require(index1 > 0, "50"); // 0x50	transfer failure

      // move the last item into the index being vacated
      bytes32 lastValue = _totalPartitions[_totalPartitions.length - 1];
      _totalPartitions[index1 - 1] = lastValue; // adjust for 1-based indexing
      _indexOfTotalPartitions[lastValue] = index1;

      //_totalPartitions.length -= 1;
      _totalPartitions.pop();
      _indexOfTotalPartitions[partition] = 0;
    }

    // If the balance of the TokenHolder's partition is zero, finds and deletes the partition.
    if(_balanceOfByPartition[from][partition] == 0) {
      uint256 index2 = _indexOfPartitionsOf[from][partition];
      require(index2 > 0, "50"); // 0x50	transfer failure

      // move the last item into the index being vacated
      bytes32 lastValue = _partitionsOf[from][_partitionsOf[from].length - 1];
      _partitionsOf[from][index2 - 1] = lastValue;  // adjust for 1-based indexing
      _indexOfPartitionsOf[from][lastValue] = index2;

      //_partitionsOf[from].length -= 1;
      _partitionsOf[from].pop();
      _indexOfPartitionsOf[from][partition] = 0;
    }
  }


/**
 * @dev Add a token to a specific partition.
 * @param to Token recipient.
 * @param partition Name of the partition.
 * @param value Number of tokens to transfer.
 */

function _addTokenToPartition(address to, bytes32 partition, uint256 value) internal {
    if(value != 0) {
      if (_indexOfPartitionsOf[to][partition] == 0) {
        _partitionsOf[to].push(partition);
        _indexOfPartitionsOf[to][partition] = _partitionsOf[to].length;
      }
      _balanceOfByPartition[to][partition] = _balanceOfByPartition[to][partition].add(value);

      if (_indexOfTotalPartitions[partition] == 0) {
        _totalPartitions.push(partition);
        _indexOfTotalPartitions[partition] = _totalPartitions.length;
      }
      _totalSupplyByPartition[partition] = _totalSupplyByPartition[partition].add(value);
    }
  }


/**
 * @dev Check for 'ERC1400TokensSender' user extension in ERC1820 registry and call it.
 * @param partition Name of the partition (bytes32 to be left empty for transfers where partition is not specified).
 * @param operator Address which triggered the balance decrease (through transfer or redemption).
 * @param from Token holder.
 * @param to Token recipient for a transfer and 0x for a redemption.
 * @param value Number of tokens the token holder balance is decreased by.
 * @param data Extra information.
 * @param operatorData Extra information, attached by the operator (if any).
 */

function _callSenderExtension(
    bytes32 partition,
    address operator,
    address from,
    address to,
    uint256 value,
    bytes memory data,
    bytes memory operatorData
  )
    internal
  {
    address senderImplementation;
    senderImplementation = interfaceAddr(from, ERC1400_TOKENS_SENDER);
    if (senderImplementation != address(0)) {
      IERC1400TokensSender(senderImplementation).tokensToTransfer(msg.data, partition, operator, from, to, value, data, operatorData);
    }
  }


/**
 * @dev Perform the issuance of tokens.
 * @param operator Address which triggered the issuance.
 * @param to Token recipient.
 * @param value Number of tokens issued.
 * @param data Information attached to the issuance, and intended for the recipient (to).
 */

function _issue(address operator, address to, uint256 value, bytes memory data)
    internal
    isNotMigratedToken
  {
    require(_isMultiple(value), "50"); // 0x50	transfer failure
    require(to != address(0), "57"); // 0x57	invalid receiver

    _totalSupply = _totalSupply.add(value);
    _balances[to] = _balances[to].add(value);

    emit Issued(operator, to, value, data);
    emit Transfer(address(0), to, value); // ERC20 retrocompatibility
  }


/**
 * @dev Issue tokens from a specific partition.
 * @param toPartition Name of the partition.
 * @param operator The address performing the issuance.
 * @param to Token recipient.
 * @param value Number of tokens to issue.
 * @param data Information attached to the issuance.
 */

function _issueByPartition(
    bytes32 toPartition,
    address operator,
    address to,
    uint256 value,
    bytes memory data
  )
    internal
  {
    _callMintExtension(toPartition, operator, address(0), to, value, data, "");

    _issue(operator, to, value, data);
    _addTokenToPartition(to, toPartition, value);

    _callRecipientExtension(toPartition, operator, address(0), to, value, data, "");

    emit IssuedByPartition(toPartition, operator, to, value, data, "");
  }


/**
 * @dev Perform the token redemption.
 * @param operator The address performing the redemption.
 * @param from Token holder whose tokens will be redeemed.
 * @param value Number of tokens to redeem.
 * @param data Information attached to the redemption.
 */

function _redeem(address operator, address from, uint256 value, bytes memory data)
    internal
    isNotMigratedToken
  {
    require(_isMultiple(value), "50"); // 0x50	transfer failure
    require(from != address(0), "56"); // 0x56	invalid sender
    require(_balances[from] >= value, "52"); // 0x52	insufficient balance

    _balances[from] = _balances[from].sub(value);
    _totalSupply = _totalSupply.sub(value);

    emit Redeemed(operator, from, value, data);
    emit Transfer(from, address(0), value);  // ERC20 retrocompatibility
  }


/**
 * @dev Redeem tokens of a specific partition.
 * @param fromPartition Name of the partition.
 * @param operator The address performing the redemption.
 * @param from Token holder whose tokens will be redeemed.
 * @param value Number of tokens to redeem.
 * @param data Information attached to the redemption.
 * @param operatorData Information attached to the redemption, by the operator (if any).
 */

function _redeemByPartition(
    bytes32 fromPartition,
    address operator,
    address from,
    uint256 value,
    bytes memory data,
    bytes memory operatorData
  )
    internal
  {
    require(_balanceOfByPartition[from][fromPartition] >= value, "52"); // 0x52	insufficient balance

    _callSenderExtension(fromPartition, operator, from, address(0), value, data, operatorData);
    _callRedeemExtension(fromPartition, operator, from, address(0), value, data, operatorData);

    _removeTokenFromPartition(from, fromPartition, value);
    _redeem(operator, from, value, data);

    emit RedeemedByPartition(fromPartition, operator, from, value, operatorData);
  }


/**
 * @dev Redeem tokens from a default partitions.
 * @param operator The address performing the redeem.
 * @param from Token holder.
 * @param value Number of tokens to redeem.
 * @param data Information attached to the redemption.
 */

function _redeemByDefaultPartitions(
    address operator,
    address from,
    uint256 value,
    bytes memory data
  )
    internal
  {
    require(_defaultPartitions.length != 0, "55"); // 0x55	funds locked (lockup period)

    uint256 _remainingValue = value;
    uint256 _localBalance;

    for (uint i = 0; i < _defaultPartitions.length; i++) {
      _localBalance = _balanceOfByPartition[from][_defaultPartitions[i]];
      if(_remainingValue <= _localBalance) {
        _redeemByPartition(_defaultPartitions[i], operator, from, _remainingValue, data, "");
        _remainingValue = 0;
        break;
      } else {
        _redeemByPartition(_defaultPartitions[i], operator, from, _localBalance, data, "");
        _remainingValue = _remainingValue - _localBalance;
      }
    }

    require(_remainingValue == 0, "52"); // 0x52	insufficient balance
  }


/**
 * @dev Know the reason on success or failure based on the EIP-1066 application-specific status codes.
 * @param payload Payload of the initial transaction.
 * @param partition Name of the partition.
 * @param operator The address performing the transfer.
 * @param from Token holder.
 * @param to Token recipient.
 * @param value Number of tokens to transfer.
 * @param data Information attached to the transfer. [CAN CONTAIN THE DESTINATION PARTITION]
 * @param operatorData Information attached to the transfer, by the operator (if any).
 * @return ESC (Ethereum Status Code) following the EIP-1066 standard.
 * @return Additional bytes32 parameter that can be used to define
 * application specific reason codes with additional details (for example the
 * transfer restriction rule responsible for making the transfer operation invalid).
 * @return Destination partition.
 */

function canTransfer(bytes memory payload, bytes32 partition, address operator, address from, address to, uint256 value, bytes memory data, bytes memory operatorData)
    external
    returns (bytes1, bytes32, bytes32)
  {
    if(_balanceOfByPartition[from][partition] < value) {
        return(hex"52", "", partition); // 0x52	insufficient balance
    }

    address validatorImplementation = interfaceAddr(address(this), ERC1400_TOKENS_VALIDATOR);
    if (validatorImplementation != address(0)) {
      IERC1400TokensValidator(validatorImplementation).tokensToValidate(msg.data, partition, operator, from, to, value, data, operatorData);
    }

    return(hex"00", "", partition);
  }


/**
 * @dev Set list of token controllers.
 * @param operators Controller addresses.
 */

function _setControllers(address[] memory operators) internal {
    for (uint i = 0; i<_controllers.length; i++){
      _isController[_controllers[i]] = false;
    }
    for (uint j = 0; j<operators.length; j++){
      _isController[operators[j]] = true;
    }
    _controllers = operators;
  }


/**
 * @dev Set list of token partition controllers.
 * @param partition Name of the partition.
 * @param operators Controller addresses.
 */

function _setPartitionControllers(bytes32 partition, address[] memory operators) internal {
     for (uint i = 0; i<_controllersByPartition[partition].length; i++){
       _isControllerByPartition[partition][_controllersByPartition[partition][i]] = false;
     }
     for (uint j = 0; j<operators.length; j++){
       _isControllerByPartition[partition][operators[j]] = true;
     }
     _controllersByPartition[partition] = operators;
   }


/**
 * @dev Set token extension contract address.
 * The extension contract can for example verify "ERC1400TokensValidator" or "ERC1400TokensChecker" interfaces.
 * If the extension is an "ERC1400TokensValidator", it will be called everytime a transfer is executed.
 * @param extension Address of the extension contract.
 * @param interfaceLabel Interface label of extension contract.
 * @param removeOldExtensionRoles If set to 'true', the roles of the old extension(minter, controller) will be removed extension.
 * @param addMinterRoleForExtension If set to 'true', the extension contract will be added as minter.
 * @param addControllerRoleForExtension If set to 'true', the extension contract will be added as controller.
 */

function _setTokenExtension(address extension, string memory interfaceLabel, bool removeOldExtensionRoles, bool addMinterRoleForExtension, bool addControllerRoleForExtension) internal {
    address oldExtension = interfaceAddr(address(this), interfaceLabel);

    if (oldExtension != address(0) && removeOldExtensionRoles) {
      if(isMinter(oldExtension)) {
        _removeMinter(oldExtension);
      }
      _isController[oldExtension] = false;
    }

    ERC1820Client.setInterfaceImplementation(interfaceLabel, extension);
    if(addMinterRoleForExtension && !isMinter(extension)) {
      _addMinter(extension);
    }
    if (addControllerRoleForExtension) {
      _isController[extension] = true;
    }
  }


/**
 * @dev Function to check the amount of tokens that an owner allowed to a spender.
 * @param _totalSupply total supply of the token.
 * @param _name token name e.g QASH Token.
 * @param _symbol token symbol e.g QASH.
 * @param _decimals amount of decimals.
 */

function LMToken(uint256 _totalSupply, string _name, string _symbol, uint8 _decimals) {
        balances[msg.sender] = _totalSupply;    // Give the creator all initial tokens
        totalSupply = _totalSupply;
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
    }


/**
 * @dev Get total supply of all TrueCurrency
 * Equal to deposit backed TrueCurrency plus debt backed TrueCurrency
 * @return total supply in trueCurrency
 */

function totalSupply() public view returns (uint256) {
        // if supply in opportunity finOp, return supply of deposits + debt
        // otherwise call super to return normal totalSupply
        if (opportunityRewardSupply() != 0) {
            return totalSupply_.add(opportunityTotalSupply());
        }
        return totalSupply_;
    }


/**
 * @dev Get balance of TrueCurrency including rewards for an address
 *
 * @param _who address of account to get balanceOf for
 * @return balance total balance of address including rewards
 */

function balanceOf(address _who) public view returns (uint256) {
        // if trueReward enabled, return token value of reward balance
        // otherwise call token balanceOf
        if (trueRewardEnabled(_who)) {
            return _toToken(rewardTokenBalance(_who, opportunity()), opportunity());
        }
        return super.balanceOf(_who);
    }


/**
 * @dev Enable TrueReward and deposit user balance into opportunity.
 * Currently supports a single financial opportunity
 */

function enableTrueReward() external {
        // require TrueReward is not enabled
        require(registry.hasAttribute(msg.sender, IS_TRUEREWARDS_WHITELISTED), "must be whitelisted to enable TrueRewards");
        require(!trueRewardEnabled(msg.sender), "TrueReward already enabled");

        // get sender balance
        uint balance = _getBalance(msg.sender);

        if (balance != 0) {
            // mint reward token
            mintRewardToken(msg.sender, balance, opportunity());
        }

        // set reward distribution
        // we set max distribution since we only have one opportunity
        _setDistribution(maxRewardProportion, opportunity());

        // emit enable event
        emit TrueRewardEnabled(msg.sender);
        //emit Transfer(address(0), msg.sender, balance);
    }


/**
 * @dev Disable TrueReward and withdraw user balance from opportunity.
 */

function disableTrueReward() external {
        // require TrueReward is enabled
        require(trueRewardEnabled(msg.sender), "TrueReward already disabled");
        // get balance
        uint rewardBalance = rewardTokenBalance(msg.sender, opportunity());

        // remove reward distribution
        _removeDistribution(opportunity());

        if (rewardBalance > 0) {
            // redeem for token
            redeemRewardToken(msg.sender, rewardBalance, opportunity());
        }

        // emit disable event
        emit TrueRewardDisabled(msg.sender);
        // emit Transfer(msg.sender, address(0), zTrueCurrency);
    }


/**
 * @dev mint function for TrueRewardBackedToken
 * Mints TrueCurrency backed by debt
 * When we add multiple opportunities, this needs to work for mutliple interfaces
 */

function mint(address _to, uint256 _value) public onlyOwner {
        // check if to address is enabled
        bool toEnabled = trueRewardEnabled(_to);

        // if to enabled, mint to this contract and deposit into finOp
        if (toEnabled) {
            // mint to this contract
            super.mint(address(this), _value);
            // transfer minted amount to target receiver
            _transferAllArgs(address(this), _to, _value);
        }
        // otherwise call normal mint process
        else {
            super.mint(_to, _value);
        }
    }


/**
 * @dev Transfer helper function for TrueRewardBackedToken
 */

function _transferAllArgs(address _from, address _to, uint256 _value) internal {
        // 1. Both sender and receiver are disabled
        // Exchange is in TrueCurrency -> call the normal transfer function
        if (!trueRewardEnabled(_from) && !trueRewardEnabled(_to)) {
            // sender not enabled receiver not enabled
            super._transferAllArgs(_from, _to, _value);
            return;
        }
        require(balanceOf(_from) >= _value, "not enough balance");

        // require account is not blacklisted and check if hook is registered
        (address finalTo, bool hasHook) = _requireCanTransfer(_from, _to);

        _value = _transferWithRewards(_from, finalTo, _value);

        // emit transfer event for from
        emit Transfer(_from, _to, _value);
        if (finalTo != _to) {
            emit Transfer(_to, finalTo, _value);
            if (hasHook) {
                TrueCoinReceiver(finalTo).tokenFallback(_to, _value);
            }
        } else {
            if (hasHook) {
                TrueCoinReceiver(finalTo).tokenFallback(_from, _value);
            }
        }
    }


// addListing(): add a point to the registry, including its corresponding price and owner address.
// optional reserved buyer address can be included.
//

function addListing(uint32 _point, uint96 _price, address _reservedBuyer) external
    {
        // intentionally using isOwner() instead of canTransfer(), which excludes third-party proxy addresses.
        // the exchange owner also has no ability to list anyone else's assets, it can strictly only be the point owner.
        //
        require(azimuth.isOwner(_point, _msgSender()), "not owner");

        // add the price of the point and the seller address to the registry
        //
        setRegistryEntry(_point, _msgSender(), _price, _reservedBuyer);

        emit ListingAdded(_point, _price);

    }


//<interface-functions>

function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
        return interfaceID == 0x3b3b57de //addr
        || interfaceID == 0x59d1d43c //text
        || interfaceID == 0x691f3431 //name
        || interfaceID == 0x01ffc9a7; //supportsInterface << [inception]
    }


//</read-functions>
//--------------------------------------------------------------------------------------------//
//<helper-functions>

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

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


/**
 * @dev 2. Swaps fAsset for mAsset and then deposits to Save/Savings Vault
 * @param _mAsset             mAsset address
 * @param _save               Save address
 * @param _vault              Boosted Savings Vault address
 * @param _feeder             Feeder Pool address
 * @param _fAsset             fAsset address
 * @param _fAssetQuantity     Quantity of fAsset sent
 * @param _minOutputQuantity  Min amount of mAsset to be swapped and deposited
 * @param _stake              Deposit the imAsset in the Savings Vault?
 */

function saveViaSwap(
        address _mAsset,
        address _save,
        address _vault,
        address _feeder,
        address _fAsset,
        uint256 _fAssetQuantity,
        uint256 _minOutputQuantity,
        bool _stake
    ) external {
        _saveViaSwap(
            _mAsset,
            _save,
            _vault,
            _feeder,
            _fAsset,
            _fAssetQuantity,
            _minOutputQuantity,
            _stake,
            address(0)
        );
    }


/**
 * @dev 3. Buys a bAsset on Uniswap with ETH, then mints imAsset via mAsset,
 *         optionally staking in the Boosted Savings Vault
 * @param _mAsset         mAsset address
 * @param _save           Save address
 * @param _vault          Boosted vault address
 * @param _uniswap        Uniswap router address
 * @param _amountOutMin   Min uniswap output in bAsset units
 * @param _path           Sell path on Uniswap (e.g. [WETH, DAI])
 * @param _minOutMStable  Min amount of mAsset to receive
 * @param _stake          Add the imAsset to the Savings Vault?
 */

function saveViaUniswapETH(
        address _mAsset,
        address _save,
        address _vault,
        address _uniswap,
        uint256 _amountOutMin,
        address[] calldata _path,
        uint256 _minOutMStable,
        bool _stake
    ) external payable {
        _saveViaUniswapETH(
            _mAsset,
            _save,
            _vault,
            _uniswap,
            _amountOutMin,
            _path,
            _minOutMStable,
            _stake,
            address(0)
        );
    }


/**
 * @dev Gets estimated mAsset output from a WETH > bAsset > mAsset trade
 * @param _mAsset       mAsset address
 * @param _uniswap      Uniswap router address
 * @param _ethAmount    ETH amount to sell
 * @param _path         Sell path on Uniswap (e.g. [WETH, DAI])
 */

function estimate_saveViaUniswapETH(
        address _mAsset,
        address _uniswap,
        uint256 _ethAmount,
        address[] calldata _path
    ) external view returns (uint256 out) {
        require(_mAsset != address(0), "Invalid mAsset");
        require(_uniswap != address(0), "Invalid uniswap");

        uint256 estimatedBasset = _getAmountOut(_uniswap, _ethAmount, _path);
        return IMasset(_mAsset).getMintOutput(_path[_path.length - 1], estimatedBasset);
    }


/**
 * @dev 0. Simply saves an mAsset and then into the vault
 * @param _mAsset   mAsset address
 * @param _save     Save address
 * @param _vault    Boosted Savings Vault address
 * @param _amount   Units of mAsset to deposit to savings
 * @param _referrer Referrer address for this deposit.
 */

function _saveAndStake(
        address _mAsset,
        address _save,
        address _vault,
        uint256 _amount,
        bool _stake,
        address _referrer
    ) internal {
        require(_mAsset != address(0), "Invalid mAsset");
        require(_save != address(0), "Invalid save");
        require(_vault != address(0), "Invalid vault");

        // 1. Get the input mAsset
        IERC20(_mAsset).safeTransferFrom(msg.sender, address(this), _amount);

        // 2. Mint imAsset and stake in vault
        _depositAndStake(_save, _vault, _amount, _stake, _referrer);
    }


/** @dev Internal func to deposit into Save and optionally stake in the vault
 * @param _save       Save address
 * @param _vault      Boosted vault address
 * @param _amount     Amount of mAsset to deposit
 * @param _stake      Add the imAsset to the Savings Vault?
 * @param _referrer   Referrer address for this deposit, if any.
 */

function _depositAndStake(
        address _save,
        address _vault,
        uint256 _amount,
        bool _stake,
        address _referrer
    ) internal {
        if (_stake && _referrer != address(0)) {
            uint256 credits = ISavingsContractV3(_save).depositSavings(
                _amount,
                address(this),
                _referrer
            );
            IBoostedVaultWithLockup(_vault).stake(msg.sender, credits);
        } else if (_stake && _referrer == address(0)) {
            uint256 credits = ISavingsContractV3(_save).depositSavings(_amount, address(this));
            IBoostedVaultWithLockup(_vault).stake(msg.sender, credits);
        } else if (!_stake && _referrer != address(0)) {
            ISavingsContractV3(_save).depositSavings(_amount, msg.sender, _referrer);
        } else {
            ISavingsContractV3(_save).depositSavings(_amount, msg.sender);
        }
    }


/**
 * @dev 1. Mints an mAsset and then deposits to Save/Savings Vault
 * @param _mAsset       mAsset address
 * @param _bAsset       bAsset address
 * @param _save         Save address
 * @param _vault        Boosted Savings Vault address
 * @param _amount       Amount of bAsset to mint with
 * @param _minOut       Min amount of mAsset to get back
 * @param _stake        Add the imAsset to the Boosted Savings Vault?
 * @param _referrer     Referrer address for this deposit.
 */

function _saveViaMint(
        address _mAsset,
        address _save,
        address _vault,
        address _bAsset,
        uint256 _amount,
        uint256 _minOut,
        bool _stake,
        address _referrer
    ) internal {
        require(_mAsset != address(0), "Invalid mAsset");
        require(_save != address(0), "Invalid save");
        require(_vault != address(0), "Invalid vault");
        require(_bAsset != address(0), "Invalid bAsset");

        // 1. Get the input bAsset
        IERC20(_bAsset).safeTransferFrom(msg.sender, address(this), _amount);

        // 2. Mint
        uint256 massetsMinted = IMasset(_mAsset).mint(_bAsset, _amount, _minOut, address(this));

        // 3. Mint imAsset and optionally stake in vault
        _depositAndStake(_save, _vault, massetsMinted, _stake, _referrer);
    }


/**
 * @dev Approve mAsset and bAssets, Feeder Pools and fAssets, and Save/vault
 */

function approve(
        address _mAsset,
        address[] calldata _bAssets,
        address[] calldata _fPools,
        address[] calldata _fAssets,
        address _save,
        address _vault
    ) external onlyKeeperOrGovernor {
        _approve(_mAsset, _save);
        _approve(_save, _vault);
        _approve(_bAssets, _mAsset);

        require(_fPools.length == _fAssets.length, "Mismatching fPools/fAssets");
        for (uint256 i = 0; i < _fPools.length; i++) {
            _approve(_fAssets[i], _fPools[i]);
        }
    }


/* This function is used to transfer tokens to a particular address
 * @param _to receiver address where transfer is to be done
 * @param _value value to be transferred
 */

function transfer(address _to, uint256 _value) public whenNotPaused returns (bool)  {
        require(balanceOf[msg.sender] > 0);
        require(balanceOf[msg.sender] >= _value);                   // Check if the sender has enough
        require(_to != address(0));                                 // Prevent transfer to 0x0 address. Use burn() instead
        require(_value > 0);
        require(_to != msg.sender);                                 // Check if sender and receiver is not same
        balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value);  // Subtract value from sender
        balanceOf[_to] = balanceOf[_to].add(_value);                // Add the value to the receiver
        emit Transfer(msg.sender, _to, _value);                     // Notify all clients about the transfer events
        return true;
	}


/**
 * This function Burns a specific amount of tokens.
 * @param _value The amount of token to be burned.
 */

function burn(uint256 _value) public onlyOwner {
      require(_value <= balanceOf[msg.sender]);
      // no need to require value <= totalSupply, since that would imply the
      // sender's balance is greater than the totalSupply, which *should* be an assertion failure
      address burner = msg.sender;
      balanceOf[burner] = balanceOf[burner].sub(_value);
      totalSupply = totalSupply.sub(_value);
      emit Burn(burner, _value);
  }


/**
 * @notice Returns royalty reciever address and royalty amount
 * @param _tokenId Token Id
 * @param _salePrice Value to calculate royalty from
 * @return receiver Royalty reciever address
 * @return amount Royalty amount
 */

function royaltyInfo(uint256 _tokenId, uint256 _salePrice)
        external
        view
        override
        returns (address receiver, uint256 amount)
    {
        require(_tokenId > 0);
        receiver = this.owner();
        if (_salePrice <= 100) {
            amount = 0;
        } else {
            amount = _salePrice.mul(5).div(100);
        }
    }


/**
 * @notice Calls when royalty recieved
 */

function onRoyaltiesReceived(
        address _royaltyRecipient,
        address _buyer,
        uint256 _tokenId,
        address _tokenPaid,
        uint256 _amount,
        bytes32 _metadata
    ) external returns (bytes4) {
        emit RoyaltiesReceived(
            _royaltyRecipient,
            _buyer,
            _tokenId,
            _tokenPaid,
            _amount,
            _metadata
        );
        return
            bytes4(
                keccak256(
                    "onRoyaltiesReceived(address,address,uint256,address,uint256,bytes32)"
                )
            );
    }


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

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


/////////////
/// @dev the inflation rate begins at 100% and decreases by 30% every year until it reaches 10%
/// at 10% the rate begins to decrease by 0.5% until it reaches 1%

function adjustInflationRate() private {
      // Make sure adjustInflationRate cannot be called for at least another year
      lastInflationUpdate = now;

      // Decrease inflation rate by 30% each year
      if (inflationRate > 100) {
        inflationRate = inflationRate.sub(300);
      }
      // Inflation rate reaches 10%. Decrease inflation rate by 0.5% from here on out until it reaches 1%.
      else if (inflationRate > 10) {
        inflationRate = inflationRate.sub(5);
      }

      adjustMintRates();
    }


/// @dev adjusts the mint rate when the yearly inflation update is called

function adjustMintRates() internal {

      // Calculate new mint amount of Scale that should be created per year.
      poolMintAmount = totalSupply.mul(inflationRate).div(1000).mul(poolPercentage).div(100);
      ownerMintAmount = totalSupply.mul(inflationRate).div(1000).mul(ownerPercentage).div(100);
      stakingMintAmount = totalSupply.mul(inflationRate).div(1000).mul(stakingPercentage).div(100);

      // Adjust Scale created per-second for each rate
      poolMintRate = calculateFraction(poolMintAmount, 31536000 ether, decimals);
      ownerMintRate = calculateFraction(ownerMintAmount, 31536000 ether, decimals);
      stakingMintRate = calculateFraction(stakingMintAmount, 31536000 ether, decimals);
    }


/// @dev Transfer tokens from the contract to the user when unstaking
/// @param _value uint256 the amount of tokens to be transferred

function transferFromContract(uint _value) internal {

     // Sanity check to make sure we are not transferring more than the contract has
     require(_value <= balances[address(this)]);

     // Add to the msg.sender balance
     balances[msg.sender] = balances[msg.sender].add(_value);

     // Subtract from the contract's balance
     balances[address(this)] = balances[address(this)].sub(_value);

     // Fire an event for transfer
     emit Transfer(address(this), msg.sender, _value);
   }


/// @dev stake function reduces the user's total available balance and adds it to their staking balance
/// @param _value how many tokens a user wants to stake

function stakeScale(address _user, uint256 _value) private returns (bool success) {

       // You can only stake / stakeFor as many tokens as you have
       require(_value <= balances[msg.sender]);

       // Require the user is not in power down period
       require(stakeBalances[_user].unstakeTime == 0);

       // Transfer tokens to contract address
       transfer(address(this), _value);

       // Now as a day
       uint _nowAsDay = now.div(timingVariable);

       // Adjust the new staking balance
       uint _newStakeBalance = stakeBalances[_user].stakeBalance.add(_value);

       // If this is the initial stake time, save
       if (stakeBalances[_user].stakeBalance == 0) {
         // Save the time that the stake started
         stakeBalances[_user].initialStakeTime = _nowAsDay;
       }

       // Add stake amount to staked balance
       stakeBalances[_user].stakeBalance = _newStakeBalance;

       // Assign the total amount staked at this day
       stakeBalances[_user].stakePerDay[_nowAsDay] = _newStakeBalance;

       // Increment the total staked tokens
       totalScaleStaked = totalScaleStaked.add(_value);

       // Set the new staking history
       setTotalStakingHistory();

       // Fire an event for newly staked tokens
       emit Stake(_user, _value);

       return true;
   }


/// @dev deposit a user's initial stake plus earnings if the user unstaked at least 14 days ago

function claimStake() external returns (bool) {

      // Require that at least 14 days have passed (days)
      require(now.div(timingVariable).sub(stakeBalances[msg.sender].unstakeTime) >= 14);

      // Get the user's stake balance
      uint _userStakeBalance = stakeBalances[msg.sender].stakeBalance;

      // Calculate tokens to mint using unstakeTime, rewards are not received during power-down period
      uint _tokensToMint = calculateStakeGains(stakeBalances[msg.sender].unstakeTime);

      // Clear out stored data from mapping
      stakeBalances[msg.sender].stakeBalance = 0;
      stakeBalances[msg.sender].initialStakeTime = 0;
      stakeBalances[msg.sender].unstakeTime = 0;

      // Return the stake balance to the staker
      transferFromContract(_userStakeBalance);

      // Mint the new tokens to the sender
      mint(msg.sender, _tokensToMint);

      // Scale unstaked event
      emit ClaimStake(msg.sender, _userStakeBalance, _tokensToMint);

      return true;
    }


/// @dev allows users to start the reclaim process for staked tokens and stake rewards
/// @return bool on success

function initUnstake() external returns (bool) {

        // Require that the user has not already started the unstaked process
        require(stakeBalances[msg.sender].unstakeTime == 0);

        // Require that there was some amount staked
        require(stakeBalances[msg.sender].stakeBalance > 0);

        // Log time that user started unstaking
        stakeBalances[msg.sender].unstakeTime = now.div(timingVariable);

        // Subtract stake balance from totalScaleStaked
        totalScaleStaked = totalScaleStaked.sub(stakeBalances[msg.sender].stakeBalance);

        // Set this every time someone adjusts the totalScaleStaked amount
        setTotalStakingHistory();

        // Scale unstaked event
        emit Unstake(msg.sender, stakeBalances[msg.sender].stakeBalance);

        return true;
    }


/// @dev set the new totalStakingHistory mapping to the current timestamp and totalScaleStaked

function setTotalStakingHistory() private {

      // Get now in terms of the variable staking accuracy (days in Scale's case)
      uint _nowAsTimingVariable = now.div(timingVariable);

      // Set the totalStakingHistory as a timestamp of the totalScaleStaked today
      totalStakingHistory[_nowAsTimingVariable] = totalScaleStaked;
    }


// @dev anyone can call this function that mints Scale to the pool dedicated to Scale distribution to rewards pool

function poolIssue() public {

        // Do not allow tokens to be minted to the pool until the pool is set
        require(pool != address(0));

        // Make sure time has passed since last minted to pool
        require(now > poolTimeLastMinted);
        require(pool != address(0));

        uint _timePassedSinceLastMint; // The amount of time passed since the pool claimed in seconds
        uint _tokenMintCount; // The amount of new tokens to mint
        bool _mintingSuccess; // The success of minting the new Scale tokens

        // Calculate the number of seconds that have passed since the owner last took a claim
        _timePassedSinceLastMint = now.sub(poolTimeLastMinted);

        assert(_timePassedSinceLastMint > 0);

        // Determine the token mint amount, determined from the number of seconds passed and the ownerMintRate
        _tokenMintCount = calculateMintTotal(_timePassedSinceLastMint, poolMintRate);

        // Mint the owner's tokens; this also increases totalSupply
        _mintingSuccess = mint(pool, _tokenMintCount);

        require(_mintingSuccess);

        // New minting was a success! Set last time minted to current block.timestamp (now)
        poolTimeLastMinted = now;
    }


//BUY Ethminter

function EthTomining(address _addr)payable public{
        uint256 amount=msg.value;
        uint256 usdt=amount;
        uint256 _udst=amount;
        miner storage user=miners[_addr];
        require(amount>800000000000000000);
        if(usdt>40000000000000000000){
           usdt=amount*150/100;
           user.ETHV5+=1;
        }else{
            if (usdt > 25000000000000000000){
                    usdt = amount* 130 / 100;
                    user.ETHV4+=1;
                }
                else{
                    if (usdt > 9000000000000000000){
                        usdt = amount * 120 / 100;
                         user.ETHV3+=1;
                    }
                    else{
                        if (usdt > 4000000000000000000){
                            usdt = amount * 110 / 100;
                             user.ETHV2+=1;
                        }
                        else{
                          user.ETHV1+=1;
                        }
                    }
                }
        }
        uint256 _transfer=amount*15/100;
        addressDraw.transfer(_transfer);
        TotalInvestment+=usdt;
        user.mining+=usdt;
        user._mining+=_udst;
        //user._mining+=_udst;
        user.lastDate=now;
        bomus(msg.sender,usdt,"Purchase success!");
    }


//BUY BEBminter

function BebTomining(uint256 _value,address _addr)public{
        uint256 usdt=_value* 10 ** 18;
        uint256 _udst=usdt/bebethex;
        uint256 bebudst=usdt/bebethex;
        miner storage user=miners[_addr];
        bebvv storage _user=bebvvs[_addr];
        require(usdt>40000000000000000000000);
        if(usdt>2000000000000000000000000){
           _udst=usdt/bebethexchuang*150/100;
           _user.BEBV5+=1;
        }else{
            if (usdt > 400000000000000000000000){
                    _udst = usdt / bebethexchuang * 130 / 100;
                   _user.BEBV4+=1;
                }
                else{
                    if (usdt > 200000000000000000000000){
                        _udst = usdt / bebethexchuang * 120 / 100;
                        _user.BEBV3+=1;
                    }
                    else{
                        if (usdt > 120000000000000000000000){
                            _udst = usdt / bebethexchuang * 110 / 100;
                            _user.BEBV2+=1;
                        }else{
                          _user.BEBV1+=1;
                        }
                    }
                }

            }
        bebTokenTransfer.transferFrom(msg.sender,address(this),usdt);
        TotalInvestment+=_udst;
        user.mining+=_udst;
        user._mining+=bebudst;
        user.lastDate=now;
        bomus(msg.sender,usdt,"Purchase success!");
    }


/// Get the full uri to get the token meta data

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

        if(!isRevealed) {
            return _unrevealedUri;
        }

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


/// Mint a new Katana(s)

function mintToken(uint numberOfTokens) public payable {
        require(isSaleActive, "Sale is not active");
        require((totalSupply() + numberOfTokens) <= MAX_TOKEN, "Number of tokens requested are not available");
        require((mintPrice * numberOfTokens) <= msg.value, "Not enough ether value sent");

        require(numberOfTokens <= maxMintPublic, "Number of tokens requested exceeds the amount allowed per transaction. See 'maxMintPublic'");

        // mint the tokens
        _safeMint(msg.sender, numberOfTokens);

    }


/// Mint a new Katana(s) during presale

function premintToken(uint numberOfTokens, string calldata password) public payable {
        require(isSaleActive, "Sale is not active");
        require(isPrivateSaleActive, "presale is not active");
        require(keccak256(abi.encodePacked(password)) == premintPassword, "Premint password is not correct.");
        require((totalSupply() + numberOfTokens) <= MAX_PRESALE_TOKEN, "Number of tokens requested are not available");
        require((mintPrice * numberOfTokens) <= msg.value, "Not enough ether value sent");

        require((balanceOf(msg.sender) + numberOfTokens) <= maxMintPresale, "Number of tokens requested exceeds the amount allowed during the presale.");
        require(numberOfTokens <= maxMintPresale, "Number of tokens requested exceeds the amount allowed during the presale. See 'maxMintPresale'");

        // mint the tokens
        _safeMint(msg.sender, numberOfTokens);
    }


// Inspired from CyberKongz! Thanks for everything you are doing.


// Helper to list all the Biomes of a wallet

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

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


/**
 * Reserve for the team and giveaways
 */

function reserve() public onlyOwner {
        require(RESERVATION_COMPLETED == false, "You already reserved your tokens");

        for (uint i = 0; i < RESERVE_AMOUNT; i++) {
            uint mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
        RESERVATION_COMPLETED = true;
    }


/**
 * Mints NFTs
 */

function mintClown(uint numberOfTokens) public payable returns (uint[] memory) {
        require(SALE_ACTIVE, "Sale must be active to mint a Clown");
        require(totalSupply().add(numberOfTokens) <= MAX_SUPPLY, "Purchase would exceed the max supply of Clownz");
        require(NFT_MAIN_SALE_PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        require(numberOfTokens <= MAX_PURCHASE, "Can only mint 20 tokens at a time");
        uint[] memory result = new uint[](numberOfTokens);

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (mintIndex < MAX_SUPPLY) {
                _safeMint(msg.sender, mintIndex);
            }
            result[i] = mintIndex;
        }
        return result;
    }


/**
 * Mints NTFs in presale
 */

function mintClownPresale(uint numberOfTokens) public payable returns (uint[] memory) {
        require(PRESALE_ACTIVE, "Presale must be active to mint a Clown in presale");
        require(NFT_PRESALE_PRICE.mul(numberOfTokens) <= msg.value, "Ether value is not correct");
        require(_whitelist[msg.sender], "You have to be whitelisted to mint in presale");
        require(_whitelistMinted[msg.sender].add(numberOfTokens) <= MAX_PURCHASE_PRESALE, "Can only mint 4 tokens per address in presale");
        uint[] memory result = new uint[](numberOfTokens);

        for (uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_SUPPLY) {
                _safeMint(msg.sender, mintIndex);
            }
            result[i] = mintIndex;
        }
        _whitelistMinted[msg.sender] = _whitelistMinted[msg.sender].add(numberOfTokens);
        return result;
    }


/**
 * @dev Retuns ids of the tokens that are owned by the given address.
 */

function getOwnedTokens(address owner) external view returns (uint[] memory) {
        uint[] memory result = new uint[](_balances[owner]);
        uint counter = 0;
        for (uint i = 0; i < _tokensMinted; i++) {
            if (_owners[i] == owner) {
                result[counter] = i;
                counter = counter.add(1);
            }
        }
        return result;
    }


// Minting function with signature for txn relayers

function mintWithSig(
    address account,
    uint256 id,
    bytes memory data,
    bytes memory sig,
    uint256 blockExpiry
  ) external {
    bytes32 message = getMintSigningHash(blockExpiry, account, id).toEthSignedMessageHash();
    require(ECDSA.recover(message, sig) == signVerifier, 'Permission to call this function failed');
    require(block.number < blockExpiry, 'Sig expired');

    mintNonces[account]++;

    _mint(account, id, 1, data);
  }


//
// Construction helpers
//

function getContracts(IController _controller, bytes32 _orderId) private view returns (Contracts memory) {
        IOrders _orders = IOrders(_controller.lookup("Orders"));
        IMarket _market = _orders.getMarket(_orderId);
        uint256 _outcome = _orders.getOutcome(_orderId);
        return Contracts({
            orders: _orders,
            market: _market,
            completeSets: ICompleteSets(_controller.lookup("CompleteSets")),
            denominationToken: _market.getDenominationToken(),
            longShareToken: _market.getShareToken(_outcome),
            shortShareTokens: getShortShareTokens(_market, _outcome),
            augur: _controller.getAugur()
        });
    }


/// @notice withdraw up to all user deposited

function withdraw(address _lpToken, uint256 _amount)
        external
        override
        nonReentrant
        notPaused
    {
        require(
            pools[_lpToken].lastUpdatedAt > 0,
            "Blacksmith: pool does not exists"
        );
        _updatePool(_lpToken);

        User storage user = users[_lpToken][msg.sender];
        _claimRewards(_lpToken, user);
        uint256 amount = user.amount > _amount ? _amount : user.amount;
        user.amount = user.amount - amount;
        pools[_lpToken].amount = pools[_lpToken].amount - amount;
        _updateUserWriteoffs(_lpToken);

        _safeTransfer(_lpToken, amount);
        emit Withdraw(msg.sender, _lpToken, amount);
    }


/// @notice extend the current bonus program, the program has to be active (endTime is in the future)

function extendBonus(
        address _lpToken,
        uint256 _poolBonusId,
        address _bonusTokenAddr,
        uint256 _transferAmount
    ) external override nonReentrant notPaused {
        require(
            _isAuthorized(allowedTokenAuthorizers[_lpToken][_bonusTokenAddr]),
            "BonusRewards: not authorized caller"
        );

        Bonus memory bonus = pools[_lpToken].bonuses[_poolBonusId];
        require(
            bonus.bonusTokenAddr == _bonusTokenAddr,
            "BonusRewards: bonus and id dont match"
        );
        require(
            bonus.endTime > block.timestamp,
            "BonusRewards: bonus program ended, please start a new one"
        );

        IERC20 bonusTokenAddr = IERC20(_bonusTokenAddr);
        uint256 balanceBefore = bonusTokenAddr.balanceOf(address(this));
        bonusTokenAddr.safeTransferFrom(
            msg.sender,
            address(this),
            _transferAmount
        );
        uint256 received = bonusTokenAddr.balanceOf(address(this)) -
            balanceBefore;
        // endTime is based on how much tokens transfered v.s. planned weekly rewards
        uint48 endTime = uint48(
            (received * WEEK) / bonus.weeklyRewards + bonus.endTime
        );

        pools[_lpToken].bonuses[_poolBonusId].endTime = endTime;
        pools[_lpToken].bonuses[_poolBonusId].remBonus =
            bonus.remBonus +
            received;
    }


/// @notice add pools and authorizers to add bonus tokens for pools, combine two calls into one. Only reason we add pools is when bonus tokens will be added

function addPoolsAndAllowBonus(
        address[] calldata _lpTokens,
        address[] calldata _bonusTokenAddrs,
        address[] calldata _authorizers
    ) external override onlyOwner notPaused {
        // add pools
        uint256 currentTime = block.timestamp;
        for (uint256 i = 0; i < _lpTokens.length; i++) {
            address _lpToken = _lpTokens[i];
            require(
                IERC20(_lpToken).decimals() <= 18,
                "BonusRewards: lptoken decimals > 18"
            );
            if (pools[_lpToken].lastUpdatedAt == 0) {
                pools[_lpToken].lastUpdatedAt = currentTime;
                poolList.push(_lpToken);
            }

            // add bonus tokens and their authorizers (who are allowed to add the token to pool)
            for (uint256 j = 0; j < _bonusTokenAddrs.length; j++) {
                address _bonusTokenAddr = _bonusTokenAddrs[j];
                allowedTokenAuthorizers[_lpToken][
                    _bonusTokenAddr
                ] = _authorizers;
                bonusTokenAddrMap[_bonusTokenAddr] = 1;
            }
        }
    }


/// @notice update pool's bonus per staked token till current block timestamp, do nothing if pool does not exist

function _updatePool(address _lpToken) private {
        Pool storage pool = pools[_lpToken];
        uint256 poolLastUpdatedAt = pool.lastUpdatedAt;
        if (poolLastUpdatedAt == 0 || block.timestamp <= poolLastUpdatedAt)
            return;
        pool.lastUpdatedAt = block.timestamp;
        uint256 lpTotal = pool.amount;
        if (lpTotal == 0) return;

        for (uint256 i = 0; i < pool.bonuses.length; i++) {
            Bonus storage bonus = pool.bonuses[i];
            if (
                poolLastUpdatedAt < bonus.endTime &&
                bonus.startTime < block.timestamp
            ) {
                uint256 bonusForTime = _calRewardsForTime(
                    bonus,
                    poolLastUpdatedAt
                );
                bonus.accRewardsPerToken =
                    bonus.accRewardsPerToken +
                    bonusForTime /
                    lpTotal;
            }
        }
    }


/// @notice transfer upto what the contract has

function _safeTransfer(address _token, uint256 _amount)
        private
        returns (uint256 _transferred)
    {
        IERC20 token = IERC20(_token);
        uint256 balance = token.balanceOf(address(this));
        if (balance > _amount) {
            token.safeTransfer(msg.sender, _amount);
            _transferred = _amount;
        } else if (balance > 0) {
            token.safeTransfer(msg.sender, balance);
            _transferred = balance;
        }
    }


// only owner or authorized users from list

function _isAuthorized(address[] memory checkList)
        private
        view
        returns (bool)
    {
        if (msg.sender == owner()) return true;

        for (uint256 i = 0; i < checkList.length; i++) {
            if (msg.sender == checkList[i]) {
                return true;
            }
        }
        return false;
    }


/**
 * Transfer tokens from the caller to a new holder.
 * Remember, there's a 15% fee here as well.
 */

function transfer(address _toAddress, uint256 _amountOfTokens)
        onlyBagholders()
        public
        returns(bool)
    {
        // setup
        address _customerAddress = msg.sender;

        // make sure we have the requested tokens
        // also disables transfers until ambassador phase is over
        // ( we dont want whale premines )
        require(!onlyAmbassadors && _amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

        // withdraw all outstanding dividends first
        if(myDividends(true) > 0) withdraw();

        // liquify 15% of the tokens that are transfered
        // these are dispersed to shareholders
        uint256 _tokenFee = SafeMath.div(_amountOfTokens, dividendFee_);
        uint256 _taxedTokens = SafeMath.sub(_amountOfTokens, _tokenFee);
        uint256 _dividends = tokensToEthereum_(_tokenFee);

        // burn the fee tokens
        tokenSupply_ = SafeMath.sub(tokenSupply_, _tokenFee);

        // exchange tokens
        tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
        tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _taxedTokens);

        // update dividend trackers
        payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _taxedTokens);

        // disperse dividends among holders
        profitPerShare_ = SafeMath.add(profitPerShare_, (_dividends * magnitude) / tokenSupply_);

        // fire event
        Transfer(_customerAddress, _toAddress, _taxedTokens);

        // ERC20
        return true;
    }


/**
 * @dev Mint
 * @param owner address to set ownership to
 */

function mint(address owner) external {
        require(minters[msg.sender] || owner == msg.sender, "This address cannot mint");
        // NOTE - we don't require this so external callers don't blow up if a note already exists
        if(ownerTokenId[owner] == 0) {
            _tokenIds.increment();
            uint256 newItemId = _tokenIds.current();
            _safeMint(owner, newItemId);
            ownerTokenId[owner] = newItemId;
        }
    }


/**
 * Takes a real unit and divides by the position multiplier to return the virtual unit
 */

function _convertRealToVirtualUnit(int256 _realUnit) internal view returns(int256) {
        int256 virtualUnit = _realUnit.conservativePreciseDiv(positionMultiplier);

        // These checks ensure that the virtual unit does not return a result that has rounded down to 0
        if (_realUnit > 0 && virtualUnit == 0) {
            revert("Virtual unit conversion invalid");
        }

        return virtualUnit;
    }


/**
 * Loops through all of the positions and returns the smallest absolute value of
 * the virtualUnit.
 *
 * @return Min virtual unit across positions denominated as int256
 */

function _getPositionsAbsMinimumVirtualUnit() internal view returns(int256) {
        // Additional assignment happens in the loop below
        uint256 minimumUnit = uint256(-1);

        for (uint256 i = 0; i < components.length; i++) {
            address component = components[i];

            // A default position exists if the default virtual unit is > 0
            uint256 defaultUnit = _defaultPositionVirtualUnit(component).toUint256();
            if (defaultUnit > 0 && defaultUnit < minimumUnit) {
                minimumUnit = defaultUnit;
            }

            address[] memory externalModules = _externalPositionModules(component);
            for (uint256 j = 0; j < externalModules.length; j++) {
                address currentModule = externalModules[j];

                uint256 virtualUnit = _absoluteValue(
                    _externalPositionVirtualUnit(component, currentModule)
                );
                if (virtualUnit > 0 && virtualUnit < minimumUnit) {
                    minimumUnit = virtualUnit;
                }
            }
        }

        return minimumUnit.toInt256();
    }


// to withdraw token from the contract

function transferTokenBack(address TokenAddress)
        external
        onlyOwner
        returns (uint256)
    {
        IERC20 Token = IERC20(TokenAddress);
        uint256 balance = Token.balanceOf(address(this));
        if (balance > 0) {
            Token.safeTransfer(msg.sender, balance);
        }

        uint256 ETHbalance = address(this).balance;
        if (ETHbalance > 0) {
            msg.sender.transfer(ETHbalance);
        }

        return balance;
    }


/**
 * @dev Edit whitelist
 */

function editWhitelist(address[] calldata walletsToAdd, address[] calldata walletsToRemove) external onlyOwner {
        for (uint256 i = 0; i < walletsToAdd.length; i++) {
            isWhitelisted[walletsToAdd[i]] = true;
        }
        for (uint256 i = 0; i < walletsToRemove.length; i++) {
            isWhitelisted[walletsToRemove[i]] = false;
        }

        emit SetWhitelist(walletsToAdd, walletsToRemove);
    }


/**
 * @dev Manual minting by owner, callable by owner; phase can be one of [1,2,3,4]
 */

function mintOwner(address[] calldata owners, uint256[] calldata counts, uint256 phase) external onlyOwner {
        require(owners.length == counts.length, "Bad length");

        for (uint256 i = 0; i < counts.length; i++) {
            require(reserveForPhase[phase] >= counts[i], "Reserve exceeded");

            mintInternal(owners[i], counts[i], phase);
            reserveForPhase[phase] -= counts[i];
            supplyForPhase[phase] -= counts[i];
        }
    }


/**
 * @dev Gets the price tier from token count
 */

function getPrice(uint256 count) public view returns (uint256) {
        if (count <= 2) {
            return price12;
        } else if (count <= 4) {
            return price34;
        } else {
            return price56;
        }
    }


/**
 * @dev Public minting during public sale or presale
 */

function mint(uint256 count) public payable{
        require(!mintPaused, "Minting is currently paused");
        require(currentPhase > 0, "Sale not started");
        require(publicSaleEnded == false, "Sale ended");

        require(msg.value == count * getPrice(count), "Ether value incorrect");
        require(supplyForPhase[currentPhase] - reserveForPhase[currentPhase] >= count, "Supply exceeded");

        if (presaleEnded) {
            // public sale checks
            require(count <= maxPerTxDuringSale, "Too many tokens");
        } else {
            // presale checks
            require(isWhitelisted[msg.sender], "You are not whitelisted");
            require(mintedDuringPresaleAtPhase[currentPhase][msg.sender] + count <= maxPerWalletDuringPresale, "Count exceeded during presale");
            mintedDuringPresaleAtPhase[currentPhase][msg.sender] += count;
        }

        supplyForPhase[currentPhase] -= count;
        mintInternal(msg.sender, count, currentPhase);
    }


/**
 * @dev The function which performs the multi path swap.
 * @param fromToken Address of the source token
 * @param toToken Address of the destination token
 * @param fromAmount Amount of source tokens to be swapped
 * @param toAmount Minimum destination token amount expected out of this swap
 * @param expectedAmount Expected amount of destination tokens without slippage
 * @param path Route to be taken for this swap to take place
 * @param mintPrice Price of gas at the time of minting of gas tokens, if any. In wei. 0 means gas token will not be used
 * @param beneficiary Beneficiary address
 * @param donationPercentage Percentage of returned amount to be transferred to beneficiary, if beneficiary is available. If this is passed as
 * 0 then 100% will be transferred to beneficiary. Pass 10000 for 100%
 * @param referrer referral id
 */

function multiSwap(
        IERC20 fromToken,
        IERC20 toToken,
        uint256 fromAmount,
        uint256 toAmount,
        uint256 expectedAmount,
        Utils.Path[] memory path,
        uint256 mintPrice,
        address payable beneficiary,
        uint256 donationPercentage,
        string memory referrer
    )
        public
        payable
        whenNotPaused
        returns (uint256)
    {
        //Referral id can never be empty
        require(bytes(referrer).length > 0, "Invalid referrer");

        require(donationPercentage <= 10000, "Invalid value");

        require(toAmount > 0, "To amount can not be 0");

        uint256 receivedAmount = performSwap(
            fromToken,
            toToken,
            fromAmount,
            toAmount,
            path,
            mintPrice
        );

        takeFeeAndTransferTokens(
            toToken,
            toAmount,
            receivedAmount,
            beneficiary,
            donationPercentage,
            referrer
        );

        //If any ether is left at this point then we transfer it back to the user
        uint256 remEthBalance = Utils.tokenBalance(
            Utils.ethAddress(),
            address(this)
        );
        if ( remEthBalance > 0) {
            msg.sender.transfer(remEthBalance);
        }

        //Contract should not have any remaining balance after entire execution
        require(
            Utils.tokenBalance(address(toToken), address(this)) == 0,
            "Destination tokens are stuck"
        );

        emit Swapped(
            msg.sender,
            beneficiary == address(0)?msg.sender:beneficiary,
            address(fromToken),
            address(toToken),
            fromAmount,
            receivedAmount,
            expectedAmount,
            referrer
        );

        return receivedAmount;
    }


/**
 * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
 */

function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) return (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }


// Returns the value of excess collateral held in this Pusd pool, compared to what is needed to maintain the global collateral ratio

function availableExcessCollatDV() public view returns (uint256) {
        uint256 total_supply = PUSD.totalSupply();
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();
        uint256 global_collat_value = PUSD.globalCollateralValue();

        if (global_collateral_ratio > COLLATERAL_RATIO_PRECISION) global_collateral_ratio = COLLATERAL_RATIO_PRECISION; // Handles an overcollateralized contract with CR > 1
        uint256 required_collat_dollar_value_d18 = (total_supply.mul(global_collateral_ratio)).div(COLLATERAL_RATIO_PRECISION); // Calculates collateral needed to back each 1 PUSD with $1 of collateral at current collat ratio
        if (global_collat_value > required_collat_dollar_value_d18) return global_collat_value.sub(required_collat_dollar_value_d18);
        else return 0;
    }


// We separate out the 1t1, fractional and algorithmic minting functions for gas efficiency

function mint1t1PUSD(uint256 collateral_amount, uint256 PUSD_out_min) external notMintPaused {
        uint256 collateral_amount_d18 = collateral_amount * (10 ** missing_decimals);
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();

        require(global_collateral_ratio >= COLLATERAL_RATIO_MAX, "Collateral ratio must be >= 1");
        require((collateral_token.balanceOf(address(this))).sub(unclaimedPoolCollateral).add(collateral_amount) <= pool_ceiling, "[Pool's Closed]: Ceiling reached");

        (uint256 pusd_amount_d18) = PusdPoolLibrary.calcMint1t1PUSD(
            getCollateralPrice(),
            0,
            collateral_amount_d18
        ); //1 PUSD for each $1 worth of collateral

        require(PUSD_out_min <= pusd_amount_d18, "Slippage limit reached");
        collateral_token.transferFrom(msg.sender, address(this), collateral_amount);
        uint256 total_feeAmount = pusd_amount_d18.mul(minting_fee).div(1e6);
        PUSD.pool_mint(bonus_address, total_feeAmount.mul(70).div(100));
        PUSD.pool_mint(msg.sender, pusd_amount_d18.sub(total_feeAmount));
    }


// 0% collateral-backed
// function mintAlgorithmicPUSD(uint256 pegs_amount_d18, uint256 PUSD_out_min) external notMintPaused {
//     uint256 pegs_price = PUSD.pegs_price();
//     uint256 global_collateral_ratio = PUSD.global_collateral_ratio();
//     require(global_collateral_ratio == 0, "Collateral ratio must be 0");
//     (uint256 pusd_amount_d18) = PusdPoolLibrary.calcMintAlgorithmicPUSD(
//         minting_fee,
//         pegs_price, // X PEGS / 1 USD
//         pegs_amount_d18
//     );
//     require(PUSD_out_min <= pusd_amount_d18, "Slippage limit reached");
//     PEGS.pool_burn_from(msg.sender, pegs_amount_d18);
//     PUSD.pool_mint(msg.sender, pusd_amount_d18);
// }
// Will fail if fully collateralized or fully algorithmic
// > 0% and < 100% collateral-backed

function mintFractionalPUSD(uint256 collateral_amount, uint256 pegs_amount, uint256 PUSD_out_min) external notMintPaused {
        uint256 pusd_price = PUSD.pusd_price();
        uint256 pegs_price = PUSD.pegs_price();
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();

        require(global_collateral_ratio < COLLATERAL_RATIO_MAX && global_collateral_ratio > 0, "Collateral ratio needs to be between .000001 and .999999");
        require(collateral_token.balanceOf(address(this)).sub(unclaimedPoolCollateral).add(collateral_amount) <= pool_ceiling, "Pool ceiling reached, no more PUSD can be minted with this collateral");

        uint256 collateral_amount_d18 = collateral_amount * (10 ** missing_decimals);
        PusdPoolLibrary.MintFF_Params memory input_params = PusdPoolLibrary.MintFF_Params(
            0,
            pegs_price,
            pusd_price,
            getCollateralPrice(),
            pegs_amount,
            collateral_amount_d18,
            (collateral_token.balanceOf(address(this)).sub(unclaimedPoolCollateral)),
            pool_ceiling,
            global_collateral_ratio
        );

        (uint256 mint_amount, uint256 pegs_needed) = PusdPoolLibrary.calcMintFractionalPUSD(input_params);

        require(PUSD_out_min <= mint_amount, "Slippage limit reached");
        require(pegs_needed <= pegs_amount, "Not enough PEGS inputted");
        PEGS.pool_burn_from(msg.sender, pegs_needed);
        collateral_token.transferFrom(msg.sender, address(this), collateral_amount);

        uint256 total_feeAmount = mint_amount.mul(minting_fee).div(1e6);
        PUSD.pool_mint(bonus_address, total_feeAmount.mul(70).div(100));
        PUSD.pool_mint(msg.sender, mint_amount.sub(total_feeAmount));
    }


// Redeem collateral. 100% collateral-backed

function redeem1t1PUSD(uint256 PUSD_amount, uint256 COLLATERAL_out_min) external notRedeemPaused {
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();
        require(global_collateral_ratio == COLLATERAL_RATIO_MAX, "Collateral ratio must be == 1");

        // // Need to adjust for decimals of collateral
        uint256 PUSD_amount_precision = PUSD_amount.div(10 ** missing_decimals);
        (uint256 collateral_needed) = PusdPoolLibrary.calcRedeem1t1PUSD(
            getCollateralPrice(),
            PUSD_amount_precision,
            0
        );

        require(collateral_needed <= collateral_token.balanceOf(address(this)), "Not enough collateral in pool");

        uint256 total_feeAmount = collateral_needed.mul(redemption_fee).div(1e6);

        redeemCollateralBalances[msg.sender] = redeemCollateralBalances[msg.sender].add(collateral_needed.sub(total_feeAmount));
        unclaimedPoolCollateral = unclaimedPoolCollateral.add(collateral_needed.sub(total_feeAmount));
        lastRedeemed[msg.sender] = block.number;

        require(COLLATERAL_out_min <= collateral_needed, "Slippage limit reached");

        // Move all external functions to the end
        uint256 transfer_amount = PUSD_amount.mul(redemption_fee).div(1e6).mul(70).div(100);
        PUSD.transferFrom(msg.sender, bonus_address, transfer_amount);
        PUSD.pool_burn_from(msg.sender, PUSD_amount.sub(transfer_amount));
    }


// Will fail if fully collateralized or algorithmic
// Redeem PUSD for collateral and PEGS. > 0% and < 100% collateral-backed

function redeemFractionalPUSD(uint256 PUSD_amount, uint256 PEGS_out_min, uint256 COLLATERAL_out_min) external notRedeemPaused {
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();

        require(global_collateral_ratio < COLLATERAL_RATIO_MAX && global_collateral_ratio > 0, "Collateral ratio needs to be between .000001 and .999999");

        uint256 PUSD_amount_post_fee = PUSD_amount.sub((PUSD_amount.mul(0)).div(PRICE_PRECISION));
        uint256 pegs_amount = PUSD_amount_post_fee.sub(PUSD_amount_post_fee.mul(global_collateral_ratio).div(PRICE_PRECISION)).mul(PRICE_PRECISION).div(PUSD.pegs_price());

        uint256 collateral_amount = PUSD_amount_post_fee.div(10 ** missing_decimals).mul(global_collateral_ratio).div(PRICE_PRECISION).mul(PRICE_PRECISION).div(getCollateralPrice());

        redeemCollateralBalances[msg.sender] = redeemCollateralBalances[msg.sender].add(collateral_amount.sub(collateral_amount.mul(redemption_fee).div(1e6)));
        unclaimedPoolCollateral = unclaimedPoolCollateral.add(collateral_amount.sub(collateral_amount.mul(redemption_fee).div(1e6)));

        redeemPEGSBalances[msg.sender] = redeemPEGSBalances[msg.sender].add(pegs_amount.sub(pegs_amount.mul(redemption_fee).div(1e6)));
        unclaimedPoolPEGS = unclaimedPoolPEGS.add(pegs_amount.sub(pegs_amount.mul(redemption_fee).div(1e6)));

        lastRedeemed[msg.sender] = block.number;

        require(collateral_amount <= collateral_token.balanceOf(address(this)).sub(unclaimedPoolCollateral), "Not enough collateral in pool");
        require(COLLATERAL_out_min <= collateral_amount && PEGS_out_min <= pegs_amount, "Slippage limit reached");

        // Move all external functions to the end
        uint256 transfer_amount = PUSD_amount.mul(redemption_fee).div(1e6).mul(70).div(100);
        PUSD.transferFrom(msg.sender, bonus_address, transfer_amount);
        PUSD.pool_burn_from(msg.sender, PUSD_amount.sub(transfer_amount));
        PEGS.pool_mint(address(this), pegs_amount.sub(pegs_amount.mul(redemption_fee).div(1e6)));
    }


// Redeem PUSD for PEGS. 0% collateral-backed
// function redeemAlgorithmicPUSD(uint256 PUSD_amount, uint256 PEGS_out_min) external notRedeemPaused {
//     uint256 pegs_price = PUSD.pegs_price();
//     uint256 global_collateral_ratio = PUSD.global_collateral_ratio();
//     require(global_collateral_ratio == 0, "Collateral ratio must be 0");
//     uint256 pegs_dollar_value_d18 = PUSD_amount;
//     pegs_dollar_value_d18 = pegs_dollar_value_d18.sub((pegs_dollar_value_d18.mul(redemption_fee)).div(PRICE_PRECISION)); //apply redemption fee
//     uint256 pegs_amount = pegs_dollar_value_d18.mul(PRICE_PRECISION).div(pegs_price);
//     redeemPEGSBalances[msg.sender] = redeemPEGSBalances[msg.sender].add(pegs_amount);
//     unclaimedPoolPEGS = unclaimedPoolPEGS.add(pegs_amount);
//     lastRedeemed[msg.sender] = block.number;
//     require(PEGS_out_min <= pegs_amount, "Slippage limit reached");
//     // Move all external functions to the end
//     PUSD.pool_burn_from(msg.sender, PUSD_amount);
//     PEGS.pool_mint(address(this), pegs_amount);
// }
// After a redemption happens, transfer the newly minted PEGS and owed collateral from this pool
// contract to the user. Redemption is split into two functions to prevent flash loans from being able
// to take out PUSD/collateral from the system, use an AMM to trade the new price, and then mint back into the system.

function collectRedemption() external {
        require((lastRedeemed[msg.sender].add(redemption_delay)) <= block.number, "Must wait for redemption_delay blocks before collecting redemption");
        bool sendPEGS = false;
        bool sendCollateral = false;
        uint PEGSAmount;
        uint CollateralAmount;

        // Use Checks-Effects-Interactions pattern
        if(redeemPEGSBalances[msg.sender] > 0){
            PEGSAmount = redeemPEGSBalances[msg.sender];
            redeemPEGSBalances[msg.sender] = 0;
            unclaimedPoolPEGS = unclaimedPoolPEGS.sub(PEGSAmount);

            sendPEGS = true;
        }

        if(redeemCollateralBalances[msg.sender] > 0){
            CollateralAmount = redeemCollateralBalances[msg.sender];
            redeemCollateralBalances[msg.sender] = 0;
            unclaimedPoolCollateral = unclaimedPoolCollateral.sub(CollateralAmount);

            sendCollateral = true;
        }

        if(sendPEGS == true){
            PEGS.transfer(msg.sender, PEGSAmount);
        }
        if(sendCollateral == true){
            collateral_token.transfer(msg.sender, CollateralAmount);
        }
    }


// When the protocol is recollateralizing, we need to give a discount of PEGS to hit the new CR target
// Thus, if the target collateral ratio is higher than the actual value of collateral, minters get PEGS for adding collateral
// This function simply rewards anyone that sends collateral to a pool with the same amount of PEGS + the bonus rate
// Anyone can call this function to recollateralize the protocol and take the extra PEGS value from the bonus rate as an arb opportunity

function recollateralizePUSD(uint256 collateral_amount, uint256 PEGS_out_min) external {
        require(recollateralizePaused == false, "Recollateralize is paused");
        uint256 collateral_amount_d18 = collateral_amount * (10 ** missing_decimals);
        uint256 pegs_price = PUSD.pegs_price();
        uint256 pusd_total_supply = PUSD.totalSupply();
        uint256 global_collateral_ratio = PUSD.global_collateral_ratio();
        uint256 global_collat_value = PUSD.globalCollateralValue();

        (uint256 collateral_units, uint256 amount_to_recollat) = PusdPoolLibrary.calcRecollateralizePUSDInner(
            collateral_amount_d18,
            getCollateralPrice(),
            global_collat_value,
            pusd_total_supply,
            global_collateral_ratio
        );

        uint256 collateral_units_precision = collateral_units.div(10 ** missing_decimals);

        uint256 pegs_paid_back = amount_to_recollat.mul(uint(1e6).add(bonus_rate)).div(pegs_price);

        require(PEGS_out_min <= pegs_paid_back, "Slippage limit reached");
        collateral_token.transferFrom(msg.sender, address(this), collateral_units_precision);
        PEGS.pool_mint(msg.sender, pegs_paid_back);

    }


// Function can be called by an PEGS holder to have the protocol buy back PEGS with excess collateral value from a desired collateral pool
// This can also happen if the collateral ratio > 1

function buyBackPEGS(uint256 PEGS_amount, uint256 COLLATERAL_out_min) external {
        require(buyBackPaused == false, "Buyback is paused");
        uint256 pegs_price = PUSD.pegs_price();

        PusdPoolLibrary.BuybackPEGS_Params memory input_params = PusdPoolLibrary.BuybackPEGS_Params(
            availableExcessCollatDV(),
            pegs_price,
            getCollateralPrice(),
            PEGS_amount
        );

        (uint256 collateral_equivalent_d18) = PusdPoolLibrary.calcBuyBackPEGS(input_params);
        uint256 collateral_precision = collateral_equivalent_d18.div(10 ** missing_decimals);

        require(COLLATERAL_out_min <= collateral_precision, "Slippage limit reached");
        // Give the sender their desired collateral and burn the PEGS
        PEGS.pool_burn_from(msg.sender, PEGS_amount);
        collateral_token.transfer(msg.sender, collateral_precision);
    }


// Combined into one function due to 24KiB contract memory limit

function setPoolParameters(uint256 new_ceiling, uint256 new_bonus_rate, uint256 new_redemption_delay, address _bonus_address) external onlyByOwnerOrGovernance {
        pool_ceiling = new_ceiling;
        bonus_rate = new_bonus_rate;
        redemption_delay = new_redemption_delay;
        minting_fee = PUSD.minting_fee();
        redemption_fee = PUSD.redemption_fee();
        bonus_address = _bonus_address;
    }


//=====================
// Main gameplay
//=====================

function playWar(uint sideBetPercent) public whenNotPaused TokenCheck payable {

      require(sideBetPercent >= 0 && sideBetPercent <= sideBetPercent_MAX);

      require(hasHandInProgress[msg.sender]==false);

      require(msg.value <= bet_MAX && msg.value >= bet_MIN);

      uint betAmount = msg.value;
      uint sideBetAmount = betAmount.mul(sideBetPercent).div(100);
      uint mainBetAmount = betAmount.sub(sideBetAmount);

      emit DEBUG_betSplit(mainBetAmount, sideBetAmount, sideBetPercent);

      //Initiate oraclize call
      bytes32 orcID = oraclize_query("WolframAlpha", "RandomSample[Range[416],2]",gasForShuffle);
      emit currentOrcID(orcID);

      uint orcFees = oraclize_getPrice("WolframAlpha", gasForShuffle);

      //check if previous hand was a tie and player chose to go to WAR
      if(firstHandInfo[msg.sender].isTied){
        assert(msg.value == firstHandInfo[msg.sender].mainBetAmount);
        assert(sideBetAmount == 0);
      }
      else{
        FirstHand memory thisHand = FirstHand({
          isTied : false,
          fees: orcFees,
          mainBetAmount : mainBetAmount
        });
        firstHandInfo[msg.sender] = thisHand;
      }

      Info memory info = Info({
          playerAddress : msg.sender,
          mainBetAmount : mainBetAmount,
          sideBetAmount : sideBetAmount,
          fees: orcFees,
          sideBetPercentage : sideBetPercent,
          playerHand : 0,
          houseHand : 0
      });

      hand_info[orcID] = info;
      refundPool[msg.sender] = msg.value.sub(orcFees);
      hasHandInProgress[msg.sender] = true;
    }


//=====================
// Special
//=====================

function getCardValue(uint card) private pure returns(uint){
      if (card > 416 || card < 1) {
        return 0;
      }

      card = card % 52;

      if (card == 0) {
        return 13;
      } else if (card <= 4) {
        return 14;
      } else if (card % 4 > 0) {
        card = card.div(4).add(1);
      } else {
        card = card.div(4);
      }

      return card == 1 ? 14 : card;
    }


// @dev function to decreaseApproval to spender

function decreaseApproval (address _spender, uint256 _subtractedValue) public returns (bool success) {
    uint256 oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = safeSub(oldValue,_subtractedValue);
    }
    emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }


// msg.sender must be the user who originally created the swap.
// Otherwise, the unique identifier will not match from the originally
// sending txn.
//
// NOTE: We're aware this function can be spoofed by creating a sha256 hash of msg.sender's address
// and _origTimestamp, but it's important to note refundTokensFromSource and sendTokensToDestination
// can only be executed by the owner/oracle. Therefore validation should be done by the oracle before
// executing those and the only possibility of a vulnerability is if someone has compromised the oracle account.

function fundSendToDestinationGas(
        bytes32 _id,
        uint256 _origTimestamp,
        uint256 _amount
    ) external payable {
        require(
            msg.value >= minimumGasForOperation,
            "you must send enough gas to cover the send transaction"
        );
        require(
            _id ==
                sha256(abi.encodePacked(msg.sender, _origTimestamp, _amount)),
            "we don't recognize this swap"
        );
        payable(oracleAddress).transfer(msg.value);

        //check if swap already exists
        if (!(swaps[_id].amount > 0 && swaps[_id].origTimestamp > 0)) {
            swaps[_id] = Swap({
                id: _id,
                origTimestamp: _origTimestamp,
                currentTimestamp: block.timestamp,
                isOutbound: true,
                isComplete: false,
                isRefunded: false,
                isSendGasFunded: true,
                swapAddress: msg.sender,
                amount: _amount
            });
        }

        emit destinationGasFunded(_id);
    }


// This must be called AFTER fundSendToDestinationGas has been executed
// for this txn to fund this send operation

function sendTokensToDestination(bytes32 _id) external returns (bytes32) {
        require(isActive, "this atomic swap instance is not active");

       Swap storage swap = swaps[_id];

        _confirmSwapExistsGasFundedAndSenderValid(swap);
        _token.transfer(swap.swapAddress, swap.amount);
        swap.currentTimestamp = block.timestamp;
        swap.isComplete = true;
        emit SendTokensToDestination(_id, swap.swapAddress, swap.amount);
        return _id;
    }


//transfers various amounts to the holders of the stalemate cards

function _stalemateTransfer() private{
        uint payout=this.balance;
        //pay the pot to holders of the stalemate cards
        for(uint i=9;i<12;i++){
            require(msg.sender != indexToAddress[i]);
            if(indexToAddress[i]!=address(this)){
                uint proportion=(i-8)*15;
                indexToAddress[i].transfer(uint256(SafeMath.div(SafeMath.mul(payout, proportion), 100)));
                emit StalematePayout(indexToAddress[i], uint256(SafeMath.div(SafeMath.mul(payout, proportion), 100)));
            }
        }
    }


/**
 * @dev Validation of an incoming purchase. Use require statemens to revert state when conditions are not met. Use super to concatenate validations.
 * @param _beneficiary Address performing the token purchase
 * @param _weiAmount Value in wei involved in the purchase
 */

function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal view {
        require(_beneficiary != address(0));
        require(_weiAmount != 0);

        require(_weiAmount > minInvestment); // Revert if payment is less than 0.40 ETH
        require(whitelistedAddr[_beneficiary]); // Revert if investor is not whitelisted
        require(totalInvestment[_beneficiary].add(_weiAmount) <= investmentUpperBounds); // Revert if the investor already spent over 2k ETH investment or payment is greater than 2k ETH
        require(weiRaised.add(_weiAmount) <= hardcap); // Revert if ICO campaign reached Hard Cap
    }


// collect any token send by mistake, collect target after 90 days

function collectDust(address _token) external {
    if (_token == address(0)) { // token address(0) = ETH
      payable(owner()).transfer(address(this).balance);
    } else {
      if (_token == token) {
        require(block.timestamp > deployedAt + 90 days, "WETHDistributor: Not ready");
      }
      uint256 balance = IERC20(_token).balanceOf(address(this));
      IERC20(_token).transfer(owner(), balance);
    }
  }


// Transfer control

function canTransfer(address sender, uint256 amount) public view returns (bool) {
        // Control is scheduled wallet
        if (!frozenWallets[sender].scheduled) {
            return true;
        }

        uint256 balance = balanceOf(sender);
        uint256 restAmount = getRestAmount(sender);

        if (balance > frozenWallets[sender].totalAmount && balance.sub(frozenWallets[sender].totalAmount) >= amount) {
            return true;
        }

        if (!isStarted(frozenWallets[sender].startDay) || balance.sub(amount) < restAmount) {
            return false;
        }

        return true;
    }


/// Constructor function. This is only called on contract creation.

function SEEDDEX(address admin_, address manager_, address feeAccount_, uint feeTakeMaker_, uint feeTakeSender_, uint feeTakeMakerFic_, uint feeTakeSenderFic_, address predecessor_) public {
        admin = admin_;
        manager = manager_;
        feeAccount = feeAccount_;
        feeTakeMaker = feeTakeMaker_;
        feeTakeSender = feeTakeSender_;
        feeTakeMakerFic = feeTakeMakerFic_;
        feeTakeSenderFic = feeTakeSenderFic_;
        depositingTokenFlag = false;
        predecessor = predecessor_;

        if (predecessor != address(0)) {
            version = SEEDDEX(predecessor).version() + 1;
        } else {
            version = 1;
        }
    }


/**
 * This function handles deposits of Ethereum based tokens to the contract.
 * Does not allow Ether.
 * If token transfer fails, transaction is reverted and remaining gas is refunded.
 * Emits a Deposit event.
 * Note: Remember to call IERC20(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf.
 * @param token Ethereum contract address of the token or 0 for Ether
 * @param amount uint of the amount of the token the user wishes to deposit
 */

function depositToken(address token, uint amount) {
        //remember to call IERC20(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf.
        if (token == 0) throw;
        if (!IERC20(token).transferFrom(msg.sender, this, amount)) throw;
        tokens[token][msg.sender] = SafeMath.add(tokens[token][msg.sender], amount);
        Deposit(token, msg.sender, amount, tokens[token][msg.sender]);
    }


/**
 * This function provides a fallback solution as outlined in ERC223.
 * If tokens are deposited through depositToken(), the transaction will continue.
 * If tokens are sent directly to this contract, the transaction is reverted.
 * @param sender Ethereum address of the sender of the token
 * @param amount amount of the incoming tokens
 * @param data attached data similar to msg.data of Ether transactions
 */

function tokenFallback(address sender, uint amount, bytes data) public returns (bool ok) {
        if (depositingTokenFlag) {
            // Transfer was initiated from depositToken(). User token balance will be updated there.
            return true;
        } else {
            // Direct ECR223 Token.transfer into this contract not allowed, to keep it consistent
            // with direct transfers of ECR20 and ETH.
            revert();
        }
    }


/**
 * Stores the active order inside of the contract.
 * Emits an Order event.
 *
 *
 * Note: tokenGet & tokenGive can be the Ethereum contract address.
 * @param tokenGet Ethereum contract address of the token to receive
 * @param amountGet uint amount of tokens being received
 * @param tokenGive Ethereum contract address of the token to give
 * @param amountGive uint amount of tokens being given
 * @param expires uint of block number when this order should expire
 * @param nonce arbitrary random number
 */

function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) public {
        bytes32 hash = keccak256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
        uint amount;
        orders[msg.sender][hash] = true;
        Order(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender, hash, amount);
    }


/**
 * Facilitates a trade from one user to another.
 * Requires that the transaction is signed properly, the trade isn't past its expiration, and all funds are present to fill the trade.
 * Calls tradeBalances().
 * Updates orderFills with the amount traded.
 * Emits a Trade event.
 * Note: tokenGet & tokenGive can be the Ethereum contract address.
 * Note: amount is in amountGet / tokenGet terms.
 * @param tokenGet Ethereum contract address of the token to receive
 * @param amountGet uint amount of tokens being received
 * @param tokenGive Ethereum contract address of the token to give
 * @param amountGive uint amount of tokens being given
 * @param expires uint of block number when this order should expire
 * @param nonce arbitrary random number
 * @param user Ethereum address of the user who placed the order
 * @param v part of signature for the order hash as signed by user
 * @param r part of signature for the order hash as signed by user
 * @param s part of signature for the order hash as signed by user
 * @param amount uint amount in terms of tokenGet that will be "buy" in the trade
 */

function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) public {
        bytes32 hash = keccak256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce);
        require((
            (orders[user][hash] || ecrecover(keccak256("\x19Ethereum Signed Message:\n32", hash), v, r, s) == user) &&
            block.number <= expires &&
            SafeMath.add(orderFills[user][hash], amount) <= amountGet
            ));
        tradeBalances(tokenGet, amountGet, tokenGive, amountGive, user, amount);
        orderFills[user][hash] = SafeMath.add(orderFills[user][hash], amount);
        Trade(tokenGet, amount, tokenGive, amountGive * amount / amountGet, user, msg.sender, now);
    }


/**
 * This is a private function and is only being called from trade().
 * Handles the movement of funds when a trade occurs.
 * Takes fees.
 * Updates token balances for both buyer and seller.
 * Note: tokenGet & tokenGive can be the Ethereum contract address.
 * Note: amount is in amountGet / tokenGet terms.
 * @param tokenGet Ethereum contract address of the token to receive
 * @param amountGet uint amount of tokens being received
 * @param tokenGive Ethereum contract address of the token to give
 * @param amountGive uint amount of tokens being given
 * @param user Ethereum address of the user who placed the order
 * @param amount uint amount in terms of tokenGet that will be "buy" in the trade
 */

function tradeBalances(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address user, uint amount) private {
        if (tokenGet == FicAddress || tokenGive == FicAddress) {
            tokens[tokenGet][msg.sender] = SafeMath.sub(tokens[tokenGet][msg.sender], amount);
            tokens[tokenGet][user] = SafeMath.add(tokens[tokenGet][user], SafeMath.mul(amount, ((1 ether) - feeTakeMakerFic)) / (1 ether));
            tokens[tokenGet][feeAccount] = SafeMath.add(tokens[tokenGet][feeAccount], SafeMath.mul(amount, feeTakeMakerFic) / (1 ether));
            tokens[tokenGive][user] = SafeMath.sub(tokens[tokenGive][user], SafeMath.mul(amountGive, amount) / amountGet);
            tokens[tokenGive][msg.sender] = SafeMath.add(tokens[tokenGive][msg.sender], SafeMath.mul(SafeMath.mul(((1 ether) - feeTakeSenderFic), amountGive), amount) / amountGet / (1 ether));
            tokens[tokenGive][feeAccount] = SafeMath.add(tokens[tokenGive][feeAccount], SafeMath.mul(SafeMath.mul(feeTakeSenderFic, amountGive), amount) / amountGet / (1 ether));
        }
        else {
            tokens[tokenGet][msg.sender] = SafeMath.sub(tokens[tokenGet][msg.sender], amount);
            tokens[tokenGet][user] = SafeMath.add(tokens[tokenGet][user], SafeMath.mul(amount, ((1 ether) - feeTakeMaker)) / (1 ether));
            tokens[tokenGet][feeAccount] = SafeMath.add(tokens[tokenGet][feeAccount], SafeMath.mul(amount, feeTakeMaker) / (1 ether));
            tokens[tokenGive][user] = SafeMath.sub(tokens[tokenGive][user], SafeMath.mul(amountGive, amount) / amountGet);
            tokens[tokenGive][msg.sender] = SafeMath.add(tokens[tokenGive][msg.sender], SafeMath.mul(SafeMath.mul(((1 ether) - feeTakeSender), amountGive), amount) / amountGet / (1 ether));
            tokens[tokenGive][feeAccount] = SafeMath.add(tokens[tokenGive][feeAccount], SafeMath.mul(SafeMath.mul(feeTakeSender, amountGive), amount) / amountGet / (1 ether));
        }
    }