// File: contracts/adapters/assets/curve/CurveAdapter.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ModuleLib } from "../../lib/ModuleLib.sol";
import { CurveAdapterLib } from "./CurveAdapterLib.sol";
import { TokenUtils } from "../../../utils/TokenUtils.sol";
import { BorrowProxyLib } from "../../../BorrowProxyLib.sol";
import { ICurve } from "../../../interfaces/ICurve.sol";

contract CurveAdapter {
  using ModuleLib for *;
  using CurveAdapterLib for *;
  using BorrowProxyLib for *;
  using TokenUtils for *;
  constructor(address curveAddress) public {
    CurveAdapterLib.Isolate storage isolate = CurveAdapterLib.getIsolatePointer(address(this));
    isolate.curveAddress = curveAddress;
  }
  function getExternalIsolateHandler() external view returns (CurveAdapterLib.Isolate memory isolate) {
    isolate = CurveAdapterLib.getIsolatePointer(address(this));
  }
  function handle(ModuleLib.AssetSubmodulePayload memory payload) public payable {
    require(payload.to == CurveAdapter(payload.moduleAddress).getExternalIsolateHandler().curveAddress, "CurveAdapter instance must map one to one with a live curve.fi instance");
    (bytes4 sig, bytes memory args) = payload.callData.splitPayload();
    address newToken;
    /* not sure how to handle these unless "token" is exposed on curve as a public variable ...
    if (sig == ICurve.add_liquidity.selector) payload.liquidationSubmodule.delegateNotify(abi.encode(address(0x0)));
    else if (sig == ICurve.remove_liquidity.selector) {}
    */
    if (sig == ICurve.exchange.selector) {
      CurveAdapterLib.ExchangeInputs memory inputs = args.decodeExchangeInputs();
      require(ICurve(payload.to).coins(uint256(uint128(inputs.i))).approveForMaxIfNeeded(payload.to), "token approval failed");
      newToken = ICurve(payload.to).coins(uint256(uint128(inputs.j)));
    } else if (sig == ICurve.exchange_underlying.selector) {
      CurveAdapterLib.ExchangeInputs memory inputs = args.decodeExchangeInputs();
      require(ICurve(payload.to).underlying_coins(uint256(uint128(inputs.i))).approveForMaxIfNeeded(payload.to), "token approval failed");
      newToken = ICurve(payload.to).underlying_coins(uint256(uint128(inputs.j)));
    } else revert("unsupported contract call");
    if (newToken != address(0x0)) require(payload.liquidationSubmodule.delegateNotify(abi.encode(newToken)), "failed to notify liquidation module");
    (bool success, bytes memory retval) = payload.to.call{ gas: gasleft(), value: payload.value }(payload.callData);
    ModuleLib.bubbleResult(success, retval);
  }
}

// File: contracts/adapters/lib/ModuleLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SliceLib } from "../../utils/SliceLib.sol";

interface IModule {
  function handle(ModuleLib.AssetSubmodulePayload calldata) external payable;
}

library ModuleLib {
  address payable constant ETHER_ADDRESS = 0x0000000000000000000000000000000000000000;
  function GET_ETHER_ADDRESS() internal pure returns (address payable) {
    return ETHER_ADDRESS;
  }
  function cast(uint256 v) internal pure returns (uint256) {
    return v;
  }
  function splitPayload(bytes memory payload) internal pure returns (bytes4 sig, bytes memory args) {
    sig = bytes4(uint32(uint256(SliceLib.asWord(SliceLib.toSlice(payload, 0, 4)))));
    args = SliceLib.copy(SliceLib.toSlice(payload, 4));
  }
  struct AssetSubmodulePayload {
    address payable moduleAddress;
    address liquidationSubmodule;
    address repaymentSubmodule;
    address payable token;
    address payable txOrigin;
    address payable to;
    uint256 value;
    bytes callData;
  }
  function encodeWithSelector(AssetSubmodulePayload memory input) internal pure returns (bytes memory result) {
    result = abi.encodeWithSelector(IModule.handle.selector, input);
  }
  function bubbleResult(bool success, bytes memory retval) internal pure {
    assembly {
      if iszero(success) {
        revert(add(0x20, retval), mload(retval))
      }
      return(add(0x20, retval), mload(retval))
    }
  }
}

// File: contracts/utils/SliceLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { MemcpyLib } from "./MemcpyLib.sol";

library SliceLib {
  struct Slice {
    uint256 data;
    uint256 length;
    uint256 offset;
  }
  function toPtr(bytes memory input, uint256 offset) internal pure returns (uint256 data) {
    assembly {
      data := add(input, add(offset, 0x20))
    }
  }
  function toSlice(bytes memory input, uint256 offset, uint256 length) internal pure returns (Slice memory retval) {
    retval.data = toPtr(input, offset);
    retval.length = length;
    retval.offset = offset;
  }
  function toSlice(bytes memory input) internal pure returns (Slice memory) {
    return toSlice(input, 0);
  }
  function toSlice(bytes memory input, uint256 offset) internal pure returns (Slice memory) {
    if (input.length < offset) offset = input.length;
    return toSlice(input, offset, input.length - offset);
  }
  function toSlice(Slice memory input, uint256 offset, uint256 length) internal pure returns (Slice memory) {
    return Slice({
      data: input.data + offset,
      offset: input.offset + offset,
      length: length
    });
  }
  function toSlice(Slice memory input, uint256 offset) internal pure returns (Slice memory) {
    return toSlice(input, offset, input.length - offset);
  }
  function toSlice(Slice memory input) internal pure returns (Slice memory) {
    return toSlice(input, 0);
  }
  function maskLastByteOfWordAt(uint256 data) internal pure returns (uint8 lastByte) {
    assembly {
      lastByte := and(mload(data), 0xff)
    }
  }
  function get(Slice memory slice, uint256 index) internal pure returns (bytes1 result) {
    return bytes1(maskLastByteOfWordAt(slice.data - 0x1f + index));
  }
  function setByteAt(uint256 ptr, uint8 value) internal pure {
    assembly {
      mstore8(ptr, value)
    }
  }
  function set(Slice memory slice, uint256 index, uint8 value) internal pure {
    setByteAt(slice.data + index, value);
  }
  function wordAt(uint256 ptr, uint256 length) internal pure returns (bytes32 word) {
    assembly {
      let mask := sub(shl(mul(length, 0x8), 0x1), 0x1)
      word := and(mload(sub(ptr, sub(0x20, length))), mask)
    }
  }
  function asWord(Slice memory slice) internal pure returns (bytes32 word) {
    uint256 data = slice.data;
    uint256 length = slice.length;
    return wordAt(data, length);
  }
  function toDataStart(bytes memory input) internal pure returns (bytes32 start) {
    assembly {
      start := add(input, 0x20)
    }
  }
  function copy(Slice memory slice) internal pure returns (bytes memory retval) {
    uint256 length = slice.length;
    retval = new bytes(length);
    bytes32 src = bytes32(slice.data);
    bytes32 dest = toDataStart(retval);
    MemcpyLib.memcpy(dest, src, length);
  }
  function keccakAt(uint256 data, uint256 length) internal pure returns (bytes32 result) {
    assembly {
      result := keccak256(data, length)
    }
  }
  function toKeccak(Slice memory slice) internal pure returns (bytes32 result) {
    return keccakAt(slice.data, slice.length);
  }
}

// File: contracts/utils/MemcpyLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library MemcpyLib {
  function memcpy(bytes32 dest, bytes32 src, uint256 len) internal pure {
    assembly {
      for {} iszero(lt(len, 0x20)) { len := sub(len, 0x20) } {
        mstore(dest, mload(src))
        dest := add(dest, 0x20)
        src := add(src, 0x20)
      }
      let mask := sub(shl(mul(sub(32, len), 8), 1), 1)
      mstore(dest, or(and(mload(src), not(mask)), and(mload(dest), mask)))
    }
  }
}

// File: contracts/adapters/assets/curve/CurveAdapterLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ModuleLib } from "../../lib/ModuleLib.sol";

library CurveAdapterLib {
  struct Isolate {
    address curveAddress;
  }
  function getCastStorageType() internal pure returns (function (uint256) internal pure returns (Isolate storage) swap) {
    function (uint256) internal returns (uint256) cast = ModuleLib.cast;
    assembly {
      swap := cast
    }
  }
  function toIsolatePointer(uint256 key) internal pure returns (Isolate storage) {
    return getCastStorageType()(key);
  }
  function computeIsolatePointer(address instance) internal pure returns (uint256) {
    return uint256(keccak256(abi.encodePacked("isolate.curve-adapter", instance)));
  }
  function getIsolatePointer(address instance) internal pure returns (Isolate storage) {
    return toIsolatePointer(computeIsolatePointer(instance));
  }
  struct ExchangeInputs {
    int128 i;
    int128 j;
    uint256 dx;
    uint256 min_dy;
  }
  function decodeExchangeInputs(bytes memory args) internal pure returns (ExchangeInputs memory) {
    (int128 i, int128 j, uint256 dx, uint256 min_dy) = abi.decode(args, (int128, int128, uint256, uint256));
    return ExchangeInputs({
      i: i,
      j: j,
      dx: dx,
      min_dy: min_dy
    });
  }
}

// File: contracts/utils/TokenUtils.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

library TokenUtils {
  function encodeTransfer(address target, uint256 amount) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSelector(IERC20.transfer.selector, target, amount);
  }
  function sendToken(address token, address target, uint256 amount) internal returns (bool) {
    (bool success,) = token.call(encodeTransfer(target, amount));
    return success;
  }
  function encodeTransferFrom(address from, address to, uint256 amount) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount);
  }
  function transferTokenFrom(address token, address from, address to, uint256 amount) internal returns (bool) {
    (bool success,) = token.call(encodeTransferFrom(from, to, amount));
    return success;
  }
  function encodeApproval(address target, uint256 amount) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSelector(IERC20.approve.selector, target, amount);
  }
  function approveToken(address token, address target, uint256 amount) internal returns (bool) {
    (bool success,) = token.call(encodeApproval(target, amount));
    return success;
  }
  uint256 constant THRESHOLD = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
  uint256 constant MAX_UINT256 = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
  function approveForMaxIfNeeded(address token, address target) internal returns (bool) {
    uint256 approved = getApproved(token, address(this), target);
    if (approved > THRESHOLD) return true;
    if (approved != 0 && !approveToken(token, address(this), 0)) return false;
    return approveToken(token, target, MAX_UINT256);
  }
  function encodeAllowance(address source, address target) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSelector(IERC20.allowance.selector, source, target);
  }
  function decodeUint(bytes memory input) internal pure returns (uint256 retval) {
    (retval) = abi.decode(input, (uint256));
  }
  function getApproved(address token, address source, address target) internal returns (uint256) {
    (bool success, bytes memory retval) = token.call(encodeAllowance(source, target));
    if (!success || retval.length != 0x20) return 0x1;
    return decodeUint(retval);
  }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: contracts/BorrowProxyLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { IModuleRegistryProvider } from "./interfaces/IModuleRegistryProvider.sol";
import { AddressSetLib } from "./utils/AddressSetLib.sol";
import { ExtLib } from "./utils/ExtLib.sol";
import { RevertCaptureLib } from "./utils/RevertCaptureLib.sol";
import { SandboxLib } from "./utils/sandbox/SandboxLib.sol";
import { ModuleLib } from "./adapters/lib/ModuleLib.sol";

library BorrowProxyLib {
  using ModuleLib for *;
  struct ProxyIsolate {
    address payable masterAddress;
    bool unbound;
    address owner;
    address token;
    uint256 actualizedShift;
    uint256 liquidationIndex;
    uint256 repaymentIndex;
    bool isRepaying;
    bool isLiquidating;
    AddressSetLib.AddressSet liquidationSet;
    AddressSetLib.AddressSet repaymentSet;
  }
  struct ControllerIsolate {
    mapping (address => bytes32) proxyInitializerRecord;
    mapping (address => address) ownerByProxy;
    mapping (address => address) tokenByProxy;
    mapping (address => bool) isKeeper;
  }
  struct Module {
    bool isPrecompiled;
    address assetSubmodule;
    address liquidationSubmodule;
    address repaymentSubmodule;
  }
  struct ModuleDetails {
    ModuleRegistrationType moduleType;
    address target;
    bytes4[] sigs;
  }
  struct ModuleRegistration {
    ModuleRegistrationType moduleType;
    address target;
    bytes4[] sigs;
    Module module;
  }
  enum ModuleRegistrationType {
    UNINITIALIZED,
    BY_CODEHASH,
    BY_ADDRESS
  }
  struct ModuleExecution {
    address to;
    address token;
    Module encapsulated;
  }
  function registryRegisterModule(ModuleRegistry storage registry, ModuleRegistration memory registration) internal {
    if (registration.moduleType == ModuleRegistrationType.BY_CODEHASH) for (uint256 i = 0; i < registration.sigs.length; i++) {
      registerModuleByCodeHash(registry, registration.target, registration.sigs[i], registration.module);
    } else if (registration.moduleType == ModuleRegistrationType.BY_ADDRESS) for (uint256 i = 0; i < registration.sigs.length; i++) {
      registerModuleByAddress(registry, registration.target, registration.sigs[i], registration.module);
    }
  }
  function encodeLiquidate(address liquidationSubmodule) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSignature("liquidate(address)", liquidationSubmodule);
  }
  function decodeBool(bytes memory input) internal pure returns (bool retval) {
    (retval) = abi.decode(input, (bool));
  }
  function delegateLiquidate(address liquidationSubmodule) internal returns (bool) {
    (bool success, bytes memory retval) = liquidationSubmodule.delegatecall(encodeLiquidate(liquidationSubmodule));
    if (!success) revert(RevertCaptureLib.decodeError(retval));
    return decodeBool(retval);
  }
  function encodeRepay(address repaymentSubmodule) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSignature("repay(address)", repaymentSubmodule);
  }
  function delegateRepay(address repaymentSubmodule) internal returns (bool) {
    (bool success, bytes memory retval) = repaymentSubmodule.delegatecall(encodeRepay(repaymentSubmodule));
    if (!success) revert(RevertCaptureLib.decodeError(retval));
    return decodeBool(retval);
  }
  function encodeNotify(address liquidationSubmodule, bytes memory payload) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSignature("notify(address,bytes)", liquidationSubmodule, payload);
  }
  function delegateNotify(address liquidationSubmodule, bytes memory payload) internal returns (bool) {
    (bool success,) = liquidationSubmodule.delegatecall(encodeNotify(liquidationSubmodule, payload));
    return success;
  }
  function delegate(ModuleExecution memory module, bytes memory payload, uint256 value) internal returns (bool, bytes memory) {
    (bool success, bytes memory retval) = module.encapsulated.assetSubmodule.delegatecall{ gas: gasleft() }(ModuleLib.AssetSubmodulePayload({
      moduleAddress: address(uint160(module.encapsulated.assetSubmodule)),
      liquidationSubmodule: module.encapsulated.liquidationSubmodule,
      repaymentSubmodule: module.encapsulated.repaymentSubmodule,
      token: address(uint160(module.token)),
      txOrigin: tx.origin,
      to: address(uint160(module.to)),
      value: value,
      callData: payload
    }).encodeWithSelector());
    return (success, retval);
  }
  function isDefined(Module memory module) internal pure returns (bool) {
    return module.assetSubmodule != address(0x0);
  }
  function isInitialized(ControllerIsolate storage isolate, address proxyAddress) internal view returns (bool) {
    return isolate.proxyInitializerRecord[proxyAddress] != bytes32(uint256(0x0));
  }
  struct ModuleRegistry {
    mapping (bytes32 => Module) modules;
  }
  function isDisbursing(ProxyIsolate storage isolate) internal view returns (bool) {
    return isolate.isLiquidating && isolate.liquidationIndex != isolate.liquidationSet.set.length;
  }
  event BorrowProxyMade(address indexed user, address indexed proxyAddress, bytes record);
  function emitBorrowProxyMade(address user, address proxyAddress, bytes memory record) internal {
    emit BorrowProxyMade(user, proxyAddress, record);
  }
  function computeModuleKeyPreimage(address to, bytes4 signature) internal pure returns (bytes memory result) {
    result = abi.encodePacked(to, signature);
  }
  function computeModuleKey(address to, bytes4 signature) internal pure returns (bytes32) {
    return keccak256(computeModuleKeyPreimage(to, signature));
  }
  function computeCodeResolverKeyPreimage(bytes32 codehash, bytes4 signature) internal pure returns (bytes memory result) {
    result = abi.encodePacked(codehash, signature);
  }
  function computeCodeResolverKey(address to, bytes4 signature) internal view returns (bytes32) {
    bytes32 exthash = ExtLib.getExtCodeHash(to);
    return keccak256(computeCodeResolverKeyPreimage(exthash, signature));
  }
  function resolveModule(ModuleRegistry storage registry, address to, bytes4 sig) internal view returns (Module memory) {
    Module memory module = registry.modules[computeCodeResolverKey(to, sig)];
    if (!isDefined(module)) module = registry.modules[computeModuleKey(to, sig)];
    return module;
  }
  function getModuleExecution(ModuleRegistry storage registry, address to, bytes4 signature) internal view returns (ModuleExecution memory) {
    Module memory encapsulated = resolveModule(registry, to, signature);
    return ModuleExecution({
      encapsulated: encapsulated,
      token: address(0x0), // fill in in the proxy call
      to: to
    });
  }
  function validateProxyRecord(ControllerIsolate storage isolate, address proxyAddress, bytes memory data) internal view returns (bool) {
    return isolate.proxyInitializerRecord[proxyAddress] == keccak256(data);
  }
  function mapProxyRecord(ControllerIsolate storage isolate, address proxyAddress, bytes memory data) internal {
    isolate.proxyInitializerRecord[proxyAddress] = keccak256(data);
  }
  function setProxyOwner(ControllerIsolate storage isolate, address proxyAddress, address identity) internal {
    isolate.ownerByProxy[proxyAddress] = identity;
  }
  function setProxyToken(ControllerIsolate storage isolate, address proxyAddress, address token) internal {
    isolate.tokenByProxy[proxyAddress] = token;
  }
  function getProxyToken(ControllerIsolate storage isolate, address proxyAddress) internal view returns (address) {
    return isolate.tokenByProxy[proxyAddress];
  }
  function getProxyOwner(ControllerIsolate storage isolate, address proxyAddress) internal view returns (address) {
    return isolate.ownerByProxy[proxyAddress];
  }
  function registerModuleByAddress(ModuleRegistry storage registry, address to, bytes4 signature, Module memory module) internal {
    registry.modules[computeModuleKey(to, signature)] = module;
  }
  function registerModuleByCodeHash(ModuleRegistry storage registry, address to, bytes4 signature, Module memory module) internal {
    registry.modules[computeCodeResolverKey(to, signature)] = module;
  }
  function fetchModule(ProxyIsolate storage isolate, address to, bytes4 signature) internal returns (ModuleExecution memory) {
    return ModuleExecution({
      encapsulated: IModuleRegistryProvider(isolate.masterAddress).fetchModuleHandler(to, signature),
      token: isolate.token,
      to: to
    });
  }
}

// File: @openzeppelin/contracts/utils/Create2.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address) {
        address addr;
        require(address(this).balance >= amount, "Create2: insufficient balance");
        require(bytecode.length != 0, "Create2: bytecode length is zero");
        // solhint-disable-next-line no-inline-assembly
        assembly {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
        }
        require(addr != address(0), "Create2: Failed on deploy");
        return addr;
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) {
        bytes32 _data = keccak256(
            abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)
        );
        return address(uint256(_data));
    }
}

// File: contracts/interfaces/IModuleRegistryProvider.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { BorrowProxyLib } from "../BorrowProxyLib.sol";

interface IModuleRegistryProvider {
  function fetchModuleHandler(address to, bytes4 sig) external returns (BorrowProxyLib.Module memory);
}

// File: contracts/utils/AddressSetLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library AddressSetLib {
  struct AddressSet {
    mapping (address => bool) uniq;
    address[] set;
  }
  function insert(AddressSet storage addressSet, address item) internal {
    if (addressSet.uniq[item]) return;
    addressSet.set.push(item);
  }
  function get(AddressSet storage addressSet, uint256 i) internal view returns (address) {
    return addressSet.set[i];
  }
  function size(AddressSet storage addressSet) internal view returns (uint256) {
    return addressSet.set.length;
  }
}

// File: contracts/utils/ExtLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library ExtLib {
  function getExtCodeHash(address target) internal view returns (bytes32 result) {
    assembly {
      result := extcodehash(target)
    }
  }
  function isContract(address target) internal view returns (bool result) {
    assembly {
      result := iszero(iszero(extcodesize(target)))
    }
  }
}

// File: contracts/utils/RevertCaptureLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SliceLib } from "./SliceLib.sol";

library RevertCaptureLib {
  using SliceLib for *;
  uint32 constant REVERT_WITH_REASON_MAGIC = 0x08c379a0; // keccak256("Error(string)")
  function decodeString(bytes memory input) internal pure returns (string memory retval) {
    (retval) = abi.decode(input, (string));
  }
  function decodeError(bytes memory buffer) internal pure returns (string memory) {
    if (buffer.length == 0) return "captured empty revert buffer";
    if (uint32(uint256(bytes32(buffer.toSlice(0, 4).asWord()))) != REVERT_WITH_REASON_MAGIC) return "captured a revert error, but it doesn't conform to the standard";
    bytes memory revertMessageEncoded = buffer.toSlice(4).copy();
    if (revertMessageEncoded.length == 0) return "captured empty revert message";
    (string memory revertMessage) = decodeString(revertMessageEncoded);
    return revertMessage;
  }
}

// File: contracts/utils/sandbox/SandboxLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SliceLib } from "../SliceLib.sol";
import { SafeViewLib } from "./SafeViewLib.sol";
import { BorrowProxy } from "../../BorrowProxy.sol";
import { ShifterBorrowProxyLib } from "../../ShifterBorrowProxyLib.sol";
import { RevertCaptureLib } from "../../utils/RevertCaptureLib.sol";
import { PreprocessorLib } from "../../preprocessors/lib/PreprocessorLib.sol";
import { StringLib } from "../../utils/StringLib.sol";

library SandboxLib {
  using SafeViewLib for *;
  using SliceLib for *;
  using StringLib for *;
  using PreprocessorLib for *;
  struct ProtectedExecution {
    address to;
    bytes txData;
    bool success;
    bytes returnData;
  }
  function applyExecutionResult(ProtectedExecution[][] memory trace, uint256 index, ShifterBorrowProxyLib.InitializationAction[] memory preprocessed) internal pure returns (bool) {
    ProtectedExecution[] memory execution = new ProtectedExecution[](preprocessed.length);
    for (uint256 i = 0; i < execution.length; i++) {
      execution[i].txData = preprocessed[i].txData;
      execution[i].to = preprocessed[i].to;
    }
    trace[index] = execution;
    return execution.length != 0;
  }
  struct Context {
    address preprocessorAddress;
    ProtectedExecution[][] trace;
  }
  function encodeContext(ExecutionContext memory input) internal pure returns (bytes memory context) {
    context = abi.encode(input);
  }
  function toContext(bytes memory input) internal pure returns (ExecutionContext memory context) {
    (context) = abi.decode(input, (ExecutionContext));
  }
  function _write(ProtectedExecution[][] memory trace, uint256 newSize) internal pure {
    assembly {
      mstore(trace, newSize)
    }
  }
  function _restrict(Context memory context) internal pure {
    _write(context.trace, 0);
  }
  function _grow(Context memory context) internal pure {
    ProtectedExecution[][] memory trace = context.trace;
    uint256 newSize = trace.length;
    _write(trace, newSize + 1);
  }
  function toInitializationActions(bytes memory input) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory action) {
    (action) = abi.decode(input, (ShifterBorrowProxyLib.InitializationAction[]));
  }
  function encodeInitializationActions(ShifterBorrowProxyLib.InitializationAction[] memory input) internal pure returns (bytes memory result) {
    result = abi.encode(input);
  }
  function _shrink(Context memory context) internal pure {
    _write(context.trace, context.trace.length - 1);
  }
  function encodeProxyCall(ProtectedExecution memory execution) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSelector(BorrowProxy.proxy.selector, execution.to, 0, execution.txData);
  }
  function toFlat(ProtectedExecution[][] memory execution) internal pure returns (ProtectedExecution[] memory trace) {
    uint256 total = 0;
    for (uint256 i = 0; i < execution.length; i++) {
      total += execution[i].length;
    }
    trace = new ProtectedExecution[](total);
    uint256 seen = 0;
    for (uint256 i = 0; i < execution.length; i++) {
      for (uint256 j = 0; j < execution[i].length; j++) {
        trace[seen] = execution[i][j];
      }
    }
  }
  function getNewContext(ShifterBorrowProxyLib.InitializationAction[] memory actions) internal pure returns (Context memory context) {
    ProtectedExecution[][] memory trace = new ProtectedExecution[][](actions.length);
    for (uint256 i = 0; i < actions.length; i++) {
      trace[i] = new ProtectedExecution[](1);
      trace[i][0].to = actions[i].to;
      trace[i][0].txData = actions[i].txData;
    }
    context = Context({
      trace: trace,
      preprocessorAddress: address(0)
    });
  }
  struct ExecutionContext {
    ProtectedExecution last;
    address preprocessorAddress;
  }
  function executeSafeView(bytes memory creationCode, uint256 index, Context memory context) internal returns (SafeViewLib.SafeViewResult memory result) {
    ExecutionContext memory executionContext;
    executionContext.preprocessorAddress = context.preprocessorAddress;
    if (index != 0) {
      ProtectedExecution[] memory lastBatch = context.trace[index];
      if (lastBatch.length != 0) executionContext.last = lastBatch[lastBatch.length - 1];
    }
    result = creationCode.safeView(encodeContext(executionContext));
  }
  function processActions(ShifterBorrowProxyLib.InitializationAction[] memory actions) internal returns (ProtectedExecution[] memory trace) {
    Context memory context = getNewContext(actions);
    for (uint256 i = 0; i < actions.length; i++) {
      ProtectedExecution[] memory execution = context.trace[i];
      if (execution[0].to == address(0x0)) {
        context.preprocessorAddress = execution[0].txData.deriveViewAddress();
        SafeViewLib.SafeViewResult memory safeViewResult = executeSafeView(execution[0].txData, i, context);
        execution[0].txData = new bytes(0x0);
        if (safeViewResult.success) {
          if (!applyExecutionResult(context.trace, i, toInitializationActions(safeViewResult.data))) break;
        } else {
          execution[0].returnData = safeViewResult.data;
          execution[0].success = safeViewResult.success;
// for debugging
          bytes memory data = execution[0].returnData;
          assembly {
            revert(add(data, 0x20), mload(data))
          }
// end
          continue;
        }
      }
      execution = context.trace[i];
      for (uint256 j = 0; j < execution.length; j++) {
        (bool success, bytes memory returnData) = address(this).call(encodeProxyCall(execution[j]));
        execution[j].success = success;
// for debugging
        if (!execution[j].success) {
          assembly {
            revert(add(0x20, returnData), mload(returnData))
          }
        }
// end
        execution[j].returnData = returnData;
      }
    }
    return toFlat(context.trace);
  }
}

// File: contracts/utils/sandbox/SafeViewLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ISafeView } from "./ISafeView.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";

library SafeViewLib {
  struct SafeViewResult {
    bool success;
    bytes data;
  }
  function executeLogic(address viewLayer, bytes memory context) internal returns (SafeViewResult memory) {
    (bool success, bytes memory retval) = viewLayer.delegatecall(encodeExecute(context));
    return SafeViewResult({
      success: success,
      data: retval
    });
  }
  function encodeResult(SafeViewResult memory input) internal pure returns (bytes memory retval) {
    retval = abi.encode(input);
  }
  function revertWithData(bytes memory input) internal pure {
    assembly {
      revert(add(input, 0x20), mload(input))
    }
  }
  function decodeViewResult(bytes memory data) internal pure returns (SafeViewResult memory result) {
     (result) = abi.decode(data, (SafeViewResult));
   }
   function encodeExecuteSafeView(bytes memory creationCode, bytes memory context) internal pure returns (bytes memory retval) {
     retval = abi.encodeWithSelector(ISafeView._executeSafeView.selector, creationCode, context);
   }
   function encodeExecute(bytes memory context) internal pure returns (bytes memory retval) {
     retval = abi.encodeWithSelector(ISafeView.execute.selector, context);
   }
  function safeView(bytes memory creationCode, bytes memory context) internal returns (SafeViewLib.SafeViewResult memory) {
    (/* bool success */, bytes memory retval) = address(this).call(encodeExecuteSafeView(creationCode, context));
    return decodeViewResult(retval);
  }
  bytes32 constant STEALTH_VIEW_DEPLOY_SALT = 0xad53495153c7c363e98a26920ec679e0e687636458f6908c91cf6deadb190801;
  function GET_STEALTH_VIEW_DEPLOY_SALT() internal pure returns (bytes32) {
    return STEALTH_VIEW_DEPLOY_SALT;
  }
  function deriveViewAddress(bytes memory creationCode) internal view returns (address) {
    return Create2.computeAddress(STEALTH_VIEW_DEPLOY_SALT, keccak256(creationCode));
  }
}

// File: contracts/utils/sandbox/ISafeView.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface ISafeView {
  function execute(bytes calldata) external;
  function _executeSafeView(bytes calldata, bytes calldata) external;
}

// File: contracts/BorrowProxy.sol



// SPDX-License-Identifier: MIT
pragma experimental ABIEncoderV2;
pragma solidity ^0.6.0;

import { BorrowProxyLib } from "./BorrowProxyLib.sol";
import { RevertCaptureLib } from "./utils/RevertCaptureLib.sol";
import { SliceLib } from "./utils/SliceLib.sol";
import { IShifter } from "./interfaces/IShifter.sol";
import { IBorrowProxyController } from "./interfaces/IBorrowProxyController.sol";
import { AddressSetLib } from "./utils/AddressSetLib.sol";
import { ModuleLib } from "./adapters/lib/ModuleLib.sol";

contract BorrowProxy {
  using SliceLib for *;
  using BorrowProxyLib for *;
  using AddressSetLib for *;
  BorrowProxyLib.ProxyIsolate isolate;
  modifier onlyOwnerOrPool {
   require(msg.sender == isolate.owner || msg.sender == isolate.masterAddress || msg.sender == address(this), "borrow proxy can only be used by borrower");
    _;
  }
  function setup(address owner, address token, bool unbound) public returns (bool) {
    require(isolate.owner == address(0x0), "can't initialize twice");
    isolate.masterAddress = msg.sender;
    isolate.owner = owner;
    isolate.unbound = unbound;
    isolate.token = token;
    return true;
  }
  function validateProxyRecord(bytes memory record) internal returns (bool) {
    return IBorrowProxyController(isolate.masterAddress).validateProxyRecordHandler(record);
  }
  function proxy(address to, uint256 value, bytes memory payload) public onlyOwnerOrPool {
    require(to != isolate.masterAddress, "can't target the ShifterPool");
    bytes4 sig = bytes4(uint32(uint256(payload.toSlice(0, 4).asWord())));
    BorrowProxyLib.ModuleExecution memory module = isolate.fetchModule(to, sig);
    module.token = isolate.token;
    if (isolate.unbound && !module.encapsulated.isPrecompiled) {
      (bool success, bytes memory retval) = to.call{
        value: value
      }(payload);
      if (!success) revert(RevertCaptureLib.decodeError(retval));
      ModuleLib.bubbleResult(success, retval);
      return;
    }
    require(module.encapsulated.isDefined(), "function handler not registered");
    (bool success, bytes memory retval) = module.delegate(payload, value);
    if (!success) revert(RevertCaptureLib.decodeError(retval));
    if (module.encapsulated.liquidationSubmodule != address(0x0)) isolate.liquidationSet.insert(module.encapsulated.liquidationSubmodule);
    if (module.encapsulated.repaymentSubmodule != address(0x0)) {
      isolate.repaymentSet.insert(module.encapsulated.repaymentSubmodule);
    }
    ModuleLib.bubbleResult(success, retval);
  }
  receive() external payable virtual {
    // just receive ether, do nothing
  } 
  fallback() external payable virtual {}
}

// File: contracts/interfaces/IShifter.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IShifter {
  function mint(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external returns (uint256);
  function mintFee() external view returns (uint256);
}

// File: contracts/interfaces/IBorrowProxyController.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IBorrowProxyController {
  function getProxyOwnerHandler() external returns (address);
  function validateProxyRecordHandler(bytes calldata) external returns (bool);
}

// File: contracts/ShifterBorrowProxyLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { IInitializationActionsReceiver } from "./interfaces/IInitializationActionsReceiver.sol";
import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BorrowProxy } from "./BorrowProxy.sol";
import { BorrowProxyLib } from "./BorrowProxyLib.sol";
import { TokenUtils } from "./utils/TokenUtils.sol";
import { RevertCaptureLib } from "./utils/RevertCaptureLib.sol";
import { SandboxLib } from "./utils/sandbox/SandboxLib.sol";
import { IShifter } from "./interfaces/IShifter.sol";
import { IShifterERC20 } from "./interfaces/IShifterERC20.sol";

library ShifterBorrowProxyLib {
  using SafeMath for *;
  using TokenUtils for *;
  struct ProxyRecord {
    LiquidityRequest request;
    LenderRecord loan;
    uint256 expected;
  }
  struct LiquidityRequest {
    address payable borrower;
    address token;
    bytes32 nonce;
    uint256 amount;
    bool forbidLoan;
    InitializationAction[] actions;
  }
  struct InitializationAction {
    address to;
    bytes txData;
  }
  event BorrowProxyInitialization(address indexed proxyAddress, SandboxLib.ProtectedExecution[]);
  function emitBorrowProxyInitialization(address /* proxyAddress */, SandboxLib.ProtectedExecution[] memory /* trace */) internal {
//    emit BorrowProxyInitialization(proxyAddress, trace);
  }
    
  function encodeProxyRecord(ProxyRecord memory record) internal pure returns (bytes memory result) {
    result = abi.encode(record);
  }
  function decodeProxyRecord(bytes memory record) internal pure returns (ProxyRecord memory result) {
    (result) = abi.decode(record, (ProxyRecord));
  }
  struct LiquidityRequestParcel {
    LiquidityRequest request;
    uint256 gasRequested;
    bytes signature;
  }
  function computeDepositAddress(LiquidityRequestParcel memory /* parcel */, address /* mpkh */, bool /* btcTestnet */) internal pure returns (string memory result) {
    result = "";
  }
  struct LenderParams {
    uint256 timeoutExpiry;
    uint256 bond;
    uint256 poolFee;
    uint256 keeperFee;
  }
  struct LenderRecord {
    address keeper;
    LenderParams params;
  }
  event ShifterBorrowProxyRepaid(address indexed user, ProxyRecord record);
  function emitShifterBorrowProxyRepaid(address user, ProxyRecord memory record) internal {
    emit ShifterBorrowProxyRepaid(user, record);
  }
  function encodeBorrowerMessage(LiquidityRequest memory params, bytes memory parcelActionsEncoded) internal pure returns (bytes memory result) {
    result = abi.encodePacked(params.borrower, params.token, params.nonce, params.amount, params.forbidLoan, parcelActionsEncoded);
  }
  function computeBorrowerSalt(LiquidityRequest memory params) internal pure returns (bytes32 result) {
    result = keccak256(computeBorrowerSaltPreimage(params));
  }
  function computeBorrowerSaltPreimage(LiquidityRequest memory params) internal pure returns (bytes memory result) {
    bytes memory parcelActionsEncoded = encodeParcelActions(params.actions);
    result = encodeBorrowerMessage(params, parcelActionsEncoded);
  }
  function encodeParcelActions(InitializationAction[] memory actions) internal pure returns (bytes memory retval) {
    retval = abi.encode(actions);
  }
  function computeLiquidityRequestParcelMessage(LiquidityRequestParcel memory parcel, bytes memory parcelActionsEncoded) internal view returns (bytes memory retval) {
    retval = abi.encodePacked(address(this), parcel.request.token, parcel.request.nonce, parcel.request.amount, parcel.gasRequested, parcel.request.forbidLoan, parcelActionsEncoded);
  }
  function computeLiquidityRequestPreimage(LiquidityRequestParcel memory parcel) internal view returns (bytes memory result) {
    bytes memory parcelActionsEncoded = encodeParcelActions(parcel.request.actions);
    result = computeLiquidityRequestParcelMessage(parcel, parcelActionsEncoded);
  }
  function computeLiquidityRequestHash(LiquidityRequestParcel memory parcel) internal view returns (bytes32 result) {
    result = keccak256(computeLiquidityRequestPreimage(parcel));
  }
  function validateSignature(LiquidityRequestParcel memory parcel, bytes32 hash) internal pure returns (bool) {
    return parcel.request.borrower == ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), parcel.signature);
  }
  function validateSignature(LiquidityRequestParcel memory parcel) internal view returns (bool) {
    return parcel.request.borrower == ECDSA.recover(ECDSA.toEthSignedMessageHash(computeLiquidityRequestHash(parcel)), parcel.signature);
  }
  struct ShiftParameters {
    bytes32 txhash;
    uint256 vout;
    bytes32 pHash;
    uint256 amount;
    bytes darknodeSignature;
  }
  struct TriggerParcel {
    ProxyRecord record;
    ShiftParameters shiftParameters;
  }
  struct SansBorrowShiftParcel {
    LiquidityRequestParcel liquidityRequestParcel;
    ShiftParameters shiftParameters;
    InitializationAction[] actions;
  }
  function decodeTriggerParcel(bytes memory parcel) internal pure returns (TriggerParcel memory result) {
    (result) = abi.decode(parcel, (TriggerParcel));
  }
  function encodeNPreimage(TriggerParcel memory parcel) internal pure returns (bytes memory result) {
    result = abi.encodePacked(parcel.record.request.nonce, parcel.shiftParameters.txhash, parcel.shiftParameters.vout);
  }
  function computeNHash(TriggerParcel memory parcel) internal pure returns (bytes32) {
    return keccak256(encodeNPreimage(parcel));
  }
  uint256 constant BIPS_DENOMINATOR = 10000;
  function computeExpectedAmount(uint256 amount, address shifter, address token) internal returns (uint256 expected) {
    uint256 mintFee = getMintFee(shifter);
    uint256 underlyingAmount = getUnderlyingAmount(token, amount);
    uint256 fee = underlyingAmount.mul(mintFee).div(BIPS_DENOMINATOR);
    expected = underlyingAmount.sub(fee);
  }
  function getMintFee(address shifter) internal view returns (uint256 mintFee) {
    mintFee = uint256(IShifter(shifter).mintFee());
  }
  function getUnderlyingAmount(address token, uint256 amount) internal returns (uint256 underlyingAmount) {
    underlyingAmount = IShifterERC20(token).fromUnderlying(amount);
  }
  function computePostFee(ProxyRecord memory record) internal pure returns (uint256) {
    return record.expected.sub(computePoolFee(record).add(computeKeeperFee(record)));
  }
  function computePoolFee(ProxyRecord memory record) internal pure returns (uint256) {
    return record.expected.mul(record.loan.params.poolFee).div(uint256(1 ether));
  }
  function computeKeeperFee(ProxyRecord memory record) internal pure returns (uint256) {
    return record.expected.mul(record.loan.params.keeperFee).div(uint256(1 ether));
  }
  function computeAdjustedKeeperFee(ProxyRecord memory record, uint256 actual) internal pure returns (uint256) {
    return actual.mul(record.loan.params.keeperFee).div(uint256(1 ether));
  }
}

// File: contracts/interfaces/IInitializationActionsReceiver.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SandboxLib } from "../utils/sandbox/SandboxLib.sol";
import { ShifterBorrowProxyLib } from "../ShifterBorrowProxyLib.sol";

interface IInitializationActionsReceiver {
  function receiveInitializationActions(ShifterBorrowProxyLib.InitializationAction[] calldata actions) external returns (SandboxLib.Context memory context);
}

// File: @openzeppelin/contracts/cryptography/ECDSA.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Check the signature length
        if (signature.length != 65) {
            revert("ECDSA: invalid signature length");
        }

        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }

        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            revert("ECDSA: invalid signature 's' value");
        }

        if (v != 27 && v != 28) {
            revert("ECDSA: invalid signature 'v' value");
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * replicates the behavior of the
     * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
     * JSON-RPC method.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }
}

// File: @openzeppelin/contracts/math/SafeMath.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (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 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: contracts/interfaces/IShifterERC20.sol



pragma solidity ^0.6.0;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IShifterERC20 {
  function fromUnderlying(uint256 amount) external returns (uint256);
}
  

// File: contracts/preprocessors/lib/PreprocessorLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SandboxLib } from "../../utils/sandbox/SandboxLib.sol";
import { ShifterBorrowProxyLib } from "../../ShifterBorrowProxyLib.sol";

library PreprocessorLib {
  function toContext(bytes memory input) internal pure returns (SandboxLib.ExecutionContext memory) {
    return SandboxLib.toContext(input);
  }
  function then(ShifterBorrowProxyLib.InitializationAction memory action, ShifterBorrowProxyLib.InitializationAction memory nextAction) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    result = new ShifterBorrowProxyLib.InitializationAction[](2);
    result[0] = action;
    result[1] = nextAction;
  }
  function getLastExecution(SandboxLib.Context memory context) internal pure returns (bool foundLast, SandboxLib.ProtectedExecution memory execution) {
    if (context.trace.length == 0) return (foundLast, execution);
    SandboxLib.ProtectedExecution[] memory lastBatch = context.trace[context.trace.length - 1];
    execution = lastBatch[lastBatch.length - 1];
  }
  function then(ShifterBorrowProxyLib.InitializationAction[] memory actions, ShifterBorrowProxyLib.InitializationAction[] memory nextActions) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    result = new ShifterBorrowProxyLib.InitializationAction[](actions.length + nextActions.length);
    uint256 i = 0;
    for (; i < actions.length; i++) {
      result[i] = actions[i];
    }
    for (uint256 j = 0; j < nextActions.length; j++) {
      result[i] = nextActions[j];
      i++;
    }
  }
  function then(ShifterBorrowProxyLib.InitializationAction[] memory actions, ShifterBorrowProxyLib.InitializationAction memory nextAction) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    result = then(actions, toList(nextAction));
  }
  function toList(ShifterBorrowProxyLib.InitializationAction memory input) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    result = new ShifterBorrowProxyLib.InitializationAction[](1);
    result[0] = input;
  }
  function sendTransaction(address to, bytes memory txData) internal pure returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    return toList(ShifterBorrowProxyLib.InitializationAction({
      to: to,
      txData: txData
    }));
  }
}

// File: contracts/utils/StringLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library StringLib {
    /// @notice Convert a uint value to its decimal string representation
    // solium-disable-next-line security/no-assign-params
    function toString(uint256 _i) internal pure returns (string memory) {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len - 1;
        while (_i != 0) {
            bstr[k--] = bytes1(uint8(48 + (_i % 10)));
            _i /= 10;
        }
        return string(bstr);
    }

    /// @notice Convert a bytes32 value to its hex string representation
    function toString(bytes32 _value) internal pure returns (string memory) {
        bytes memory alphabet = "0123456789abcdef";

        bytes memory str = new bytes(32 * 2 + 2);
        str[0] = "0";
        str[1] = "x";
        for (uint256 i = 0; i < 32; i++) {
            str[2 + i * 2] = alphabet[uint256(uint8(_value[i] >> 4))];
            str[3 + i * 2] = alphabet[uint256(uint8(_value[i] & 0x0f))];
        }
        return string(str);
    }

    /// @notice Convert an address to its hex string representation
    function toString(address _addr) internal pure returns (string memory) {
        bytes32 value = bytes32(uint256(_addr));
        bytes memory alphabet = "0123456789abcdef";

        bytes memory str = new bytes(20 * 2 + 2);
        str[0] = "0";
        str[1] = "x";
        for (uint256 i = 0; i < 20; i++) {
            str[2 + i * 2] = alphabet[uint256(uint8(value[i + 12] >> 4))];
            str[3 + i * 2] = alphabet[uint256(uint8(value[i + 12] & 0x0f))];
        }
        return string(str);
    }
    function toString(bytes memory input) internal pure returns (string memory) {
      return string(input);
    }
}

// File: contracts/interfaces/ICurve.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

abstract contract ICurve {
  uint256 constant N_COINS = 4;
  function add_liquidity(uint256[N_COINS] calldata amounts, uint256 min_mint_amount) external virtual;
  function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external virtual;
  function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external virtual;
  function remove_liquidity(uint256 _amount, uint256[N_COINS] calldata min_amounts) external virtual;
  function remove_liquidity_imbalance(uint256[N_COINS] calldata amounts, uint256 max_burn_amount) external virtual;
  function coins(uint256 i) external virtual returns (address payable);
  function underlying_coins(uint256 i) external virtual returns (address payable);
}

// File: contracts/adapters/assets/erc20/ERC20Adapter.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ModuleLib } from "../../lib/ModuleLib.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20AdapterLib } from "./ERC20AdapterLib.sol";
import { TokenUtils } from "../../../utils/TokenUtils.sol";
import { BorrowProxyLib } from "../../../BorrowProxyLib.sol";

contract ERC20Adapter {
  using ERC20AdapterLib for *;
  using ModuleLib for *;
  using TokenUtils for *;
  BorrowProxyLib.ProxyIsolate proxyIsolate;
  function getExternalIsolateHandler() external pure returns (ERC20AdapterLib.Isolate memory) {
    return ERC20AdapterLib.getIsolatePointer();
  }
  function repay(address /* moduleAddress */)  public returns (bool) {
    return proxyIsolate.processEscrowForwards();
  }
  function handle(ModuleLib.AssetSubmodulePayload memory payload) public payable {
    (bytes4 sig, bytes memory args) = payload.callData.splitPayload();
    if (sig == ERC20.transfer.selector) {
       ERC20AdapterLib.TransferInputs memory inputs = args.decodeTransferInputs();
       require(proxyIsolate.sendToEscrow(inputs.recipient, payload.to, inputs.amount), "token transfer to escrow wallet failed");
    } else if (sig == ERC20.approve.selector) {
      // do nothing
    } else revert("unsupported token call");
  }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;
    using Address for address;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
     * called.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

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

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Sets {decimals} to a value other than the default one of 18.
     *
     * WARNING: This function should only be called from the constructor. Most
     * applications that interact with token contracts will not expect
     * {decimals} to ever change, and may work incorrectly if it does.
     */
    function _setupDecimals(uint8 decimals_) internal {
        _decimals = decimals_;
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

// File: @openzeppelin/contracts/GSN/Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.2;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies in extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain`call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return _functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        return _functionCallWithValue(target, data, value, errorMessage);
    }

    function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// File: contracts/adapters/assets/erc20/ERC20AdapterLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ModuleLib } from "../../lib/ModuleLib.sol";
import { AssetForwarder } from "../../lib/AssetForwarder.sol";
import { AssetForwarderLib } from "../../lib/AssetForwarderLib.sol";
import { ERC20Adapter } from "./ERC20Adapter.sol";
import { BorrowProxyLib } from "../../../BorrowProxyLib.sol";
import { StringLib } from "../../../utils/StringLib.sol";
import { ShifterPoolLib } from "../../../ShifterPoolLib.sol";
import { TokenUtils } from "../../../utils/TokenUtils.sol";
import { FactoryLib } from "../../../FactoryLib.sol";
import { ShifterPool } from "../../../ShifterPool.sol";

library ERC20AdapterLib {
  using ShifterPoolLib for *;
  using TokenUtils for *;
  using StringLib for *;
  struct EscrowRecord {
    address recipient;
    address token;
  }
  struct Isolate {
    EscrowRecord[] payments;
    bool isProcessing;
    uint256 processed;
  }
  function isDone(Isolate storage isolate) internal view returns (bool) {
    return !isolate.isProcessing && isolate.payments.length == isolate.processed;
  }
  function computeIsolatePointer() public pure returns (uint256) {
    return uint256(keccak256("isolate.erc20-adapter"));
  }
  function computeForwarderSalt(uint256 index) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(index));
  }
  function computeForwarderAddress(BorrowProxyLib.ProxyIsolate storage proxyIsolate, uint256 index) internal view returns (address) {
    return FactoryLib.deriveInstanceAddress(proxyIsolate.masterAddress, ShifterPool(proxyIsolate.masterAddress).getAssetForwarderImplementationHandler(), keccak256(abi.encodePacked(AssetForwarderLib.GET_ASSET_FORWARDER_IMPLEMENTATION_SALT(), address(this), computeForwarderSalt(index))));
  }
  function liquidate(BorrowProxyLib.ProxyIsolate storage proxyIsolate) internal returns (bool) {
    return processEscrowReturns(proxyIsolate);
  }
  struct TransferInputs {
    address recipient;
    uint256 amount;
  }
  function decodeTransferInputs(bytes memory args) internal pure returns (TransferInputs memory) {
    (address recipient, uint256 amount) = abi.decode(args, (address, uint256));
    return TransferInputs({
      recipient: recipient,
      amount: amount
    });
  }
  event Log(address indexed data);
  function forwardEscrow(BorrowProxyLib.ProxyIsolate storage proxyIsolate, EscrowRecord memory record, uint256 index) internal {
    address forwarder = proxyIsolate.deployAssetForwarder(computeForwarderSalt(index));
    emit Log(forwarder);
    emit Log(computeForwarderAddress(proxyIsolate, index));
    AssetForwarder(forwarder).forwardAsset(address(uint160(record.recipient)), record.token);
  }
  function returnEscrow(BorrowProxyLib.ProxyIsolate storage proxyIsolate, EscrowRecord memory record, uint256 index) internal {
    address forwarder = proxyIsolate.deployAssetForwarder(computeForwarderSalt(index));
    AssetForwarder(forwarder).forwardAsset(address(uint160(address(this))), record.token);
  }
  uint256 constant MINIMUM_GAS_TO_PROCESS = 5e5;
  uint256 constant MAX_RECORDS = 100;
  function processEscrowForwards(BorrowProxyLib.ProxyIsolate storage proxyIsolate) internal returns (bool) {
    Isolate storage isolate = getIsolatePointer();
    if (!isolate.isProcessing) isolate.isProcessing = true;
    for (uint256 i = isolate.processed; i < isolate.payments.length; i++) {
      if (gasleft() < MINIMUM_GAS_TO_PROCESS) {
        isolate.processed = i;
        return false;
      } else {
        forwardEscrow(proxyIsolate, isolate.payments[i], i);
      }
    }
    return true;
  }
  function processEscrowReturns(BorrowProxyLib.ProxyIsolate storage proxyIsolate) internal returns (bool) {
    Isolate storage isolate = getIsolatePointer();
    if (!isolate.isProcessing) isolate.isProcessing = true;
    for (uint256 i = isolate.processed; i < isolate.payments.length; i++) {
      if (gasleft() < MINIMUM_GAS_TO_PROCESS) {
        isolate.processed = i;
        return false;
      } else {
        returnEscrow(proxyIsolate, isolate.payments[i], i);
      }
    }
    return true;
  }
  function sendToEscrow(BorrowProxyLib.ProxyIsolate storage proxyIsolate, address recipient, address token, uint256 amount) internal returns (bool) {
     Isolate storage isolate = getIsolatePointer();
     address escrowWallet = computeForwarderAddress(proxyIsolate, isolate.payments.length);
     installEscrowRecord(recipient, token);
     return token.sendToken(escrowWallet, amount);
  }
  function installEscrowRecord(address recipient, address token) internal {
    Isolate storage isolate = getIsolatePointer();
    isolate.payments.push(EscrowRecord({
      recipient: recipient,
      token: token
    }));
  }
  function deriveNextForwarderAddress(BorrowProxyLib.ProxyIsolate storage proxyIsolate) internal view returns (address) {
    Isolate storage isolate = getIsolatePointer();
    return computeForwarderAddress(proxyIsolate, isolate.payments.length);
  }
  function getCastStorageType() internal pure returns (function (uint256) internal pure returns (Isolate storage) swap) {
    function (uint256) internal returns (uint256) cast = ModuleLib.cast;
    assembly {
      swap := cast
    }
  }
  function toIsolatePointer(uint256 key) internal pure returns (Isolate storage) {
    return getCastStorageType()(key);
  }
  function getIsolatePointer() internal pure returns (Isolate storage) {
    return toIsolatePointer(computeIsolatePointer());
  }
}

// File: contracts/adapters/lib/AssetForwarder.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenUtils } from "../../utils/TokenUtils.sol";

contract AssetForwarder {
  using TokenUtils for *;
  bool public locked;
  function lock() public {
    locked = true;
  }
  function forwardAsset(address payable target, address token) public payable {
    require(!locked);
    if (token != address(0x0)) require(token.sendToken(target, IERC20(token).balanceOf(address(this))), "erc20 forward failure");
    selfdestruct(target);
  }
}

// File: contracts/adapters/lib/AssetForwarderLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { AssetForwarder } from "./AssetForwarder.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { FactoryLib } from "../../FactoryLib.sol";
import { ShifterPool } from "../../ShifterPool.sol";

library AssetForwarderLib {
  bytes32 constant ASSET_FORWARDER_IMPLEMENTATION_SALT = 0x6b6f6b75258f8f4cdb6ef275682bc2be3e8b970e5e4417113bd1ea53622d907e; // keccak("asset-forwarder-implementation")
  function GET_ASSET_FORWARDER_IMPLEMENTATION_SALT() internal pure returns (bytes32) {
    return ASSET_FORWARDER_IMPLEMENTATION_SALT;
  }
  function deployAssetForwarder() internal returns (address output) {
    output = Create2.deploy(0, ASSET_FORWARDER_IMPLEMENTATION_SALT, type(AssetForwarder).creationCode);
  }
  function deployAssetForwarderClone(address target, bytes32 salt) internal returns (address output) {
    output = FactoryLib.create2Clone(target, uint256(salt));
  }
  function deriveAssetForwarderAddress(address payable shifterPool, address from, bytes32 salt) internal view returns (address) {
    return FactoryLib.deriveInstanceAddress(shifterPool, ShifterPool(shifterPool).getAssetForwarderImplementationHandler(), keccak256(abi.encodePacked(GET_ASSET_FORWARDER_IMPLEMENTATION_SALT(), from, salt)));
  }
}

// File: contracts/FactoryLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";

library FactoryLib {
  function computeCreationCode(address target) internal view returns (bytes memory clone) {
    clone = computeCreationCode(address(this), target);
  }
  function computeCreationCode(address deployer, address target) internal pure returns (bytes memory clone) {
      bytes memory consData = abi.encodeWithSignature("cloneConstructor(bytes)", new bytes(0));
      clone = new bytes(99 + consData.length);
      assembly {
        mstore(add(clone, 0x20),
           0x3d3d606380380380913d393d73bebebebebebebebebebebebebebebebebebebe)
        mstore(add(clone, 0x2d),
           mul(deployer, 0x01000000000000000000000000))
        mstore(add(clone, 0x41),
           0x5af4602a57600080fd5b602d8060366000396000f3363d3d373d3d3d363d73be)
           mstore(add(clone, 0x60),
           mul(target, 0x01000000000000000000000000))
        mstore(add(clone, 116),
           0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
      }
      for (uint256 i = 0; i < consData.length; i++) {
        clone[i + 99] = consData[i];
      }
  }
  function deriveInstanceAddress(address target, bytes32 salt) internal view returns (address) {
    return Create2.computeAddress(salt, keccak256(computeCreationCode(target)));
  }
  function deriveInstanceAddress(address from, address target, bytes32 salt) internal pure returns (address) {
     return Create2.computeAddress(salt, keccak256(computeCreationCode(from, target)), from);
  }
  function create2Clone(address target, uint saltNonce) internal returns (address result) {
    bytes memory clone = computeCreationCode(target);
    bytes32 salt = bytes32(saltNonce);
      
    assembly {
      let len := mload(clone)
      let data := add(clone, 0x20)
      result := create2(0, data, len, salt)
    }
      
    require(result != address(0), "create2 failed");
  }
}

// File: contracts/ShifterPool.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IShifterRegistry } from "./interfaces/IShifterRegistry.sol";
import { IShifter } from "./interfaces/IShifter.sol";
import { ShifterPoolLib } from "./ShifterPoolLib.sol";
import { ShifterBorrowProxyLib } from "./ShifterBorrowProxyLib.sol";
import { ShifterBorrowProxyFactoryLib } from "./ShifterBorrowProxyFactoryLib.sol";
import { ShifterBorrowProxy } from "./ShifterBorrowProxy.sol";
import { BorrowProxy } from "./BorrowProxy.sol";
import { BorrowProxyLib } from "./BorrowProxyLib.sol";
import { TokenUtils } from "./utils/TokenUtils.sol";
import { ViewExecutor } from "./utils/ViewExecutor.sol";
import { LiquidityToken } from "./LiquidityToken.sol";
import { SandboxLib } from "./utils/sandbox/SandboxLib.sol";
import { SafeViewExecutor } from "./utils/sandbox/SafeViewExecutor.sol";
import { FactoryLib } from "./FactoryLib.sol";
import { NullCloneConstructor } from "./NullCloneConstructor.sol";
import { AssetForwarderLib } from "./adapters/lib/AssetForwarderLib.sol";
import { AssetForwarder } from "./adapters/lib/AssetForwarder.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { StringLib } from "./utils/StringLib.sol";
import { ExtLib } from "./utils/ExtLib.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";

contract ShifterPool is Ownable, SafeViewExecutor, NullCloneConstructor {
  using SandboxLib for *;
  using SafeMath for *;
  using ShifterPoolLib for *;
  using TokenUtils for *;
  using StringLib for *;
  using ExtLib for *;
  using ShifterBorrowProxyLib for *;
  using ShifterBorrowProxyFactoryLib for *;
  using BorrowProxyLib for *;
  ShifterPoolLib.Isolate isolate;
  constructor() Ownable() public {
    isolate.genesis = block.number;
  }
  function setup(ShifterPoolLib.SetupParams memory params, BorrowProxyLib.ModuleDetails[] memory moduleDetails, BorrowProxyLib.Module[] memory modules, ShifterPoolLib.LiquidityTokenLaunch[] memory tokenLaunches) public onlyOwner {
    require(modules.length == moduleDetails.length, "can't zip module registations: modules.length != moduleDetails.length");
    isolate.shifterRegistry = params.shifterRegistry;
    isolate.maxLoan = params.maxLoan;
    isolate.minTimeout = params.minTimeout;
    isolate.poolFee = params.poolFee;
    isolate.daoFee = params.daoFee;
    isolate.gasEstimate = params.gasEstimate;
    isolate.maxGasPriceForRefund = params.maxGasPriceForRefund;
    for (uint256 i = 0; i < modules.length; i++) {
      BorrowProxyLib.ModuleRegistration memory registration = BorrowProxyLib.ModuleRegistration({
        module: modules[i],
        target: moduleDetails[i].target,
        sigs: moduleDetails[i].sigs,
        moduleType: moduleDetails[i].moduleType
      });
      isolate.registry.registryRegisterModule(registration);
    }
    for (uint256 i = 0; i < tokenLaunches.length; i++) {
      ShifterPoolLib.LiquidityTokenLaunch memory launch = tokenLaunches[i];
      isolate.tokenToLiquidityToken[launch.token] = launch.liqToken;
      isolate.tokenToBaseFee[launch.token] = launch.baseFee;
    }
  }
  function getGasReserved(address proxyAddress) view public returns (uint256) {
    return isolate.gasReserved[proxyAddress];
  }
  function payoutCallbackGas(address payable borrower, uint256 amountBorrower, uint256 amountOrigin) public {
    if (amountOrigin != 0) tx.origin.send(amountOrigin);
    if (amountBorrower != 0) borrower.send(amountBorrower);
  }
  function getLiquidityTokenForTokenHandler(address token) public view returns (address) {
    return isolate.tokenToLiquidityToken[token];
  }
  bytes32 constant BORROW_PROXY_IMPLEMENTATION_SALT = 0xfe1e3164ba4910db3c9afd049cd8feb4552390569c846692e6df4ac68aeaa90e;
  function deployBorrowProxyImplementation() public {
    require(isolate.borrowProxyImplementation == address(0x0), "already deployed");
    isolate.borrowProxyImplementation = isolate.makeBorrowProxy(BORROW_PROXY_IMPLEMENTATION_SALT);
    address payable borrowProxyImplementation = address(uint160(isolate.borrowProxyImplementation));
//    borrowProxyImplementation.setupBorrowProxy(address(0x1), address(0x1), false);
  }
  function computeProxyAddress(bytes32 salt) public view returns (address) {
    return isolate.borrowProxyImplementation.deriveBorrowerAddress(salt);
  }
  function deployAssetForwarderImplementation() public {
    require(isolate.assetForwarderImplementation == address(0x0), "already deployed");
    isolate.assetForwarderImplementation = AssetForwarderLib.deployAssetForwarder();
    AssetForwarder(isolate.assetForwarderImplementation).lock();
  }

  function deployAssetForwarderClone(bytes32 salt) public returns (address created) {

    created = FactoryLib.create2Clone(isolate.assetForwarderImplementation, uint256(keccak256(abi.encodePacked(AssetForwarderLib.GET_ASSET_FORWARDER_IMPLEMENTATION_SALT(), msg.sender, salt))));

  }
  function getAssetForwarderImplementationHandler() public view returns (address implementation) {
    return isolate.assetForwarderImplementation;
  }
  function deployBorrowProxyClone(bytes32 salt) internal returns (address payable created) {
    created = address(uint160(FactoryLib.create2Clone(isolate.borrowProxyImplementation, uint256(salt))));
  }
  function validateUnderMaxLoan(ShifterBorrowProxyLib.LiquidityRequestParcel memory parcel) internal view returns (bool) {
    uint8 decimals = ERC20(parcel.request.token).decimals();
    require(decimals <= 18, "the token supplied is not a shifter token -- decimals too high");
    require(parcel.request.amount / 10**(18 - uint256(decimals)) <= isolate.maxLoan, "loan exceeds maximum");
  }
  function _executeBorrow(ShifterPoolLib.BorrowState memory state, ShifterBorrowProxyLib.LiquidityRequestParcel memory liquidityRequestParcel, uint256 bond, uint256 timeoutExpiry) internal returns (address payable proxyAddress) {
    require(liquidityRequestParcel.request.forbidLoan == false, "is not a loan request, try using executeShiftSansBorrow");
    require(
      liquidityRequestParcel.gasRequested == msg.value,
      "supplied ether is not equal to gas requested"
  
    );
    require(
      liquidityRequestParcel.validateSignature(
        liquidityRequestParcel.computeLiquidityRequestHash()
      ),
      "liquidity request signature rejected"
    );
    ShifterBorrowProxyLib.LiquidityRequest memory liquidityRequest = liquidityRequestParcel.request;
    bytes32 borrowerSalt = liquidityRequest.computeBorrowerSalt();
    liquidityRequest.actions = new ShifterBorrowProxyLib.InitializationAction[](0);
    ShifterBorrowProxyLib.ProxyRecord memory proxyRecord = ShifterBorrowProxyLib.ProxyRecord({
      request: liquidityRequest,
      loan: ShifterBorrowProxyLib.LenderRecord(
        msg.sender,
        isolate.computeLoanParams(liquidityRequest.amount, bond, timeoutExpiry)
      ),
      expected: liquidityRequest.amount.computeExpectedAmount(address(isolate.getShifter(liquidityRequest.token)), liquidityRequest.token).sub(isolate.tokenToBaseFee[liquidityRequest.token])
    });
    proxyAddress = address(uint160(deployBorrowProxyClone(borrowerSalt)));
    ShifterPoolLib.mapBorrowProxy(isolate, proxyAddress, proxyRecord);
    isolate.issueLoan(liquidityRequest.token, proxyAddress, proxyRecord.computePostFee(), state.refundAmount);
    require(liquidityRequest.token.transferTokenFrom(msg.sender, address(this), bond), "bond submission failed");
  }
  function executeBorrow(ShifterBorrowProxyLib.LiquidityRequestParcel memory liquidityRequestParcel, uint256 bond, uint256 timeoutExpiry) public payable {
    ShifterPoolLib.BorrowState memory state = ShifterPoolLib.BorrowState({
      refundAmount: 0,
      gasPrice: Math.min(isolate.maxGasPriceForRefund, tx.gasprice),
      startGas: gasleft()
    });
    state.refundAmount = state.gasPrice*isolate.gasEstimate;
    require(isolate.isKeeper[msg.sender], "only can be called by keeper");
    ShifterBorrowProxyLib.InitializationAction[] memory actions = liquidityRequestParcel.request.actions;
    validateUnderMaxLoan(liquidityRequestParcel);
    address payable proxyAddress = _executeBorrow(state, liquidityRequestParcel, bond, timeoutExpiry);
    proxyAddress.setupBorrowProxy(liquidityRequestParcel.request.borrower, liquidityRequestParcel.request.token, false);
    proxyAddress.sendInitializationActions(actions);
    state.startGas = Math.min(state.refundAmount, (state.startGas - gasleft()  + 8600)*state.gasPrice); // estimate 10000 for additional gas, should be close
    tx.origin.transfer(state.startGas); // just reuse this memory loc, startGas becomes total amount refunded
    isolate.gasReserved[proxyAddress] = state.refundAmount - state.startGas;
  }
  function setKeeper(address user, bool isKeeper) public onlyOwner {
    isolate.isKeeper[user] = isKeeper;
  }
  function executeShiftSansBorrow(ShifterBorrowProxyLib.SansBorrowShiftParcel memory parcel) public payable {
    (address payable proxyAddress, ShifterBorrowProxyLib.InitializationAction[] memory actions) = _executeShiftSansBorrow(parcel);
    if (actions.length != 0) proxyAddress.sendInitializationActions(actions);
  }
  function _executeShiftSansBorrow(ShifterBorrowProxyLib.SansBorrowShiftParcel memory parcel) internal returns (address payable proxyAddress, ShifterBorrowProxyLib.InitializationAction[] memory actions) {
    ShifterBorrowProxyLib.TriggerParcel memory triggerParcel = ShifterBorrowProxyLib.TriggerParcel({
      record: ShifterBorrowProxyLib.ProxyRecord({
        expected: parcel.liquidityRequestParcel.request.amount,
        request: parcel.liquidityRequestParcel.request,
        loan: ShifterBorrowProxyLib.LenderRecord({
          keeper: msg.sender,
          params: isolate.computeLoanParams(parcel.liquidityRequestParcel.request.amount, parcel.liquidityRequestParcel.request.amount / 10, 1000)
        })
      }),
      shiftParameters: ShifterBorrowProxyLib.ShiftParameters({
        txhash: parcel.shiftParameters.txhash,
        amount: parcel.shiftParameters.amount,
        vout: parcel.shiftParameters.vout,
        pHash: parcel.shiftParameters.pHash,
        darknodeSignature: parcel.shiftParameters.darknodeSignature
      })
    });
    require(
      parcel.liquidityRequestParcel.validateSignature(
        parcel.liquidityRequestParcel.computeLiquidityRequestHash()
      ),
      "liquidity request signature rejected"
    );
    bytes32 borrowerSalt = parcel.liquidityRequestParcel.request.computeBorrowerSalt();
    proxyAddress = address(uint160(isolate.borrowProxyImplementation.deriveBorrowerAddress(borrowerSalt)));
    require(!proxyAddress.isContract(), "proxy has already been initialized");
    isolate.borrowProxyController.mapProxyRecord(proxyAddress, abi.encodePacked(uint256(1)));
    uint256 fee = triggerParcel.record.computeAdjustedKeeperFee(parcel.shiftParameters.amount);
    deployBorrowProxyClone(borrowerSalt);
    proxyAddress.setupBorrowProxy(parcel.liquidityRequestParcel.request.borrower, parcel.liquidityRequestParcel.request.token, true);
    if (parcel.liquidityRequestParcel.request.borrower != msg.sender && msg.value == parcel.liquidityRequestParcel.gasRequested) {
      parcel.liquidityRequestParcel.request.borrower.transfer(msg.value);
      ShifterPoolLib.sendMint(proxyAddress, address(isolate.getShifter(parcel.liquidityRequestParcel.request.token)), parcel, triggerParcel.computeNHash(), fee);
      require(parcel.liquidityRequestParcel.request.token.sendToken(msg.sender, fee), "keeper payout failed");
      actions = parcel.liquidityRequestParcel.request.actions;
    } else if (parcel.liquidityRequestParcel.request.borrower == msg.sender) {
      ShifterPoolLib.sendMint(proxyAddress, address(isolate.getShifter(parcel.liquidityRequestParcel.request.token)), parcel, triggerParcel.computeNHash(), 0);
      actions = parcel.actions;
    }

    else revert("incorrect gas supplied with gas requested");
  }
  function validateProxyRecordHandler(bytes memory proxyRecord) public view returns (bool) {
    return isolate.borrowProxyController.validateProxyRecord(msg.sender, proxyRecord);
  }
  function getProxyTokenHandler(address proxyAddress) public view returns (address) {
    return isolate.borrowProxyController.getProxyToken(proxyAddress);
  }
  function getProxyOwnerHandler(address user) public view returns (address) {
    return isolate.borrowProxyController.getProxyOwner(user);
  }
  function getShifterHandler(address token) public view returns (IShifter) {
    return isolate.getShifter(token);
  }
  function getLiquidityTokenHandler(address token) public view returns (LiquidityToken) {
    return LiquidityToken(isolate.getLiquidityToken(token));
  }
  function fetchModuleHandler(address to, bytes4 sig) public view returns (BorrowProxyLib.Module memory) {
    return isolate.registry.resolveModule(to, sig);
  }
  function relayResolveLoan(address token, address liquidityToken, address keeper, uint256 bond, uint256 repay, uint256 originalAmount) public returns (bool) {
    require(isolate.borrowProxyController.proxyInitializerRecord[msg.sender] != bytes32(0x0), "not a registered borrow proxy");
    if (repay < originalAmount) {
      if (repay + bond < originalAmount) {
        repay = repay + bond;
        bond = 0;
      } else {
        bond -= originalAmount - repay;
        repay = originalAmount;
      }
    }
    if (bond != 0) require(token.sendToken(keeper, bond), "failed to return bond to keeper");
    if (repay != 0) {
       (uint256 amount, uint256 daoAmount) = ShifterPoolLib.splitForDAO(repay, isolate.daoFee);
       require(token.sendToken(liquidityToken, amount), "failed to repay lost funds");
       require(token.sendToken(owner(), daoAmount), "failed to repay the governing DAO");
    }
    require(LiquidityToken(liquidityToken).resolveLoan(msg.sender), "loan resolution failure");
    return true;
  }
  receive() external payable { }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "../GSN/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(_owner == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: contracts/interfaces/IShifterRegistry.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { IShifter } from "./IShifter.sol";

interface IShifterRegistry {
  function getGatewayByToken(address _tokenAddress) external view returns (IShifter);
}

// File: contracts/ShifterPoolLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol";
import { TokenUtils } from "./utils/TokenUtils.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BorrowProxyLib } from "./BorrowProxyLib.sol";
import { IShifter } from "./interfaces/IShifter.sol";
import { IShifterRegistry } from "./interfaces/IShifterRegistry.sol";
import { LiquidityToken } from "./LiquidityToken.sol";
import { ShifterBorrowProxy } from "./ShifterBorrowProxy.sol";
import { ShifterBorrowProxyLib } from "./ShifterBorrowProxyLib.sol";
import { ShifterBorrowProxyFactoryLib } from "./ShifterBorrowProxyFactoryLib.sol";
import { FactoryLib } from "./FactoryLib.sol";
import { ShifterPool } from "./ShifterPool.sol";
import { AssetForwarderLib } from "./adapters/lib/AssetForwarderLib.sol";

library ShifterPoolLib {
  using BorrowProxyLib for *;
  using TokenUtils for *;
  using ShifterBorrowProxyLib for *;
  using ShifterBorrowProxyFactoryLib for *;
  using SafeMath for *;
  struct SetupParams {
    address shifterRegistry;
    uint256 minTimeout;
    uint256 poolFee;
    uint256 daoFee;
    uint256 maxLoan;
    uint256 gasEstimate;
    uint256 maxGasPriceForRefund;
  }
  struct BorrowState {
    uint256 startGas;
    uint256 gasPrice;
    uint256 refundAmount;
  }
  struct Isolate {
    uint256 genesis;
    address borrowProxyImplementation;
    address assetForwarderImplementation;
    address shifterRegistry;
    uint256 minTimeout;
    uint256 poolFee;
    uint256 daoFee;
    uint256 maxLoan;
    uint256 gasEstimate;
    uint256 maxGasPriceForRefund;
    mapping (address => uint256) gasReserved;
    mapping (address => bool) isKeeper;
    mapping (bytes32 => bool) provisionExecuted;
    mapping (address => address) tokenToLiquidityToken;
    mapping (address => uint256) tokenToBaseFee;
    BorrowProxyLib.ControllerIsolate borrowProxyController;
    BorrowProxyLib.ModuleRegistry registry;
  }
  function splitForDAO(uint256 actualAmount, uint256 daoFee) internal pure returns (uint256 amount, uint256 daoAmount) {
    daoAmount = actualAmount.mul(uint256(daoFee)).div(uint256(1 ether));
    if (daoAmount < actualAmount) {
      amount = actualAmount - daoAmount;
    } else daoAmount = actualAmount;
  }
  function deployAssetForwarder(BorrowProxyLib.ProxyIsolate storage isolate, bytes32 salt) internal returns (address created) {
    return ShifterPool(isolate.masterAddress).deployAssetForwarderClone(salt);
  }
  function sendMint(address proxyAddress, address shifter, ShifterBorrowProxyLib.SansBorrowShiftParcel memory parcel, bytes32 nHash, uint256 fee) internal  {
    ShifterBorrowProxy(address(uint160(proxyAddress))).relayMint(shifter, parcel.liquidityRequestParcel.request.token, parcel.shiftParameters.pHash, parcel.shiftParameters.amount, nHash, parcel.shiftParameters.darknodeSignature, fee);
  }
  function makeBorrowProxy(Isolate storage isolate, bytes32 salt) internal returns (address payable proxyAddress) {
    proxyAddress = address(uint160(isolate.deployBorrowProxy(salt)));
  }
  function issueLoan(Isolate storage isolate, address token, address payable proxyAddress, uint256 fee, uint256 getGas) internal {
    require(LiquidityToken(getLiquidityToken(isolate, token)).loan(proxyAddress, fee, getGas), "insufficient funds in liquidity pool");
  }
  function setupBorrowProxy(address payable proxyAddress, address borrower, address token, bool unbound) internal {
    require(ShifterBorrowProxy(proxyAddress).setup(borrower, token, unbound), "setup phase failure");
  }
  function sendInitializationActions(address payable proxyAddress, ShifterBorrowProxyLib.InitializationAction[] memory actions) internal {
    ShifterBorrowProxy(proxyAddress).receiveInitializationActions(actions);
  }
  function computeLoanParams(Isolate storage isolate, uint256 amount, uint256 bond, uint256 timeoutExpiry) internal view returns (ShifterBorrowProxyLib.LenderParams memory) {
    require(timeoutExpiry >= isolate.minTimeout, "timeout insufficient");
    uint256 baseKeeperFee = uint256(1 ether).div(100); // 1%
    require(bond.mul(uint256(1 ether)).div(amount) > uint256(1 ether).div(100), "bond below minimum");
    uint256 keeperFee = amount < bond ? baseKeeperFee : uint256(baseKeeperFee).mul(bond).div(amount);
    return ShifterBorrowProxyLib.LenderParams({
      keeperFee: keeperFee,
      poolFee: isolate.poolFee,
      timeoutExpiry: block.number + timeoutExpiry,
      bond: bond
    });
  }
  struct LiquidityProvisionMessage {
    uint256 amount;
    uint256 nonce;
    uint256 keeperFee;
    uint256 timeoutExpiry;
    bytes signature;
  }
  struct LiquidityTokenLaunch {
    address token;
    address liqToken;
    uint256 baseFee;
  }
  function launchLiquidityToken(Isolate storage isolate, address weth, address router, address token, string memory name, string memory symbol, uint8 decimals) internal returns (address) {
    require(isolate.tokenToLiquidityToken[token] == address(0x0), "already deployed liquidity token for target token");
    address liquidityToken = address(new LiquidityToken(weth, router, address(uint160(address(this))), token, name, symbol, decimals));
    isolate.tokenToLiquidityToken[token] = liquidityToken;
    return liquidityToken;
  }
  function getLiquidityToken(Isolate storage isolate, address token) internal view returns (address) {
    address retval = isolate.tokenToLiquidityToken[token];
    require(retval != address(0x0), "not a registered liquidity token");
    return retval;
  }
  function lendLiquidity(Isolate storage isolate, address provider, address token, address target, uint256 amount) internal returns (bool) {
    if (!isolate.isKeeper[provider]) return false;
    return token.transferTokenFrom(provider, target, amount);
  }
  function getShifter(Isolate storage isolate, address token) internal view returns (IShifter) {
    return IShifterRegistry(isolate.shifterRegistry).getGatewayByToken(token);
  }
  function provisionHashAlreadyUsed(Isolate storage isolate, bytes32 provisionHash) internal view returns (bool) {
    return isolate.provisionExecuted[provisionHash];
  }
  function preventProvisionReplay(Isolate storage isolate, bytes32 provisionHash) internal returns (bool) {
    isolate.provisionExecuted[provisionHash] = true;
    return true;
  }
  function mapBorrowProxy(Isolate storage isolate, address proxyAddress, ShifterBorrowProxyLib.ProxyRecord memory record) internal {
    bytes memory data = record.encodeProxyRecord();
    isolate.borrowProxyController.setProxyToken(proxyAddress, record.request.token);
    isolate.borrowProxyController.setProxyOwner(proxyAddress, record.request.borrower);
    record.request.borrower.transfer(msg.value);
    isolate.borrowProxyController.mapProxyRecord(proxyAddress, data);
    BorrowProxyLib.emitBorrowProxyMade(record.request.borrower, proxyAddress, data);
  }
}   

// File: contracts/LiquidityToken.sol



// SPDX-License-Identifier: MIT
pragma experimental ABIEncoderV2;
pragma solidity ^0.6.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol";
import { TokenUtils } from "./utils/TokenUtils.sol";
import { IUniswapV2Router01 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

contract LiquidityToken is ERC20, ERC20Burnable {
  using TokenUtils for *;
  address payable public pool;
  address public asset;
  address public router;
  address public weth;
  uint256 public offset;
  mapping (address => uint256) public outstandingLoans;
  constructor(address _weth, address _router, address payable shifterPool, address underlyingAsset, string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) public {
    weth = _weth;
    router = _router;
    pool = shifterPool;
    asset = underlyingAsset;
    require(weth.approveForMaxIfNeeded(router) && asset.approveForMaxIfNeeded(router), "failed to approve router for asset");
    _setupDecimals(decimals);
  }
  modifier onlyPool {
    require(msg.sender == pool, "must be called by pool manager");
    _;
  }
  function loan(address proxy, uint256 amount, uint256 getGas) public onlyPool returns (bool) {
    offset += amount;
    outstandingLoans[proxy] = amount;
    address[] memory path = new address[](2);
    path[0] = asset;
    path[1] = weth;
    uint256[] memory amounts = IUniswapV2Router01(router).swapTokensForExactETH(getGas, amount, path, pool, block.timestamp + 1);
    require(asset.sendToken(proxy, amount - amounts[0]), "loan transfer failed");
    return true;
  }
  function resolveLoan(address proxy) public onlyPool returns (bool) {
    offset -= outstandingLoans[proxy];
    outstandingLoans[proxy] = 0;
    return true;
  }
  function getReserve() internal view returns (uint256) {
    return offset + IERC20(asset).balanceOf(address(this));
  }
  function addLiquidity(uint256 value) public returns (uint256) {
    uint256 totalLiquidity = totalSupply();
    uint256 reserve = getReserve();
    uint256 totalMinted = value * (totalLiquidity == 0 ? 1 : totalLiquidity) / (reserve + 1);
    require(asset.transferTokenFrom(msg.sender, address(this), value), "transfer token failed");
    _mint(msg.sender, totalMinted);
    return totalMinted;
  }
  function removeLiquidity(uint256 value) public returns (uint256) {
    uint256 totalLiquidity = totalSupply();
    uint256 reserve = getReserve();
    uint256 totalReturned = value * (reserve + 1) / (totalLiquidity == 0 ? 1 : totalLiquidity);
    _burn(msg.sender, value);
    require(asset.sendToken(msg.sender, totalReturned), "failed to send back token");
  }
}

// File: @openzeppelin/contracts/token/ERC20/ERC20Burnable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "../../GSN/Context.sol";
import "./ERC20.sol";

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20Burnable is Context, ERC20 {
    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) public virtual {
        uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount, "ERC20: burn amount exceeds allowance");

        _approve(account, _msgSender(), decreasedAllowance);
        _burn(account, amount);
    }
}

// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol



pragma solidity >=0.6.2;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

// File: contracts/ShifterBorrowProxy.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { TokenUtils } from "./utils/TokenUtils.sol";
import { LiquidityToken } from "./LiquidityToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeViewExecutor } from "./utils/sandbox/SafeViewExecutor.sol";
import { SandboxLib } from "./utils/sandbox/SandboxLib.sol";
import { StringLib } from "./utils/StringLib.sol";
import { NullCloneConstructor } from "./NullCloneConstructor.sol";
import { BorrowProxy } from "./BorrowProxy.sol";
import { ShifterBorrowProxyLib } from "./ShifterBorrowProxyLib.sol";
import { ShifterPool } from "./ShifterPool.sol";
import { IShifter } from "./interfaces/IShifter.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";

contract ShifterBorrowProxy is BorrowProxy, SafeViewExecutor, NullCloneConstructor {
  using ShifterBorrowProxyLib for *;
  using StringLib for *;
  using SandboxLib for *;
  using TokenUtils for *;
  uint256 constant MINIMUM_GAS_CONTINUE = 5e5;
  function getShifter(address token) internal view returns (IShifter shifter) {
    shifter = ShifterPool(isolate.masterAddress).getShifterHandler(token);
  }
  function mint(ShifterBorrowProxyLib.TriggerParcel memory parcel) internal returns (uint256 amount) {
    amount = getShifter(parcel.record.request.token).mint(parcel.shiftParameters.pHash, parcel.shiftParameters.amount, parcel.computeNHash(), parcel.shiftParameters.darknodeSignature);
  }
  function _getGasReserved() internal view returns (uint256 result) {
    result = ShifterPool(isolate.masterAddress).getGasReserved(address(this));
  }
  function _payoutCallbackGas(address payable borrower, uint256 amount, uint256 originAmount) internal {
    if (amount != 0) ShifterPool(isolate.masterAddress).payoutCallbackGas(borrower, amount - originAmount, originAmount);
  }
  function repayLoan(bytes memory data) public returns (bool) {
    uint256 startGas = gasleft();
    (bool success, ShifterBorrowProxyLib.ProxyRecord memory record, address payable pool, uint256 repayAmount) = _repayLoan(data);
    if (maybeRelayResolveLoan(success, record, pool, repayAmount)) {
      ShifterBorrowProxyLib.emitShifterBorrowProxyRepaid(record.request.borrower, record);
      uint256 amount = _getGasReserved();
      uint256 originAmount = Math.min((gasleft() - startGas + 20000)*tx.gasprice, amount); // another estimate
      _payoutCallbackGas(record.request.borrower, amount, originAmount);
      return true;
    } else return false;
  }
  function _repayLoan(bytes memory data) internal returns (bool success, ShifterBorrowProxyLib.ProxyRecord memory record, address payable pool, uint256 repayAmount) {
    (ShifterBorrowProxyLib.TriggerParcel memory parcel) = data.decodeTriggerParcel();
    record = parcel.record;
    parcel.record.request.actions = new ShifterBorrowProxyLib.InitializationAction[](0);
    require(validateProxyRecord(parcel.record.encodeProxyRecord()), "proxy record invalid");
    require(!isolate.isLiquidating, "proxy is being liquidated");
    uint256 fee = parcel.record.computeAdjustedKeeperFee(parcel.record.expected);
    pool = isolate.masterAddress;
    uint256 amount;
    if (!isolate.isRepaying) {
      isolate.isRepaying = true;
      isolate.actualizedShift = amount = mint(parcel);
    } else amount = isolate.actualizedShift;
    address[] memory set = isolate.repaymentSet.set;
    for (uint256 i = isolate.repaymentIndex; i < set.length; i++) {
      if (gasleft() < MINIMUM_GAS_CONTINUE || !set[i].delegateRepay()) {
        isolate.repaymentIndex = i;
        return (false, record, pool, 0);
      }
    }
    isolate.unbound = true;
    require(parcel.record.request.token.sendToken(parcel.record.loan.keeper, fee), "keeper payout failed");
    record.request.amount = amount;
    repayAmount = amount - fee;
    success = true;
  }
  function relayMint(address shifter, address token, bytes32 pHash, uint256 amount, bytes32 nHash, bytes memory darknodeSignature, uint256 fee) public returns (bool) {
    require(isolate.masterAddress == msg.sender, "must be called by shifter pool");
    IShifter(shifter).mint(pHash, amount, nHash, darknodeSignature);
    if (fee != 0) require(token.sendToken(msg.sender, fee), "failed to send token");
    isolate.unbound = true;
  }
  function getBalanceOf(address token, address user) internal view returns (uint256) {
    return IERC20(token).balanceOf(user);
  }
  function getLiquidityToken(address payable pool, address token) internal view returns (address liqToken) {
    liqToken = address(ShifterPool(pool).getLiquidityTokenHandler(token));
  }
  function maybeRelayResolveLoan(bool success, ShifterBorrowProxyLib.ProxyRecord memory record, address payable pool, uint256 repayPool) internal returns (bool) {
    if (success) {
      address liqToken = getLiquidityToken(pool, record.request.token);
      require(record.request.token.sendToken(pool, repayPool), "failed to approve pool for token transfer");
      require(ShifterPool(pool).relayResolveLoan(record.request.token, liqToken, record.loan.keeper, record.loan.params.bond, repayPool, record.expected), "loan resolution failure");
      return true;
    }
    return false;
  }
  function defaultLoan(bytes memory data) public {
    (bool success, ShifterBorrowProxyLib.ProxyRecord memory record, address payable pool, uint256 postBalance) = _defaultLoan(data);
    maybeRelayResolveLoan(success, record, pool, postBalance);
    _payoutCallbackGas(record.request.borrower, 0, _getGasReserved());
    selfdestruct(msg.sender);
  }
  function _defaultLoan(bytes memory data) internal returns (bool success, ShifterBorrowProxyLib.ProxyRecord memory record, address payable pool, uint256 postBalance) {
    require(!isolate.isRepaying, "loan being repaid");
    require(!isolate.unbound, "loan already repaid");
    require(validateProxyRecord(data), "proxy record invalid");
    record = data.decodeProxyRecord();
    address[] memory set = isolate.liquidationSet.set;
    if (record.loan.params.timeoutExpiry >= block.number) {
      isolate.isLiquidating = true;
      for (uint256 i = isolate.liquidationIndex; i < set.length; i++) {
        if (gasleft() < MINIMUM_GAS_CONTINUE || !set[i].delegateLiquidate()) {
          isolate.liquidationIndex = i;
          return (false, record, pool, postBalance);
        }
      }
      isolate.liquidationIndex = set.length;
      pool = isolate.masterAddress;
      postBalance = getBalanceOf(record.request.token, address(this));
      success = true;
    } else {
      success = false;
    }
  }
  function receiveInitializationActions(ShifterBorrowProxyLib.InitializationAction[] memory actions) public {
    require(msg.sender == address(isolate.masterAddress), "must be called from shifter pool");
    actions.processActions();
  }
  fallback() external payable override {}
  receive() external payable override {}
}

// File: contracts/utils/sandbox/SafeViewExecutor.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SafeViewLib } from "./SafeViewLib.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";

contract SafeViewExecutor {
  using SafeViewLib for *;
  bytes32 constant STEALTH_VIEW_DEPLOY_SALT = 0xad53495153c7c363e98a26920ec679e0e687636458f6908c91cf6deadb190801;
  function _executeSafeView(bytes memory creationCode, bytes memory context) public {
    address viewLayer = Create2.deploy(0, SafeViewLib.GET_STEALTH_VIEW_DEPLOY_SALT(), creationCode);
    bytes memory result = viewLayer.executeLogic(context).encodeResult();
    result.revertWithData();
  }
  function query(bytes memory creationCode, bytes memory context) public returns (SafeViewLib.SafeViewResult memory) {
    return creationCode.safeView(context);
  }
}

// File: contracts/NullCloneConstructor.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract NullCloneConstructor {
  function cloneConstructor(bytes memory consData) public {
    // do nothing
  }
}

// File: @openzeppelin/contracts/math/Math.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }
}

// File: contracts/ShifterBorrowProxyFactoryLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { ShifterBorrowProxy } from "./ShifterBorrowProxy.sol";
import { ShifterPoolLib } from "./ShifterPoolLib.sol";
import { FactoryLib } from "./FactoryLib.sol";

library ShifterBorrowProxyFactoryLib {
  function deployBorrowProxy(ShifterPoolLib.Isolate storage /* isolate */, bytes32 salt) external returns (address output) {
    output = Create2.deploy(0, salt, type(ShifterBorrowProxy).creationCode);
  }
  function deriveBorrowerAddress(address target, bytes32 salt) internal view returns (address) {
    return FactoryLib.deriveInstanceAddress(target, salt);
  }
}

// File: contracts/utils/ViewExecutor.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract ViewExecutor {
  function encodeExecuteQuery(address viewLogic, bytes memory payload) internal pure returns (bytes memory retval) {
    retval = abi.encodeWithSignature("_executeQuery(address,bytes)", viewLogic, payload);
  }
  function query(address viewLogic, bytes memory payload) public view returns (bytes memory) {
    (bool success, bytes memory response) = viewLogic.staticcall(encodeExecuteQuery(viewLogic, payload));
    require(success, string(response));
    return response;
  }
  function _executeQuery(address delegateTo, bytes memory callData) public returns (bytes memory) {
    require(msg.sender == address(this), "unauthorized view layer delegation");
    (bool success, bytes memory retval) = delegateTo.delegatecall(callData);
    require(success);
    return retval;
  }
}

// File: contracts/adapters/assets/uniswap-v2/UniswapV2Adapter.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { IUniswapV2Router01 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenUtils } from "../../../utils/TokenUtils.sol";
import { ModuleLib } from "../../lib/ModuleLib.sol";
import { UniswapV2AdapterLib } from "./UniswapV2AdapterLib.sol";
import { BorrowProxyLib } from "../../../BorrowProxyLib.sol";
import { ERC20AdapterLib } from "../erc20/ERC20AdapterLib.sol";
import { ShifterPool } from "../../../ShifterPool.sol";

contract UniswapV2Adapter {
  using TokenUtils for *;
  using ModuleLib for *;
  using BorrowProxyLib for *;
  using UniswapV2AdapterLib for *;
  using ERC20AdapterLib for *;
  BorrowProxyLib.ProxyIsolate proxyIsolate;
  constructor(address erc20Module) public {
    UniswapV2AdapterLib.Isolate storage isolate = UniswapV2AdapterLib.getIsolatePointer(address(this));
    isolate.erc20Module = erc20Module;
  }
  function validateIsERC20Module(ModuleLib.AssetSubmodulePayload memory payload, address token) internal view returns (bool) {
    UniswapV2AdapterLib.Isolate memory externalIsolate = UniswapV2AdapterLib.getExternalIsolate(payload.moduleAddress);
    require(ShifterPool(proxyIsolate.masterAddress).fetchModuleHandler(token, IERC20.transfer.selector).assetSubmodule == externalIsolate.erc20Module, "asset is not associated with the ERC20 module");
  }
  function getExternalIsolateHandler() external view returns (UniswapV2AdapterLib.Isolate memory) {
    return UniswapV2AdapterLib.getIsolatePointer(address(this));
  }
  function getWETH(ModuleLib.AssetSubmodulePayload memory payload) internal pure returns (address) {
    return IUniswapV2Router01(payload.to).WETH();
  }
  function handle(ModuleLib.AssetSubmodulePayload memory payload) public payable {
    (bytes4 sig, bytes memory args) = payload.callData.splitPayload();
    bool shouldTriggerERC20Handlers = false;
    address escrowWallet = proxyIsolate.deriveNextForwarderAddress();
    address newToken = address(0x0);
    address startToken = address(0x0);
    if (sig == IUniswapV2Router01.swapExactTokensForTokens.selector) {
      UniswapV2AdapterLib.SwapExactTokensForTokensInputs memory inputs = args.decodeSwapExactTokensForTokensInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else if (sig == IUniswapV2Router01.swapTokensForExactTokens.selector) {
      UniswapV2AdapterLib.SwapTokensForExactTokensInputs memory inputs = args.decodeSwapTokensForExactTokensInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else if (sig == IUniswapV2Router01.swapExactETHForTokens.selector) {
      UniswapV2AdapterLib.SwapExactETHForTokensInputs memory inputs = args.decodeSwapExactETHForTokensInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else if (sig == IUniswapV2Router01.swapTokensForExactETH.selector) {
      UniswapV2AdapterLib.SwapExactETHForTokensInputs memory inputs = args.decodeSwapExactETHForTokensInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else if (sig == IUniswapV2Router01.swapExactTokensForETH.selector) {
      UniswapV2AdapterLib.SwapExactTokensForETHInputs memory inputs = args.decodeSwapExactTokensForETHInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else if (sig == IUniswapV2Router01.swapETHForExactTokens.selector) {
      UniswapV2AdapterLib.SwapETHForExactTokensInputs memory inputs = args.decodeSwapETHForExactTokensInputs();
      address WETH = getWETH(payload);
      (startToken, newToken) = inputs.path.validatePath(WETH);
      validateIsERC20Module(payload, newToken);
      if (inputs.to != address(this)) {
        payload.callData = inputs.changeRecipient(escrowWallet).encodeWithSelector();
        shouldTriggerERC20Handlers = true;
        ERC20AdapterLib.installEscrowRecord(inputs.to, newToken);
      }
    } else revert("unsupported contract call");
    if (newToken != address(0x0)) {
      require(payload.liquidationSubmodule.delegateNotify(newToken.encodeLiquidationNotify()), "liquidation module notification failure");
    }
    require(startToken.approveForMaxIfNeeded(payload.to), "failed to approve start token");
    if (shouldTriggerERC20Handlers) proxyIsolate.triggerERC20Handlers(payload.moduleAddress);
    (bool success, bytes memory retval) = payload.to.call{ gas: gasleft(), value: payload.value }(payload.callData);
    ModuleLib.bubbleResult(success, retval);
  }
}

// File: contracts/adapters/assets/uniswap-v2/UniswapV2AdapterLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { UniswapV2Adapter } from "./UniswapV2Adapter.sol";
import { IUniswapV2Router01 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import { BorrowProxyLib } from "../../../BorrowProxyLib.sol";
import { ShifterBorrowProxyLib } from "../../../ShifterBorrowProxyLib.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { ModuleLib } from "../../lib/ModuleLib.sol";
import { AddressSetLib } from "../../../utils/AddressSetLib.sol";

library UniswapV2AdapterLib {
  using BorrowProxyLib for *;
  using AddressSetLib for *;
  using ShifterBorrowProxyLib for *;
  bytes32 constant ETHER_FORWARDER_SALT = 0x3e8d8e49b9a35f50b96f6ba4b93e0fc6c1d66a2e1c04975ef848d7031c8158a4; // keccak("uniswap-adapter.ether-forwarder")
  struct Isolate {
    uint256 liquidityMinimum;
    address erc20Module;
  }
  struct SwapExactTokensForTokensInputs {
    uint256 amountIn;
    uint256 amountOutMin;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapExactTokensForTokensInputs(bytes memory args) internal pure returns (SwapExactTokensForTokensInputs memory) {
    (uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, uint256, address[], address, uint256));
    return SwapExactTokensForTokensInputs({
      amountIn: amountIn,
      amountOutMin: amountOutMin,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapExactTokensForTokensInputs memory inputs, address newRecipient) internal pure returns (SwapExactTokensForTokensInputs memory) {
    return SwapExactTokensForTokensInputs({
      amountIn: inputs.amountIn,
      amountOutMin: inputs.amountOutMin,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapExactTokensForTokensInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapExactTokensForTokens.selector, inputs.amountIn, inputs.amountOutMin, inputs.path, inputs.to, inputs.deadline);
  }
  struct SwapTokensForExactTokensInputs {
    uint256 amountOut;
    uint256 amountInMax;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapTokensForExactTokensInputs(bytes memory args) internal pure returns (SwapTokensForExactTokensInputs memory) {
    (uint256 amountOut, uint256 amountInMax, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, uint256, address[], address, uint256));
    return SwapTokensForExactTokensInputs({
      amountOut: amountOut,
      amountInMax: amountInMax,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapTokensForExactTokensInputs memory inputs, address newRecipient) internal pure returns (SwapTokensForExactTokensInputs memory) {
    return SwapTokensForExactTokensInputs({
      amountOut: inputs.amountOut,
      amountInMax: inputs.amountInMax,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapTokensForExactTokensInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapTokensForExactTokens.selector, inputs.amountOut, inputs.amountInMax, inputs.path, inputs.to, inputs.deadline);
  }
  struct SwapExactETHForTokensInputs {
    uint256 amountOutMin;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapExactETHForTokensInputs(bytes memory args) internal pure returns (SwapExactETHForTokensInputs memory) {
    (uint256 amountOutMin, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, address[], address, uint256));
    return SwapExactETHForTokensInputs({
      amountOutMin: amountOutMin,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapExactETHForTokensInputs memory inputs, address newRecipient) internal pure returns (SwapExactETHForTokensInputs memory) {
    return SwapExactETHForTokensInputs({
      amountOutMin: inputs.amountOutMin,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapExactETHForTokensInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapExactETHForTokens.selector, inputs.amountOutMin, inputs.path, inputs.to, inputs.deadline);
  }
  struct SwapTokensForExactETHInputs {
    uint256 amountOut;
    uint256 amountInMax;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapTokensForExactETHInputs(bytes memory args) internal pure returns (SwapTokensForExactETHInputs memory) {
    (uint256 amountOut, uint256 amountInMax, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, uint256, address[], address, uint256));
    return SwapTokensForExactETHInputs({
      amountOut: amountOut,
      amountInMax: amountInMax,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapTokensForExactETHInputs memory inputs, address newRecipient) internal pure returns (SwapTokensForExactETHInputs memory) {
    return SwapTokensForExactETHInputs({
      amountOut: inputs.amountOut,
      amountInMax: inputs.amountInMax,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapTokensForExactETHInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapTokensForExactETH.selector, inputs.amountOut, inputs.amountInMax, inputs.path, inputs.to, inputs.deadline);
  }
  struct SwapExactTokensForETHInputs {
    uint256 amountIn;
    uint256 amountOutMin;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapExactTokensForETHInputs(bytes memory args) internal pure returns (SwapExactTokensForETHInputs memory) {
    (uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, uint256, address[], address, uint256));
    return SwapExactTokensForETHInputs({
      amountIn: amountIn,
      amountOutMin: amountOutMin,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapExactTokensForETHInputs memory inputs, address newRecipient) internal pure returns (SwapExactTokensForETHInputs memory) {
    return SwapExactTokensForETHInputs({
      amountIn: inputs.amountIn,
      amountOutMin: inputs.amountOutMin,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapExactTokensForETHInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapExactTokensForETH.selector, inputs.amountIn, inputs.amountOutMin, inputs.path, inputs.to, inputs.deadline);
  }
  struct SwapETHForExactTokensInputs {
    uint256 amountOut;
    address[] path;
    address to;
    uint256 deadline;
  }
  function decodeSwapETHForExactTokensInputs(bytes memory args) internal pure returns (SwapETHForExactTokensInputs memory) {
    (uint256 amountOut, address[] memory path, address to, uint256 deadline) = abi.decode(args, (uint256, address[], address, uint256));
    return SwapETHForExactTokensInputs({
      amountOut: amountOut,
      path: path,
      to: to,
      deadline: deadline
    });
  }
  function changeRecipient(SwapETHForExactTokensInputs memory inputs, address newRecipient) internal pure returns (SwapETHForExactTokensInputs memory) {
    return SwapETHForExactTokensInputs({
      amountOut: inputs.amountOut,
      path: inputs.path,
      to: newRecipient,
      deadline: inputs.deadline
    });
  }
  function encodeWithSelector(SwapETHForExactTokensInputs memory inputs) internal pure returns (bytes memory callData) {
    callData = abi.encodeWithSelector(IUniswapV2Router01.swapETHForExactTokens.selector, inputs.amountOut, inputs.path, inputs.to, inputs.deadline);
  }
  function computeIsolatePointer(address instance) public pure returns (uint256) {
    return uint256(keccak256(abi.encodePacked("isolate.uniswap-v2-adapter", instance)));
  }
  function validatePath(address[] memory path, address WETH) internal pure returns (address begin, address destination) {
    if (path.length < 2) revert("path too short, must be 3 elements");
    if (path.length > 3 || !(path[0] == WETH || path[1] == WETH)) revert("path must be 2 or 3 items and include WETH");
    return (path[0], path[path.length - 1]);
  }
  function generatePathForToken(address startToken, address WETH, address token) internal pure returns (address[] memory path) {
    path = new address[](3);
    path[0] = startToken;
    path[1] = WETH;
    path[2] = token;
  }
  function getCastStorageType() internal pure returns (function (uint256) internal pure returns (Isolate storage) swap) {
    function (uint256) internal returns (uint256) cast = ModuleLib.cast;
    assembly {
      swap := cast
    }
  }
  function toIsolatePointer(uint256 key) internal pure returns (Isolate storage) {
    return getCastStorageType()(key);
  }
  function getIsolatePointer(address instance) internal pure returns (Isolate storage) {
    return toIsolatePointer(computeIsolatePointer(instance));
  }
  function getExternalIsolate(address payable moduleAddress) internal view returns (Isolate memory) {
    return UniswapV2Adapter(moduleAddress).getExternalIsolateHandler();
  }
  function encodeLiquidationNotify(address newToken) internal pure returns (bytes memory result) {
    result = abi.encode(newToken);
  }
  function triggerERC20Handlers(BorrowProxyLib.ProxyIsolate storage proxyIsolate, address moduleAddress) internal {
    Isolate memory isolate = UniswapV2Adapter(moduleAddress).getExternalIsolateHandler();
    proxyIsolate.repaymentSet.insert(isolate.erc20Module);
  }
}

// File: contracts/adapters/liquidity/SimpleBurnLiquidationModule.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { AddressSetLib } from "../../utils/AddressSetLib.sol";
import { BorrowProxyLib } from "../../BorrowProxyLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenUtils } from "../../utils/TokenUtils.sol";
import { SimpleBurnLiquidationModuleLib } from "./SimpleBurnLiquidationModuleLib.sol";
import { ERC20AdapterLib } from "../assets/erc20/ERC20AdapterLib.sol";
import { UniswapV2AdapterLib } from "../assets/uniswap-v2/UniswapV2AdapterLib.sol";
import { IUniswapV2Router01 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import { ShifterPool } from "../../ShifterPool.sol";
import { StringLib } from "../../utils/StringLib.sol";

contract SimpleBurnLiquidationModule {
  using StringLib for *;
  using AddressSetLib for *;
  using TokenUtils for *;
  using UniswapV2AdapterLib for *;
  BorrowProxyLib.ProxyIsolate proxyIsolate;
  constructor(address routerAddress, address erc20Module) public {
    SimpleBurnLiquidationModuleLib.Isolate storage isolate = SimpleBurnLiquidationModuleLib.getIsolatePointer();
    isolate.routerAddress = routerAddress;
    isolate.erc20Module = erc20Module;
  }
  function notify(address /* moduleAddress */, bytes memory payload) public returns (bool) {
    (address token) = abi.decode(payload, (address));
    SimpleBurnLiquidationModuleLib.Isolate storage isolate = SimpleBurnLiquidationModuleLib.getIsolatePointer();
    isolate.toLiquidate.insert(token);
    return true;
  }
  function fetchExternals(address moduleAddress) internal view returns (address routerAddress, address erc20Module) {
    (routerAddress, erc20Module) = SimpleBurnLiquidationModule(moduleAddress).getExternalIsolateHandler();
  }
  function fetchLiquidityToken(address liquidateTo) internal view returns (address) {
    return ShifterPool(proxyIsolate.masterAddress).getLiquidityTokenForTokenHandler(liquidateTo);
  }
  function getWETH(IUniswapV2Router01 router) internal pure returns (address) {
    return router.WETH();
  }
  function liquidate(address moduleAddress) public returns (bool) {
    if (!ERC20AdapterLib.liquidate(proxyIsolate)) return false;
    SimpleBurnLiquidationModuleLib.Isolate storage isolate = SimpleBurnLiquidationModuleLib.getIsolatePointer();
    address liquidateTo = address(uint160(proxyIsolate.token));
    (address routerAddress, /* address erc20Module */) = fetchExternals(moduleAddress);
    IUniswapV2Router01 router = IUniswapV2Router01(routerAddress);
    address WETH = getWETH(router);
    uint256 i;
    for (i = isolate.liquidated; i < isolate.toLiquidate.set.length; i++) {
      address tokenAddress = isolate.toLiquidate.set[i];
      if (liquidateTo == tokenAddress) continue;
      if (gasleft() < 3e5) {
        isolate.liquidated = i;
        return false;
      }
      uint256 tokenBalance = IERC20(tokenAddress).balanceOf(address(this));
      address[] memory path = UniswapV2AdapterLib.generatePathForToken(tokenAddress, WETH, liquidateTo);
      tokenAddress.approveForMaxIfNeeded(address(router));
      router.swapExactTokensForTokens(tokenBalance, 1, path, address(this), block.timestamp + 1);
    }
    isolate.liquidated = i;
    return true;
  }
  function getExternalIsolateHandler() external view returns (address, address) {
    SimpleBurnLiquidationModuleLib.Isolate storage isolate = SimpleBurnLiquidationModuleLib.getIsolatePointer();
    return (isolate.routerAddress, isolate.erc20Module);
  }
}

// File: contracts/adapters/liquidity/SimpleBurnLiquidationModuleLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { AddressSetLib } from "../../utils/AddressSetLib.sol";
import { BorrowProxyLib } from "../../BorrowProxyLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenUtils } from "../../utils/TokenUtils.sol";
import { ModuleLib } from "../lib/ModuleLib.sol";
import { SimpleBurnLiquidationModule } from "./SimpleBurnLiquidationModule.sol";

library SimpleBurnLiquidationModuleLib {
  struct Isolate {
    address routerAddress;
    address erc20Module;
    uint256 liquidated;
    AddressSetLib.AddressSet toLiquidate;
  }
  struct ExternalIsolate {
    address routerAddress;
    address erc20Module;
  }
  function computeIsolatePointer() public pure returns (uint256) {
    return uint256(keccak256(abi.encodePacked("isolate.simple-burn")));
  }
  function getCastStorageType() internal pure returns (function (uint256) internal pure returns (Isolate storage) swap) {
    function (uint256) internal returns (uint256) cast = ModuleLib.cast;
    assembly {
      swap := cast
    }
  }
  function toIsolatePointer(uint256 key) internal pure returns (Isolate storage) {
    return getCastStorageType()(key);
  }
  function getIsolatePointer() internal pure returns (Isolate storage) {
    return toIsolatePointer(computeIsolatePointer());
  }
}

// File: contracts/Exports.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ShifterBorrowProxyLib } from "./ShifterBorrowProxyLib.sol";

contract Exports {
  event InitializationActionsExport(ShifterBorrowProxyLib.InitializationAction[] actions);
  event ProxyRecordExport(ShifterBorrowProxyLib.ProxyRecord record);
  event TriggerParcelExport(ShifterBorrowProxyLib.TriggerParcel record);
}

// File: contracts/interfaces/ILiquidationModule.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface ILiquidationModule {
  function notify(address moduleAddress, bytes calldata payload) external returns (bool);
  function liquidate(address moduleAddress) external returns (bool);
}

// File: contracts/interfaces/IUniswapExchange.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IUniswapExchange {
  event TokenPurchase(address indexed buyer, uint256 indexed eth_sold, uint256 indexed tokens_bought);
  event EthPurchase(address indexed buyer, uint256 indexed tokens_sold, uint256 indexed eth_bought);
  event AddLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount);
  event RemoveLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount);

   /**
   * @notice Convert ETH to Tokens.
   * @dev User specifies exact input (msg.value).
   * @dev User cannot specify minimum output or deadline.
   */
  receive() payable external;
  

 /**
   * @dev Pricing function for converting between ETH && Tokens.
   * @param input_amount Amount of ETH or Tokens being sold.
   * @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
   * @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
   * @return Amount of ETH or Tokens bought.
   */
  function getInputPrice(uint256 input_amount, uint256 input_reserve, uint256 output_reserve) external view returns (uint256);

 /**
   * @dev Pricing function for converting between ETH && Tokens.
   * @param output_amount Amount of ETH or Tokens being bought.
   * @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
   * @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
   * @return Amount of ETH or Tokens sold.
   */
  function getOutputPrice(uint256 output_amount, uint256 input_reserve, uint256 output_reserve) external view returns (uint256);


  /** 
   * @notice Convert ETH to Tokens.
   * @dev User specifies exact input (msg.value) && minimum output.
   * @param min_tokens Minimum Tokens bought.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return Amount of Tokens bought.
   */ 
  function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256);

  /** 
   * @notice Convert ETH to Tokens && transfers Tokens to recipient.
   * @dev User specifies exact input (msg.value) && minimum output
   * @param min_tokens Minimum Tokens bought.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output Tokens.
   * @return  Amount of Tokens bought.
   */
  function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns(uint256);


  /** 
   * @notice Convert ETH to Tokens.
   * @dev User specifies maximum input (msg.value) && exact output.
   * @param tokens_bought Amount of tokens bought.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return Amount of ETH sold.
   */
  function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns(uint256);
  /** 
   * @notice Convert ETH to Tokens && transfers Tokens to recipient.
   * @dev User specifies maximum input (msg.value) && exact output.
   * @param tokens_bought Amount of tokens bought.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output Tokens.
   * @return Amount of ETH sold.
   */
  function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256);

  /** 
   * @notice Convert Tokens to ETH.
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_eth Minimum ETH purchased.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return Amount of ETH bought.
   */
  function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256);

  /** 
   * @notice Convert Tokens to ETH && transfers ETH to recipient.
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_eth Minimum ETH purchased.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @return  Amount of ETH bought.
   */
  function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient) external returns (uint256);

  /** 
   * @notice Convert Tokens to ETH.
   * @dev User specifies maximum input && exact output.
   * @param eth_bought Amount of ETH purchased.
   * @param max_tokens Maximum Tokens sold.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return Amount of Tokens sold.
   */
  function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256);

  /**
   * @notice Convert Tokens to ETH && transfers ETH to recipient.
   * @dev User specifies maximum input && exact output.
   * @param eth_bought Amount of ETH purchased.
   * @param max_tokens Maximum Tokens sold.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @return Amount of Tokens sold.
   */
  function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (token_addr).
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_tokens_bought Minimum Tokens (token_addr) purchased.
   * @param min_eth_bought Minimum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param token_addr The address of the token being purchased.
   * @return Amount of Tokens (token_addr) bought.
   */
  function tokenToTokenSwapInput(
    uint256 tokens_sold, 
    uint256 min_tokens_bought, 
    uint256 min_eth_bought, 
    uint256 deadline, 
    address token_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (token_addr) && transfers
   *         Tokens (token_addr) to recipient.
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_tokens_bought Minimum Tokens (token_addr) purchased.
   * @param min_eth_bought Minimum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @param token_addr The address of the token being purchased.
   * @return Amount of Tokens (token_addr) bought.
   */
  function tokenToTokenTransferInput(
    uint256 tokens_sold, 
    uint256 min_tokens_bought, 
    uint256 min_eth_bought, 
    uint256 deadline, 
    address recipient, 
    address token_addr) 
    external returns (uint256);


  /**
   * @notice Convert Tokens (token) to Tokens (token_addr).
   * @dev User specifies maximum input && exact output.
   * @param tokens_bought Amount of Tokens (token_addr) bought.
   * @param max_tokens_sold Maximum Tokens (token) sold.
   * @param max_eth_sold Maximum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param token_addr The address of the token being purchased.
   * @return Amount of Tokens (token) sold.
   */
  function tokenToTokenSwapOutput(
    uint256 tokens_bought, 
    uint256 max_tokens_sold, 
    uint256 max_eth_sold, 
    uint256 deadline, 
    address token_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (token_addr) && transfers
   *         Tokens (token_addr) to recipient.
   * @dev User specifies maximum input && exact output.
   * @param tokens_bought Amount of Tokens (token_addr) bought.
   * @param max_tokens_sold Maximum Tokens (token) sold.
   * @param max_eth_sold Maximum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @param token_addr The address of the token being purchased.
   * @return Amount of Tokens (token) sold.
   */
  function tokenToTokenTransferOutput(
    uint256 tokens_bought, 
    uint256 max_tokens_sold, 
    uint256 max_eth_sold, 
    uint256 deadline, 
    address recipient, 
    address token_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (exchange_addr.token).
   * @dev Allows trades through contracts that were not deployed from the same factory.
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_tokens_bought Minimum Tokens (token_addr) purchased.
   * @param min_eth_bought Minimum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param exchange_addr The address of the exchange for the token being purchased.
   * @return Amount of Tokens (exchange_addr.token) bought.
   */
  function tokenToExchangeSwapInput(
    uint256 tokens_sold, 
    uint256 min_tokens_bought, 
    uint256 min_eth_bought, 
    uint256 deadline, 
    address exchange_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (exchange_addr.token) && transfers
   *         Tokens (exchange_addr.token) to recipient.
   * @dev Allows trades through contracts that were not deployed from the same factory.
   * @dev User specifies exact input && minimum output.
   * @param tokens_sold Amount of Tokens sold.
   * @param min_tokens_bought Minimum Tokens (token_addr) purchased.
   * @param min_eth_bought Minimum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @param exchange_addr The address of the exchange for the token being purchased.
   * @return Amount of Tokens (exchange_addr.token) bought.
   */
  function tokenToExchangeTransferInput(
    uint256 tokens_sold, 
    uint256 min_tokens_bought, 
    uint256 min_eth_bought, 
    uint256 deadline, 
    address recipient, 
    address exchange_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (exchange_addr.token).
   * @dev Allows trades through contracts that were not deployed from the same factory.
   * @dev User specifies maximum input && exact output.
   * @param tokens_bought Amount of Tokens (token_addr) bought.
   * @param max_tokens_sold Maximum Tokens (token) sold.
   * @param max_eth_sold Maximum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param exchange_addr The address of the exchange for the token being purchased.
   * @return Amount of Tokens (token) sold.
   */
  function tokenToExchangeSwapOutput(
    uint256 tokens_bought, 
    uint256 max_tokens_sold, 
    uint256 max_eth_sold, 
    uint256 deadline, 
    address exchange_addr) 
    external returns (uint256);

  /**
   * @notice Convert Tokens (token) to Tokens (exchange_addr.token) && transfers
   *         Tokens (exchange_addr.token) to recipient.
   * @dev Allows trades through contracts that were not deployed from the same factory.
   * @dev User specifies maximum input && exact output.
   * @param tokens_bought Amount of Tokens (token_addr) bought.
   * @param max_tokens_sold Maximum Tokens (token) sold.
   * @param max_eth_sold Maximum ETH purchased as intermediary.
   * @param deadline Time after which this transaction can no longer be executed.
   * @param recipient The address that receives output ETH.
   * @param exchange_addr The address of the exchange for the token being purchased.
   * @return Amount of Tokens (token) sold.
   */
  function tokenToExchangeTransferOutput(
    uint256 tokens_bought, 
    uint256 max_tokens_sold, 
    uint256 max_eth_sold, 
    uint256 deadline, 
    address recipient, 
    address exchange_addr) 
    external returns (uint256);


  /***********************************|
  |         Getter Functions          |
  |__________________________________*/

  /**
   * @notice external price function for ETH to Token trades with an exact input.
   * @param eth_sold Amount of ETH sold.
   * @return Amount of Tokens that can be bought with input ETH.
   */
  function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256);

  /**
   * @notice external price function for ETH to Token trades with an exact output.
   * @param tokens_bought Amount of Tokens bought.
   * @return Amount of ETH needed to buy output Tokens.
   */
  function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256);

  /**
   * @notice external price function for Token to ETH trades with an exact input.
   * @param tokens_sold Amount of Tokens sold.
   * @return Amount of ETH that can be bought with input Tokens.
   */
  function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256);

  /**
   * @notice external price function for Token to ETH trades with an exact output.
   * @param eth_bought Amount of output ETH.
   * @return Amount of Tokens needed to buy output ETH.
   */
  function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256);

  /** 
   * @return Address of Token that is sold on this exchange.
   */
  function tokenAddress() external view returns (address);

  /**
   * @return Address of factory that created this exchange.
   */
  function factoryAddress() external view returns (address);


  /***********************************|
  |        Liquidity Functions        |
  |__________________________________*/

  /** 
   * @notice Deposit ETH && Tokens (token) at current ratio to mint UNI tokens.
   * @dev min_liquidity does nothing when total UNI supply is 0.
   * @param min_liquidity Minimum number of UNI sender will mint if total UNI supply is greater than 0.
   * @param max_tokens Maximum number of tokens deposited. Deposits max amount if total UNI supply is 0.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return The amount of UNI minted.
   */
  function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256);

  /**
   * @dev Burn UNI tokens to withdraw ETH && Tokens at current ratio.
   * @param amount Amount of UNI burned.
   * @param min_eth Minimum ETH withdrawn.
   * @param min_tokens Minimum Tokens withdrawn.
   * @param deadline Time after which this transaction can no longer be executed.
   * @return The amount of ETH && Tokens withdrawn.
   */
  function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256);
}

// File: contracts/interfaces/IUniswapFactory.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IUniswapFactory {
  event NewExchange(address indexed token, address indexed exchange);

  function initializeFactory(address template) external;
  function createExchange(address token) external returns (address payable);
  function getExchange(address token) external view returns (address payable);
  function getToken(address token) external view returns (address);
  function getTokenWihId(uint256 token_id) external view returns (address);
}

// File: contracts/mock/DAI.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { TestToken } from "./TestToken.sol";

contract DAI is TestToken {
  constructor() TestToken("DAI", "DAI", 18) public {}
}

// File: contracts/mock/TestToken.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestToken is ERC20, ERC20Burnable {
  constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) public {
    _setupDecimals(decimals);
  }
  function mint(address user, uint256 amount) public {
    _mint(user, amount);
  }
}

// File: contracts/mock/MockWETH.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { TestToken } from "./TestToken.sol";

contract MockWETH is TestToken {
  constructor() TestToken("WETH", "WETH", 18) public {}
  function deposit() public {}
  function withdraw(uint256 amount) public {
    _burn(msg.sender, amount);
    msg.sender.transfer(amount);
  }
}

// File: contracts/mock/ShifterERC20Mock.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { TestToken } from "./TestToken.sol";

contract ShifterERC20Mock is TestToken {
  constructor() TestToken("zBTC", "zBTC", 8) public {}
  function fromUnderlying(uint256 amount) public pure returns (uint256) {
    return amount;
  }
}

// File: contracts/mock/ShifterMock.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ShifterERC20Mock } from "./ShifterERC20Mock.sol";

contract ShifterMock {
  address public token;
  uint256 constant BIPS_DENOMINATOR = 10000;
  uint256 constant FEE = 5000;
  constructor() public {
    token = address(new ShifterERC20Mock());
  }
  function mint(bytes32 /* pHash */, uint256 amount , bytes32 /* nHash */, bytes memory /* darknode signature */) public returns (uint256) {
    ShifterERC20Mock(token).mint(msg.sender, amount / 2);
    return amount *FEE / BIPS_DENOMINATOR;
  }
  function mintFee() public pure returns (uint256) {
    return FEE;
  }
}
    

// File: contracts/mock/ShifterRegistryMock.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ShifterMock } from "./ShifterMock.sol";

contract ShifterRegistryMock {
  mapping (address => address) public getGatewayByToken;
  address public token;
  address public shifter;
  constructor() public {
    shifter = address(new ShifterMock());
    token = ShifterMock(shifter).token();
    getGatewayByToken[token] = shifter;
  }
}

// File: contracts/mock/WBTC.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { TestToken } from "./TestToken.sol";

contract WBTC is TestToken {
  constructor() TestToken("WBTC", "WBTC", 8) public {}
}

// File: contracts/preprocessors/lib/Preprocessor.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { PreprocessorLib } from "./PreprocessorLib.sol";

contract Preprocessor {
  using PreprocessorLib for *;
  function destroy() external {
    selfdestruct(msg.sender);
  }
}

// File: contracts/preprocessors/SwapEntireLoan.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { PreprocessorLib } from "./lib/PreprocessorLib.sol";
import { IUniswapExchange } from "../interfaces/IUniswapExchange.sol";
import { IUniswapFactory } from "../interfaces/IUniswapFactory.sol";
import { ShifterBorrowProxyLib } from "../ShifterBorrowProxyLib.sol";
import { SandboxLib } from "../utils/sandbox/SandboxLib.sol";
import { BorrowProxyLib } from "../BorrowProxyLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { StringLib } from "../utils/StringLib.sol";

contract SwapEntireLoan {
  using PreprocessorLib for *;
  using StringLib for *;
  BorrowProxyLib.ProxyIsolate isolate;
  address public factory;
  address public target;
  function setup(bytes memory consData) public {
    (factory, target) = abi.decode(consData, (address, address));
  }
  function execute(bytes memory data) view public returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    SandboxLib.ExecutionContext memory context = data.toContext();
    result = IUniswapFactory(SwapEntireLoan(context.preprocessorAddress).factory())
      .getExchange(isolate.token)
      .sendTransaction(abi.encodeWithSelector(
        IUniswapExchange.tokenToTokenSwapInput.selector,
        IERC20(isolate.token).balanceOf(address(this)),
        1,
        1,
        block.timestamp + 1,
        SwapEntireLoan(context.preprocessorAddress).target()));
  }
}

// File: contracts/preprocessors/TransferAll.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { PreprocessorLib } from "./lib/PreprocessorLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ShifterBorrowProxyLib } from "../ShifterBorrowProxyLib.sol";
import { SandboxLib } from "../utils/sandbox/SandboxLib.sol";
import { BorrowProxyLib } from "../BorrowProxyLib.sol";

contract TransferAll {
  using PreprocessorLib for *;
  BorrowProxyLib.ProxyIsolate isolate;
  address public target;
  function setup(bytes memory consData) public {
    (target) = abi.decode(consData, (address));
  }
  function execute(bytes memory data) view public returns (ShifterBorrowProxyLib.InitializationAction[] memory) {
    SandboxLib.ExecutionContext memory context = data.toContext();
    address token = isolate.token;
    return isolate.token.sendTransaction(abi.encodeWithSelector(IERC20.transfer.selector, TransferAll(context.preprocessorAddress).target(), IERC20(token).balanceOf(address(this))));
  }
}

// File: contracts/preprocessors/V2SwapAndDrop.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { PreprocessorLib } from "./lib/PreprocessorLib.sol";
import { ShifterBorrowProxyLib } from "../ShifterBorrowProxyLib.sol";
import { SandboxLib } from "../utils/sandbox/SandboxLib.sol";
import { BorrowProxyLib } from "../BorrowProxyLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { StringLib } from "../utils/StringLib.sol";
import { IUniswapV2Router01 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import { UniswapV2AdapterLib } from "../adapters/assets/uniswap-v2/UniswapV2AdapterLib.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

contract V2SwapAndDrop {
  using SafeMath for uint256;
  using UniswapV2AdapterLib for *;
  using PreprocessorLib for *;
  BorrowProxyLib.ProxyIsolate isolate;
  address public router;
  address public token;
  address public recipient;
  function setup(bytes memory consData) public {
    (router, token, recipient) = abi.decode(consData, (address, address, address));
  }
  function execute(bytes memory data) view public returns (ShifterBorrowProxyLib.InitializationAction[] memory result) {
    SandboxLib.ExecutionContext memory context = data.toContext();
    V2SwapAndDrop self = V2SwapAndDrop(context.preprocessorAddress);
    IUniswapV2Router01 selfRouter = IUniswapV2Router01(self.router());
    address borrowToken = isolate.token;
    address[] memory path = UniswapV2AdapterLib.generatePathForToken(borrowToken, selfRouter.WETH(), self.token());
    return address(selfRouter).sendTransaction(UniswapV2AdapterLib.SwapExactTokensForTokensInputs({
      amountIn: IERC20(borrowToken).balanceOf(address(this)),
      amountOutMin: 1,
      path: path,
      to: self.recipient(),
      deadline: block.timestamp + 1
    }).encodeWithSelector());
  }
}

// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol



pragma solidity >=0.5.0;

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function PERMIT_TYPEHASH() external pure returns (bytes32);
    function nonces(address owner) external view returns (uint);

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint);
    function factory() external view returns (address);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function price0CumulativeLast() external view returns (uint);
    function price1CumulativeLast() external view returns (uint);
    function kLast() external view returns (uint);

    function mint(address to) external returns (uint liquidity);
    function burn(address to) external returns (uint amount0, uint amount1);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function skim(address to) external;
    function sync() external;

    function initialize(address, address) external;
}

// File: contracts/queries/GenesisQuery.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ShifterPoolQuery } from "./ShifterPoolQuery.sol";

contract GenesisQuery is ShifterPoolQuery {
  function execute(bytes memory /* context */) view public returns (uint256) {
    return isolate.genesis;
  }
}

// File: contracts/queries/ShifterPoolQuery.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ShifterPoolLib } from "../ShifterPoolLib.sol";

contract ShifterPoolQuery is Ownable {
  ShifterPoolLib.Isolate isolate;
}

// File: contracts/queries/lib/QueryLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library QueryLib {
  function returnBytes(bytes memory buffer) internal pure {
    assembly {
      return(add(0x20, buffer), mload(buffer))
    }
  }
}


// File: contracts/queries/pending-transfers/PendingTransfersQuery.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { ERC20AdapterLib } from "../../adapters/assets/erc20/ERC20AdapterLib.sol";

contract PendingTransfersQuery {
  function execute(bytes memory) public view returns (ERC20AdapterLib.EscrowRecord[] memory) {
    return ERC20AdapterLib.getIsolatePointer().payments;
  }
}

// File: contracts/queries/ShifterPoolIsolateQuery.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;

import { ShifterPoolQuery } from "./ShifterPoolQuery.sol";
import {ShifterPoolLib} from "../ShifterPoolLib.sol";

contract ShifterPoolIsolateQuery is ShifterPoolQuery {
  struct ExternalIsolate {
    uint256 genesis;
    address borrowProxyImplementation;
    address assetForwarderImplementation;
    address shifterRegistry;
    uint256 minTimeout;
    uint256 poolFee;
    uint256 daoFee;
    uint256 maxLoan;
  }
  function execute(bytes memory /* context */) public view returns (ExternalIsolate memory) {
    return ExternalIsolate({
      genesis: isolate.genesis,
      borrowProxyImplementation: isolate.borrowProxyImplementation,
      assetForwarderImplementation: isolate.assetForwarderImplementation,
      shifterRegistry: isolate.shifterRegistry,
      minTimeout: isolate.minTimeout,
      poolFee: isolate.poolFee,
      daoFee: isolate.daoFee,
      maxLoan: isolate.maxLoan
    });
  }
}

// File: contracts/utils/Base58Lib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library Base58Lib {
    bytes constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    /// @dev Converts hex string to base 58
    function toBase58(bytes memory source) internal pure returns (string memory) {
        if (source.length == 0) return new string(0);
        uint8[] memory digits = new uint8[](40); //TODO: figure out exactly how much is needed
        digits[0] = 0;
        uint8 digitlength = 1;
        for (uint256 i = 0; i < source.length; ++i) {
            uint256 carry = uint8(source[i]);
            for (uint256 j = 0; j < digitlength; ++j) {
                carry += uint256(digits[j]) * 256;
                digits[j] = uint8(carry % 58);
                carry = carry / 58;
            }

            while (carry > 0) {
                digits[digitlength] = uint8(carry % 58);
                digitlength++;
                carry = carry / 58;
            }
        }
        //return digits;
        return string(toAlphabet(reverse(truncate(digits, digitlength))));
    }

    function truncate(uint8[] memory array, uint8 length)
        internal pure returns (uint8[] memory)
    {
        uint8[] memory output = new uint8[](length);
        for (uint256 i = 0; i < length; i++) {
            output[i] = array[i];
        }
        return output;
    }

    function reverse(uint8[] memory input) internal pure returns (uint8[] memory) {
        uint8[] memory output = new uint8[](input.length);
        for (uint256 i = 0; i < input.length; i++) {
            output[i] = input[input.length - 1 - i];
        }
        return output;
    }

    function toAlphabet(uint8[] memory indices) internal pure returns (bytes memory) {
        bytes memory output = new bytes(indices.length);
        for (uint256 i = 0; i < indices.length; i++) {
            output[i] = ALPHABET[indices[i]];
        }
        return output;
    }
}

// File: contracts/utils/BitcoinScriptLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { LELib } from "./LELib.sol";
import { SliceLib } from "./SliceLib.sol";
import { MemcpyLib } from "./MemcpyLib.sol";

library BitcoinScriptLib {
  using LELib for *;
  using SliceLib for *;
  struct Script {
    bytes buffer;
    uint256 len;
  }
  function newScript(uint256 hint) internal pure returns (Script memory script) {
    script.buffer = new bytes(hint);
  }
  function newScript() internal pure returns (Script memory) {
    return newScript(0x400);
  }
  // push value
  uint8 constant _OP_FALSE = 0;
  uint8 constant _OP_0 = 0;
  uint8 constant _OP_PUSHDATA1 = 76;
  uint8 constant _OP_PUSHDATA2 = 77;
  uint8 constant _OP_PUSHDATA4 = 78;
  uint8 constant _OP_1NEGATE = 79;
  uint8 constant _OP_RESERVED = 80;
  uint8 constant _OP_TRUE = 81;
  uint8 constant _OP_1 = 81;
  uint8 constant _OP_2 = 82;
  uint8 constant _OP_3 = 83;
  uint8 constant _OP_4 = 84;
  uint8 constant _OP_5 = 85;
  uint8 constant _OP_6 = 86;
  uint8 constant _OP_7 = 87;
  uint8 constant _OP_8 = 88;
  uint8 constant _OP_9 = 89;
  uint8 constant _OP_10 = 90;
  uint8 constant _OP_11 = 91;
  uint8 constant _OP_12 = 92;
  uint8 constant _OP_13 = 93;
  uint8 constant _OP_14 = 94;
  uint8 constant _OP_15 = 95;
  uint8 constant _OP_16 = 96;

  // control
  uint8 constant _OP_NOP = 97;
  uint8 constant _OP_VER = 98;
  uint8 constant _OP_IF = 99;
  uint8 constant _OP_NOTIF = 100;
  uint8 constant _OP_VERIF = 101;
  uint8 constant _OP_VERNOTIF = 102;
  uint8 constant _OP_ELSE = 103;
  uint8 constant _OP_ENDIF = 104;
  uint8 constant _OP_VERIFY = 105;
  uint8 constant _OP_RETURN = 106;
  // stack ops
  uint8 constant _OP_TOALTSTACK = 107;
  uint8 constant _OP_FROMALTSTACK = 108;
  uint8 constant _OP_2DROP = 109;
  uint8 constant _OP_2DUP = 110;
  uint8 constant _OP_3DUP = 111;
  uint8 constant _OP_2OVER = 112;
  uint8 constant _OP_2ROT = 113;
  uint8 constant _OP_2SWAP = 114;
  uint8 constant _OP_IFDUP = 115;
  uint8 constant _OP_DEPTH = 116;
  uint8 constant _OP_DROP = 117;
  uint8 constant _OP_DUP = 118;
  uint8 constant _OP_NIP = 119;
  uint8 constant _OP_OVER = 120;
  uint8 constant _OP_PICK = 121;
  uint8 constant _OP_ROLL = 122;
  uint8 constant _OP_ROT = 123;
  uint8 constant _OP_SWAP = 124;
  uint8 constant _OP_TUCK = 125;

  // splice ops
  uint8 constant _OP_CAT = 126;
  uint8 constant _OP_SUBSTR = 127;
  uint8 constant _OP_LEFT = 128;
  uint8 constant _OP_RIGHT = 129;
  uint8 constant _OP_SIZE = 130;

  // bit logic
  uint8 constant _OP_INVERT = 131;
  uint8 constant _OP_AND = 132;
  uint8 constant _OP_OR = 133;
  uint8 constant _OP_XOR = 134;
  uint8 constant _OP_EQUAL = 135;
  uint8 constant _OP_EQUALVERIFY = 136;
  uint8 constant _OP_RESERVED1 = 137;
  uint8 constant _OP_RESERVED2 = 138;

  // numeric
  uint8 constant _OP_1ADD = 139;
  uint8 constant _OP_1SUB = 140;
  uint8 constant _OP_2MUL = 141;
  uint8 constant _OP_2DIV = 142;
  uint8 constant _OP_NEGATE = 143;
  uint8 constant _OP_ABS = 144;
  uint8 constant _OP_NOT = 145;
  uint8 constant _OP_0NOTEQUAL = 146;

  uint8 constant _OP_ADD = 147;
  uint8 constant _OP_SUB = 148;
  uint8 constant _OP_MUL = 149;
  uint8 constant _OP_DIV = 150;
  uint8 constant _OP_MOD = 151;
  uint8 constant _OP_LSHIFT = 152;
  uint8 constant _OP_RSHIFT = 153;

  uint8 constant _OP_BOOLAND = 154;
  uint8 constant _OP_BOOLOR = 155;
  uint8 constant _OP_NUMEQUAL = 156;
  uint8 constant _OP_NUMEQUALVERIFY = 157;
  uint8 constant _OP_NUMNOTEQUAL = 158;
  uint8 constant _OP_LESSTHAN = 159;
  uint8 constant _OP_GREATERTHAN = 160;
  uint8 constant _OP_LESSTHANOREQUAL = 161;
  uint8 constant _OP_GREATERTHANOREQUAL = 162;
  uint8 constant _OP_MIN = 163;
  uint8 constant _OP_MAX = 164;

  uint8 constant _OP_WITHIN = 165;

  // crypto
  uint8 constant _OP_RIPEMD160 = 166;
  uint8 constant _OP_SHA1 = 167;
  uint8 constant _OP_SHA256 = 168;
  uint8 constant _OP_HASH160 = 169;
  uint8 constant _OP_HASH256 = 170;
  uint8 constant _OP_CODESEPARATOR = 171;
  uint8 constant _OP_CHECKSIG = 172;
  uint8 constant _OP_CHECKSIGVERIFY = 173;
  uint8 constant _OP_CHECKMULTISIG = 174;
  uint8 constant _OP_CHECKMULTISIGVERIFY = 175;

  uint8 constant _OP_CHECKLOCKTIMEVERIFY = 177;
  uint8 constant _OP_CHECKSEQUENCEVERIFY = 178;

  // expansion
  uint8 constant _OP_NOP1 = 176;
  uint8 constant _OP_NOP2 = 177;
  uint8 constant _OP_NOP3 = 178;
  uint8 constant _OP_NOP4 = 179;
  uint8 constant _OP_NOP5 = 180;
  uint8 constant _OP_NOP6 = 181;
  uint8 constant _OP_NOP7 = 182;
  uint8 constant _OP_NOP8 = 183;
  uint8 constant _OP_NOP9 = 184;
  uint8 constant _OP_NOP10 = 185;

  // template matching params
  uint8 constant _OP_PUBKEYHASH = 253;
  uint8 constant _OP_PUBKEY = 254;
  uint8 constant _OP_INVALIDOPCODE = 255;
  function OP_FALSE() internal pure returns (uint8) {
    return _OP_FALSE;
  }
  function OP_0() internal pure returns (uint8) {
    return _OP_0;
  }
  function OP_PUSHDATA1() internal pure returns (uint8) {
    return _OP_PUSHDATA1;
  }
  function OP_PUSHDATA2() internal pure returns (uint8) {
    return _OP_PUSHDATA2;
  }
  function OP_PUSHDATA4() internal pure returns (uint8) {
    return _OP_PUSHDATA4;
  }
  function OP_1NEGATE() internal pure returns (uint8) {
    return _OP_1NEGATE;
  }
  function OP_RESERVED() internal pure returns (uint8) {
    return _OP_RESERVED;
  }
  function OP_TRUE() internal pure returns (uint8) {
    return _OP_TRUE;
  }
  function OP_1() internal pure returns (uint8) {
    return _OP_1;
  }
  function OP_2() internal pure returns (uint8) {
    return _OP_2;
  }
  function OP_3() internal pure returns (uint8) {
    return _OP_3;
  }
  function OP_4() internal pure returns (uint8) {
    return _OP_4;
  }
  function OP_5() internal pure returns (uint8) {
    return _OP_5;
  }
  function OP_6() internal pure returns (uint8) {
    return _OP_6;
  }
  function OP_7() internal pure returns (uint8) {
    return _OP_7;
  }
  function OP_8() internal pure returns (uint8) {
    return _OP_8;
  }
  function OP_9() internal pure returns (uint8) {
    return _OP_9;
  }
  function OP_10() internal pure returns (uint8) {
    return _OP_10;
  }
  function OP_11() internal pure returns (uint8) {
    return _OP_11;
  }
  function OP_12() internal pure returns (uint8) {
    return _OP_12;
  }
  function OP_13() internal pure returns (uint8) {
    return _OP_13;
  }
  function OP_14() internal pure returns (uint8) {
    return _OP_14;
  }
  function OP_15() internal pure returns (uint8) {
    return _OP_15;
  }
  function OP_16() internal pure returns (uint8) {
    return _OP_16;
  }
  function OP_NOP() internal pure returns (uint8) {
    return _OP_NOP;
  }
  function OP_VER() internal pure returns (uint8) {
    return _OP_VER;
  }
  function OP_IF() internal pure returns (uint8) {
    return _OP_IF;
  }
  function OP_NOTIF() internal pure returns (uint8) {
    return _OP_NOTIF;
  }
  function OP_VERIF() internal pure returns (uint8) {
    return _OP_VERIF;
  }
  function OP_VERNOTIF() internal pure returns (uint8) {
    return _OP_VERNOTIF;
  }
  function OP_ELSE() internal pure returns (uint8) {
    return _OP_ELSE;
  }
  function OP_ENDIF() internal pure returns (uint8) {
    return _OP_ENDIF;
  }
  function OP_VERIFY() internal pure returns (uint8) {
    return _OP_VERIFY;
  }
  function OP_RETURN() internal pure returns (uint8) {
    return _OP_RETURN;
  }
  function OP_TOALTSTACK() internal pure returns (uint8) {
    return _OP_TOALTSTACK;
  }
  function OP_FROMALTSTACK() internal pure returns (uint8) {
    return _OP_FROMALTSTACK;
  }
  function OP_2DROP() internal pure returns (uint8) {
    return _OP_2DROP;
  }
  function OP_2DUP() internal pure returns (uint8) {
    return _OP_2DUP;
  }
  function OP_3DUP() internal pure returns (uint8) {
    return _OP_3DUP;
  }
  function OP_2OVER() internal pure returns (uint8) {
    return _OP_2OVER;
  }
  function OP_2ROT() internal pure returns (uint8) {
    return _OP_2ROT;
  }
  function OP_2SWAP() internal pure returns (uint8) {
    return _OP_2SWAP;
  }
  function OP_IFDUP() internal pure returns (uint8) {
    return _OP_IFDUP;
  }
  function OP_DEPTH() internal pure returns (uint8) {
    return _OP_DEPTH;
  }
  function OP_DROP() internal pure returns (uint8) {
    return _OP_DROP;
  }
  function OP_DUP() internal pure returns (uint8) {
    return _OP_DUP;
  }
  function OP_NIP() internal pure returns (uint8) {
    return _OP_NIP;
  }
  function OP_OVER() internal pure returns (uint8) {
    return _OP_OVER;
  }
  function OP_PICK() internal pure returns (uint8) {
    return _OP_PICK;
  }
  function OP_ROLL() internal pure returns (uint8) {
    return _OP_ROLL;
  }
  function OP_ROT() internal pure returns (uint8) {
    return _OP_ROT;
  }
  function OP_SWAP() internal pure returns (uint8) {
    return _OP_SWAP;
  }
  function OP_TUCK() internal pure returns (uint8) {
    return _OP_TUCK;
  }
  function OP_CAT() internal pure returns (uint8) {
    return _OP_CAT;
  }
  function OP_SUBSTR() internal pure returns (uint8) {
    return _OP_SUBSTR;
  }
  function OP_LEFT() internal pure returns (uint8) {
    return _OP_LEFT;
  }
  function OP_RIGHT() internal pure returns (uint8) {
    return _OP_RIGHT;
  }
  function OP_SIZE() internal pure returns (uint8) {
    return _OP_SIZE;
  }
  function OP_INVERT() internal pure returns (uint8) {
    return _OP_INVERT;
  }
  function OP_AND() internal pure returns (uint8) {
    return _OP_AND;
  }
  function OP_OR() internal pure returns (uint8) {
    return _OP_OR;
  }
  function OP_XOR() internal pure returns (uint8) {
    return _OP_XOR;
  }
  function OP_EQUAL() internal pure returns (uint8) {
    return _OP_EQUAL;
  }
  function OP_EQUALVERIFY() internal pure returns (uint8) {
    return _OP_EQUALVERIFY;
  }
  function OP_RESERVED1() internal pure returns (uint8) {
    return _OP_RESERVED1;
  }
  function OP_RESERVED2() internal pure returns (uint8) {
    return _OP_RESERVED2;
  }
  function OP_1ADD() internal pure returns (uint8) {
    return _OP_1ADD;
  }
  function OP_1SUB() internal pure returns (uint8) {
    return _OP_1SUB;
  }
  function OP_2MUL() internal pure returns (uint8) {
    return _OP_2MUL;
  }
  function OP_2DIV() internal pure returns (uint8) {
    return _OP_2DIV;
  }
  function OP_NEGATE() internal pure returns (uint8) {
    return _OP_NEGATE;
  }
  function OP_ABS() internal pure returns (uint8) {
    return _OP_ABS;
  }
  function OP_NOT() internal pure returns (uint8) {
    return _OP_NOT;
  }
  function OP_0NOTEQUAL() internal pure returns (uint8) {
    return _OP_0NOTEQUAL;
  }
  function OP_ADD() internal pure returns (uint8) {
    return _OP_ADD;
  }
  function OP_SUB() internal pure returns (uint8) {
    return _OP_SUB;
  }
  function OP_MUL() internal pure returns (uint8) {
    return _OP_MUL;
  }
  function OP_DIV() internal pure returns (uint8) {
    return _OP_DIV;
  }
  function OP_MOD() internal pure returns (uint8) {
    return _OP_MOD;
  }
  function OP_LSHIFT() internal pure returns (uint8) {
    return _OP_LSHIFT;
  }
  function OP_RSHIFT() internal pure returns (uint8) {
    return _OP_RSHIFT;
  }
  function OP_BOOLAND() internal pure returns (uint8) {
    return _OP_BOOLAND;
  }
  function OP_BOOLOR() internal pure returns (uint8) {
    return _OP_BOOLOR;
  }
  function OP_NUMEQUAL() internal pure returns (uint8) {
    return _OP_NUMEQUAL;
  }
  function OP_NUMEQUALVERIFY() internal pure returns (uint8) {
    return _OP_NUMEQUALVERIFY;
  }
  function OP_NUMNOTEQUAL() internal pure returns (uint8) {
    return _OP_NUMNOTEQUAL;
  }
  function OP_LESSTHAN() internal pure returns (uint8) {
    return _OP_LESSTHAN;
  }
  function OP_GREATERTHAN() internal pure returns (uint8) {
    return _OP_GREATERTHAN;
  }
  function OP_LESSTHANOREQUAL() internal pure returns (uint8) {
    return _OP_LESSTHANOREQUAL;
  }
  function OP_GREATERTHANOREQUAL() internal pure returns (uint8) {
    return _OP_GREATERTHANOREQUAL;
  }
  function OP_MIN() internal pure returns (uint8) {
    return _OP_MIN;
  }
  function OP_MAX() internal pure returns (uint8) {
    return _OP_MAX;
  }
  function OP_WITHIN() internal pure returns (uint8) {
    return _OP_WITHIN;
  }
  function OP_RIPEMD160() internal pure returns (uint8) {
    return _OP_RIPEMD160;
  }
  function OP_SHA1() internal pure returns (uint8) {
    return _OP_SHA1;
  }
  function OP_SHA256() internal pure returns (uint8) {
    return _OP_SHA256;
  }
  function OP_HASH160() internal pure returns (uint8) {
    return _OP_HASH160;
  }
  function OP_HASH256() internal pure returns (uint8) {
    return _OP_HASH256;
  }
  function OP_CODESEPARATOR() internal pure returns (uint8) {
    return _OP_CODESEPARATOR;
  }
  function OP_CHECKSIG() internal pure returns (uint8) {
    return _OP_CHECKSIG;
  }
  function OP_CHECKSIGVERIFY() internal pure returns (uint8) {
    return _OP_CHECKSIGVERIFY;
  }
  function OP_CHECKMULTISIG() internal pure returns (uint8) {
    return _OP_CHECKMULTISIG;
  }
  function OP_CHECKMULTISIGVERIFY() internal pure returns (uint8) {
    return _OP_CHECKMULTISIGVERIFY;
  }
  function OP_CHECKLOCKTIMEVERIFY() internal pure returns (uint8) {
    return _OP_CHECKLOCKTIMEVERIFY;
  }
  function OP_CHECKSEQUENCEVERIFY() internal pure returns (uint8) {
    return _OP_CHECKSEQUENCEVERIFY;
  }
  function OP_NOP1() internal pure returns (uint8) {
    return _OP_NOP1;
  }
  function OP_NOP2() internal pure returns (uint8) {
    return _OP_NOP2;
  }
  function OP_NOP3() internal pure returns (uint8) {
    return _OP_NOP3;
  }
  function OP_NOP4() internal pure returns (uint8) {
    return _OP_NOP4;
  }
  function OP_NOP5() internal pure returns (uint8) {
    return _OP_NOP5;
  }
  function OP_NOP6() internal pure returns (uint8) {
    return _OP_NOP6;
  }
  function OP_NOP7() internal pure returns (uint8) {
    return _OP_NOP7;
  }
  function OP_NOP8() internal pure returns (uint8) {
    return _OP_NOP8;
  }
  function OP_NOP9() internal pure returns (uint8) {
    return _OP_NOP9;
  }
  function OP_NOP10() internal pure returns (uint8) {
    return _OP_NOP10;
  }
  function OP_PUBKEYHASH() internal pure returns (uint8) {
    return _OP_PUBKEYHASH;
  }
  function OP_PUBKEY() internal pure returns (uint8) {
    return _OP_PUBKEY;
  }
  function OP_INVALIDOPCODE() internal pure returns (uint8) {
    return _OP_INVALIDOPCODE;
  }
  function _maybeRealloc(Script memory script, uint256 size) internal pure {
    if (script.len + size > script.buffer.length) {
      _realloc(script, script.buffer.length << 0x1);
    }
  }
  function _realloc(Script memory script, uint256 newLength) internal pure {
    bytes memory newBuffer = new bytes(newLength);
    bytes memory buffer = script.buffer;
    bytes32 newPtr;
    bytes32 ptr;
    assembly {
      newPtr := add(0x20, newBuffer)
      ptr := add(0x20, buffer)
    }
    MemcpyLib.memcpy(newPtr, ptr, script.len);
    script.buffer = newBuffer;
  }
  function addScript(Script memory script, uint8 op) internal pure returns (Script memory) {
    _maybeRealloc(script, 0x1);
    script.buffer[op] = byte(op);
    script.len++;
  } 
  function addScript(Script memory script, bytes memory buffer) internal pure returns (Script memory) {
    uint256 length = buffer.length;
    uint256 sz;
    bytes memory lengthBuffer;
    uint256 lenPtr;
    uint256 ptr;
    uint256 newPtr;
    byte op; 
    if (length < 0x100) {
      lengthBuffer = new bytes(1);
      sz = 1;
      op = byte(bytes1(_OP_PUSHDATA1));
      lengthBuffer[0] = byte(uint8(length));
    } else if (length < 0x10000) {
      lengthBuffer = uint16(length).toLE16();
      sz = 2;
      op = byte(bytes1(_OP_PUSHDATA2));
    } else if (length < 0x100000000) {
      lengthBuffer = uint32(length).toLE32();
      sz = 4;
      op = byte(bytes1(_OP_PUSHDATA4));
    } else revert("script pushdata overflow");
    _maybeRealloc(script, sz + 1 + buffer.length);
    bytes memory scriptBuffer = script.buffer;
    scriptBuffer[script.len] = op;
    assembly {
      lenPtr := add(0x20, lengthBuffer)
      ptr := add(0x21, scriptBuffer)
      newPtr := add(0x20, buffer)
    }
    MemcpyLib.memcpy(bytes32(ptr), bytes32(newPtr), sz);
    MemcpyLib.memcpy(bytes32(ptr + sz), bytes32(newPtr), buffer.length);
    return script;
  }
  function toBuffer(Script memory script) internal pure returns (bytes memory buffer) {
    buffer = script.buffer.toSlice(0, script.len).copy();
  }
  function hashBuffer(Script memory script) internal pure returns (bytes memory result) {
    bytes20 word = ripemd160(abi.encodePacked(keccak256(toBuffer(script))));
    result = new bytes(20);
    assembly {
      mstore(add(0x20, result), word)
    }
  }
  function toScriptHashOut(Script memory script) internal pure returns (Script memory output) {
    output = newScript(24);
    addScript(output, _OP_HASH160);
    addScript(output, hashBuffer(script));
    addScript(output, _OP_EQUAL);
  }
  function toAddress(Script memory script, bool isTestnet) internal pure returns (bytes memory buffer) {
    buffer = new bytes(21);
    buffer[0] = byte(uint8(isTestnet ? 0xc4 : 0x05));
    bytes memory scriptHash = hashBuffer(script);
    assembly {
      mstore(add(buffer, 0x21), mload(add(0x20, scriptHash)))
    }
  }
  function addressToBytes(address input) internal pure returns (bytes memory buffer) {
    buffer = new bytes(20);
    bytes20 word = bytes20(uint160(input));
    assembly {
      mstore(add(0x20, buffer), word)
    }
  }
  function bytes32ToBytes(bytes32 input) internal pure returns (bytes memory buffer) {
    buffer = new bytes(32);
    assembly {
      mstore(add(0x20, buffer), input)
    }
  }
  function assembleMintScript(bytes32 gHash, address mpkh) internal pure returns (Script memory script) {
    script = newScript(0x3d);
    addScript(script, bytes32ToBytes(gHash));
    addScript(script, _OP_DROP);
    addScript(script, _OP_DUP);
    addScript(script, _OP_HASH160);
    addScript(script, addressToBytes(mpkh));
    addScript(script, _OP_EQUALVERIFY);
    addScript(script, _OP_CHECKSIG);
    return toScriptHashOut(script);
  }
}

// File: contracts/utils/LELib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

library LELib {
  function toLE16(uint16 sz) internal pure returns (bytes memory buffer) {
    bytes2 casted = bytes2(sz);
    buffer = new bytes(2);
    assembly {
      mstore(add(0x20, buffer), casted)
    }
    byte tmp = buffer[0];
    buffer[0] = buffer[1];
    buffer[1] = tmp;
  }
  function toLE32(uint32 sz) internal pure returns (bytes memory buffer) {
    bytes4 casted = bytes4(sz);
    buffer = new bytes(4);
    assembly {
      mstore(add(0x20, buffer), casted)
    }
    byte tmp = buffer[0];
    buffer[0] = buffer[3];
    buffer[3] = tmp;
    tmp = buffer[1];
    buffer[1] = buffer[2];
    buffer[2] = tmp;
  }
}

// File: contracts/utils/RStoreLib.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { MemcpyLib } from "./MemcpyLib.sol";

library RStoreLib {
  bytes32 constant ZERO_SALT = 0x0000000000000000000000000000000000000000000000000000000000000000;
  function store(bytes memory data) internal returns (address) {
    bytes memory segment = hex"7f000000000000000000000000000000000000000000000000000000000000000080602a6000396000f3";
    uint256 length = data.length;
    bytes32 dest;
    bytes32 src;
    assembly {
      mstore(add(segment, 0x21), length)
      mstore(segment, add(0x2a, length))
      dest := add(segment, 0x4a)
      src := add(data, 0x20)
    }
    MemcpyLib.memcpy(dest, src, length);
    return Create2.deploy(0, ZERO_SALT, segment);
  }
}

// File: contracts/utils/sandbox/ISafeViewExecutor.sol



pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import { SafeViewLib } from "./SafeViewLib.sol";

contract ISafeViewExecutor {
  function query(bytes memory /* creationCode */, bytes memory /* context */) public view returns (SafeViewLib.SafeViewResult memory) {
   // stub
  }
}