Contract 0x9Daec8D56CDCBDE72abe65F4a5daF8cc0A5bF2f9

 
Users that set ERC-20 allowance via this contract should revoke all approvals from this contract based on Primitive Finance's post mortem announcement.
Txn Hash
Method
Block
From
To
Value
0x1f89b7c69fa0b21dfef0e55a8e2487fbe2dd70e3b52c742ae46b5eb7512b2702Add Short Liquid...118890442021-02-19 18:44:05586 days 19 hrs ago0x144028f5927f14f8800126c35391045bbc8a3d57 IN  Primitive: SushiSwap Connector0 Ether0.04698465159
0xf86d2f563fccbe1c020088a71336106eafb55cbd92ca25eefb1c534744ead773Add Short Liquid...118831022021-02-18 20:59:39587 days 17 hrs agoENS Name centrocapital.eth IN  Primitive: SushiSwap Connector0 Ether0.04317948147
0x457e9647f08bba5ea0cac8f72706b6288d341ceb432069866ce649be16983cf3Add Short Liquid...118788582021-02-18 5:09:25588 days 9 hrs ago0xbd7ea7c975cc23049ac31968c764af012ce45f58 IN  Primitive: SushiSwap Connector0 Ether0.03348456104
0xbb04952ce7c3830dccf524ce395265b886741328836a55961c24596409e7d7e0Add Short Liquid...118694982021-02-16 18:33:29589 days 19 hrs ago0x7bf723a7fb7d9013341c299c1b9c3fa0dd952fc6 IN  Primitive: SushiSwap Connector0 Ether0.04239385139.15
0x44257ddf9e28b952e7c81e5e63cb15f0d63e81e3e3545183c3cd0a909208db70Add Short Liquid...118694012021-02-16 18:13:13589 days 20 hrs ago0x7bf723a7fb7d9013341c299c1b9c3fa0dd952fc6 IN  Primitive: SushiSwap Connector0 Ether0.02734382121
0xa80984e93c8d41acb273ba7de0ed9597acbf122cd6e1e35f8c05577955ef669aOpen Flash Long118681192021-02-16 13:29:38590 days 59 mins ago0x99fe1a841d59379acd34da92ad01cf4244e73475 IN  Primitive: SushiSwap Connector0 Ether0.03095579112
0xb4e2f8df357ecbfae1c5bf32628ab0a853f6c14cda06566bc402510481d7f55cMint Options The...118676032021-02-16 11:42:36590 days 2 hrs agoENS Name sonnyf.eth IN  Primitive: SushiSwap Connector0 Ether0.03595255106
0x1b2c38f77c644d71578db9288e9d017f39b7c3692d9a655d2d8967a900c13a03Open Flash Long118624542021-02-15 16:36:40590 days 21 hrs ago0x69238af5756617e5218810057a03da509ec51fd4 IN  Primitive: SushiSwap Connector0 Ether0.05416992192
0xdfe7a61a58c399a25fe55f7201026d9f752036b39e0bcae080a59299413d8deaAdd Short Liquid...118571852021-02-14 21:14:31591 days 17 hrs agoENS Name nsai.eth IN  Primitive: SushiSwap Connector0 Ether0.03888175125
0x7d0eb3bd7767f53c0e0d31390266a5438174c0fb104cdac24c9cf63c7c2f0b97Add Short Liquid...118562302021-02-14 17:43:23591 days 20 hrs agoENS Name heavydextrader.eth IN  Primitive: SushiSwap Connector0 Ether0.03641682115
0xac1bf719c908e60285e3a422fd5f16ee0f35d88e3a0d5358ae3a7ab419bf3d42Add Short Liquid...118418952021-02-12 12:49:45594 days 1 hr ago0xdf488aab7738e982d45a15e55c3a9a526d76e719 IN  Primitive: SushiSwap Connector0 Ether0.03541769110
0xf715a459c4117576400c288d4511f86f6383a30d3195bfc9cf7908f1a91611f0Open Flash Long118407332021-02-12 8:32:41594 days 5 hrs ago0xafd680fb78648298d13422da8eb29199e72368ec IN  Primitive: SushiSwap Connector0 Ether0.03103221110
0xe1034f8dae05cee97074a4a091225826b2f1c676bc9a5dd86873c1f50a3eb704Add Short Liquid...118401992021-02-12 6:33:36594 days 7 hrs ago0x70cf494ca495498008abcb09af3c1209d404f3f9 IN  Primitive: SushiSwap Connector0 Ether0.0284949197
0x2b198a156ca4fa67dcacda2d7ebf0768aeca00ecc7550126ef88bc9732bfd1bbOpen Flash Long118398722021-02-12 5:24:02594 days 9 hrs ago0xafd680fb78648298d13422da8eb29199e72368ec IN  Primitive: SushiSwap Connector0 Ether0.0248671793
0x5dff08bb85978d435a82bc05527b85b7a4df67237dcb9962199a55650a8d03ecAdd Short Liquid...118381062021-02-11 22:52:09594 days 15 hrs ago0x9817e5fe1f05258747fbf70d835396d657e82c07 IN  Primitive: SushiSwap Connector0 Ether0.04887094151.8
0xb6263790eca7580bfd41b7278b0d980caa12cb409a4d670872166e12d5d433b5Open Flash Long118380932021-02-11 22:48:58594 days 15 hrs ago0xd8c680b0be0dee960fd3b2e8825bede7b0b3635d IN  Primitive: SushiSwap Connector0 Ether0.03724182132
0xa3e2cf5d2b523da1a2e07e935120e30703d82611c962ecc35115a03f0597da31Add Short Liquid...118372572021-02-11 19:50:06594 days 18 hrs ago0xMaki IN  Primitive: SushiSwap Connector0 Ether0.07886788260
0xadc3055f9e795cc76f77c15bda9e4ec4ddbb06e9f2489a73c08a93e3ff7e8148Open Flash Long118372052021-02-11 19:37:59594 days 18 hrs ago0xaf31d3c2972f62eb08f96a1fe29f579d61b4294d IN  Primitive: SushiSwap Connector0 Ether0.06350249242
0xfb353633d8dbff247a629c7a108260570abb49ffc07a07cac8969075640eb8c3Add Short Liquid...118370522021-02-11 19:03:46594 days 19 hrs ago0xaf31d3c2972f62eb08f96a1fe29f579d61b4294d IN  Primitive: SushiSwap Connector0 Ether0.04420098166
0x9a98694bb79ff82dc466315a64bac6021eb8f19d022c38f00a1d85dec78dc5ebRemove Short Liq...118370332021-02-11 18:59:52594 days 19 hrs ago0xaf31d3c2972f62eb08f96a1fe29f579d61b4294d IN  Primitive: SushiSwap Connector0 Ether0.06020932165
0xc16211513a18fe4ad667ee76688a4c86abe8c249f521c71338c4eaef015b3949Remove Short Liq...118368882021-02-11 18:29:23594 days 19 hrs ago0xMaki IN  Primitive: SushiSwap Connector0 Ether0.09465788248
0x6e8f3676b409dce9d4cba684d3ca1dd64cc553dec05bb000784f350c5fb45433Add Short Liquid...118321002021-02-11 0:39:44595 days 13 hrs ago0x9cc51db9d9c91470981b99607d7bfdfcc6244a3a IN  Primitive: SushiSwap Connector0 Ether0.05287932180
0xfdb21fa765707b9ee51efdf28e801ce32c31bad147bb85e29a163dd83b847e57Add Short Liquid...118302822021-02-10 18:05:58595 days 20 hrs agoENS Name geo.eth IN  Primitive: SushiSwap Connector0 Ether0.08250459271
0x7d4c4f2eba39f69a14e23bff8a12bae9562f42ba92b12d92da932cdddde020e0Add Short Liquid...118289422021-02-10 13:08:22596 days 1 hr ago0x391707d281e817b0fff52ba8897fabccb3e025b9 IN  Primitive: SushiSwap Connector0 Ether0.07248177273
0x90d964ce04aece85a6e2034f549f39393a6c8dcc536718eeb58eddd3fc23e05bOpen Flash Long118278452021-02-10 9:05:28596 days 5 hrs ago0x989e3d7da10d4781af5e653116c572abb4c71c91 IN  Primitive: SushiSwap Connector0 Ether0.05983824224
[ Download CSV Export 

OVERVIEW

The proxy contract that handles the trade logic for Primitive options to be traded on SushiSwap.

View more zero value Internal Transactions in Advanced View mode
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
UniswapConnector03

Compiler Version
v0.6.2+commit.bacdbe57

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 24 : IUniswapConnector03.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.2;

import {
    IUniswapV2Router02
} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import {
    IUniswapV2Factory
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import { ITrader } from "@primitivefi/contracts/contracts/option/interfaces/ITrader.sol";
import { IOption, IERC20 } from "@primitivefi/contracts/contracts/option/interfaces/IOption.sol";

interface IUniswapConnector03 {
    // ==== Combo Operations ====

    function mintShortOptionsThenSwapToTokens(
        IOption optionToken,
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (bool);

    // ==== Flash Functions ====

    function flashCloseLongOptionsThenSwap(
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 minPayout,
        address[] calldata path,
        address to
    ) external returns (uint256, uint256);

    function flashMintShortOptionsThenSwap(
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 maxPremium,
        address[] calldata path,
        address to
    ) external returns (uint256, uint256);

    function openFlashLong(
        IOption optionToken,
        uint256 amountOptions,
        uint256 amountOutMin
    ) external returns (bool);

    function closeFlashLong(
        IOption optionToken,
        uint256 amountRedeems,
        uint256 minPayout
    ) external returns (bool);

    // ==== Liquidity Functions ====

    function addShortLiquidityWithUnderlying(
        address optionAddress,
        uint256 quantityOptions,
        uint256 amountBMax,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function removeShortLiquidityThenCloseOptions(
        address optionAddress,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256, uint256);

    // ==== Management Functions ====

    function deployUniswapMarket(address optionAddress, address otherToken)
        external
        returns (address);

    // ==== View ====

    function getUniswapMarketForTokens(address token0, address token1)
        external
        view
        returns (address);

    function router() external view returns (IUniswapV2Router02);

    function factory() external view returns (IUniswapV2Factory);

    function trader() external view returns (ITrader);

    function getName() external pure returns (string memory);

    function getVersion() external pure returns (uint8);
}

File 2 of 24 : IUniswapV2Router02.sol
pragma solidity >=0.6.2;

import './IUniswapV2Router01.sol';

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

File 3 of 24 : IUniswapV2Factory.sol
pragma solidity >=0.5.0;

interface IUniswapV2Factory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);

    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

File 4 of 24 : ITrader.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.2;

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

interface ITrader {
    function safeMint(
        IOption optionToken,
        uint256 mintQuantity,
        address receiver
    ) external returns (uint256, uint256);

    function safeExercise(
        IOption optionToken,
        uint256 exerciseQuantity,
        address receiver
    ) external returns (uint256, uint256);

    function safeRedeem(
        IOption optionToken,
        uint256 redeemQuantity,
        address receiver
    ) external returns (uint256);

    function safeClose(
        IOption optionToken,
        uint256 closeQuantity,
        address receiver
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function safeUnwind(
        IOption optionToken,
        uint256 unwindQuantity,
        address receiver
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );
}

File 5 of 24 : IOption.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.2;

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

interface IOption is IERC20 {
    function mintOptions(address receiver) external returns (uint256, uint256);

    function exerciseOptions(
        address receiver,
        uint256 outUnderlyings,
        bytes calldata data
    ) external returns (uint256, uint256);

    function redeemStrikeTokens(address receiver) external returns (uint256);

    function closeOptions(address receiver)
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function redeemToken() external view returns (address);

    function getStrikeTokenAddress() external view returns (address);

    function getUnderlyingTokenAddress() external view returns (address);

    function getBaseValue() external view returns (uint256);

    function getQuoteValue() external view returns (uint256);

    function getExpiryTime() external view returns (uint256);

    function underlyingCache() external view returns (uint256);

    function strikeCache() external view returns (uint256);

    function factory() external view returns (address);

    function getCacheBalances() external view returns (uint256, uint256);

    function getAssetAddresses()
        external
        view
        returns (
            address,
            address,
            address
        );

    function getParameters()
        external
        view
        returns (
            address _underlyingToken,
            address _strikeToken,
            address _redeemToken,
            uint256 _base,
            uint256 _quote,
            uint256 _expiry
        );

    function initRedeemToken(address _redeemToken) external;

    function updateCacheBalances() external;
}

File 6 of 24 : 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 7 of 24 : 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 8 of 24 : 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 9 of 24 : 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 10 of 24 : 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 11 of 24 : 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 12 of 24 : UniswapConnector03.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.2;

///
/// @title   Combines Uniswap V2 Protocol functions with Primitive V1.
/// @notice  Primitive V1 UniswapConnector03 - @primitivefi/[email protected]
/// @author  Primitive
///

// Uniswap V2 & Primitive V1
import {
    IUniswapV2Callee
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";
import {
    IUniswapV2Pair
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import {
    IUniswapConnector03,
    IUniswapV2Router02,
    IUniswapV2Factory,
    IOption,
    ITrader,
    IERC20
} from "./interfaces/IUniswapConnector03.sol";
import {
    TraderLib
} from "@primitivefi/contracts/contracts/option/libraries/TraderLib.sol";
import {UniswapConnectorLib03} from "./libraries/UniswapConnectorLib03.sol";
// Open Zeppelin
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import {
    ReentrancyGuard
} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import "hardhat/console.sol";

contract UniswapConnector03 is
    IUniswapConnector03,
    IUniswapV2Callee,
    ReentrancyGuard
{
    using SafeERC20 for IERC20; // Reverts when `transfer` or `transferFrom` erc20 calls don't return proper data
    using SafeMath for uint256; // Reverts on math underflows/overflows

    ITrader public override trader; // The Primitive contract used to interact with the protocol
    IUniswapV2Factory public override factory; // The Uniswap V2 factory contract to get pair addresses from
    IUniswapV2Router02 public override router; // The Uniswap contract used to interact with the protocol

    event Initialized(address indexed from); // Emmitted on deployment
    event FlashOpened(address indexed from, uint256 quantity, uint256 premium); // Emmitted on flash opening a long position
    event FlashClosed(address indexed from, uint256 quantity, uint256 payout);
    event WroteOption(address indexed from, uint256 quantity);

    // ==== Constructor ====

    constructor(
        address router_,
        address factory_,
        address trader_
    ) public {
        require(address(router) == address(0x0), "ERR_INITIALIZED");
        require(address(factory) == address(0x0), "ERR_INITIALIZED");
        require(address(trader) == address(0x0), "ERR_INITIALIZED");
        router = IUniswapV2Router02(router_);
        factory = IUniswapV2Factory(factory_);
        trader = ITrader(trader_);
        emit Initialized(msg.sender);
    }

    // ==== Combo Operations ====

    ///
    /// @dev    Mints long + short option tokens, then swaps the shortOptionTokens (redeem) for tokens.
    /// @notice If the first address in the path is not the shortOptionToken address, the tx will fail.
    ///         underlyingToken -> shortOptionToken -> quoteToken.
    ///         IMPORTANT: redeemTokens = shortOptionTokens
    /// @param optionToken The address of the Option contract.
    /// @param amountIn The quantity of options to mint.
    /// @param amountOutMin The minimum quantity of tokens to receive in exchange for the shortOptionTokens.
    /// @param path The token addresses to trade through using their Uniswap V2 pools. Assumes path[0] = shortOptionToken.
    /// @param to The address to send the shortOptionToken proceeds and longOptionTokens to.
    /// @param deadline The timestamp for a trade to fail at if not successful.
    /// @return bool Whether the transaction was successful or not.
    ///
    function mintShortOptionsThenSwapToTokens(
        IOption optionToken,
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external override nonReentrant returns (bool) {
        bool success =
            UniswapConnectorLib03.mintShortOptionsThenSwapToTokens(
                router,
                optionToken,
                amountIn,
                amountOutMin,
                path,
                to,
                deadline
            );
        return success;
    }

    /// @dev    Write options by minting option tokens and selling the long option tokens for premium.
    /// @notice IMPORTANT: if `minPayout` is 0, this function can cost the caller `underlyingToken`s.
    /// @param optionToken The option contract to underwrite.
    /// @param writeQuantity The quantity of option tokens to write and equally, the quantity of underlyings to deposit.
    /// @param minPayout The minimum amount of underlyingTokens to receive from selling long option tokens.
    function mintOptionsThenFlashCloseLong(
        IOption optionToken,
        uint256 writeQuantity,
        uint256 minPayout
    ) external returns (bool) {
        // Pulls underlyingTokens from `msg.sender` using `transferFrom`. Mints option tokens to `msg.sender`.
        (, uint256 outputRedeems) =
            TraderLib.safeMint(optionToken, writeQuantity, msg.sender);

        // Sell the long option tokens for underlyingToken premium.
        bool success = closeFlashLong(optionToken, outputRedeems, minPayout);
        require(success, "ERR_FLASH_CLOSE");
        emit WroteOption(msg.sender, writeQuantity);
        return success;
    }

    // ==== Flash Functions ====

    ///
    /// @dev    Receives underlyingTokens from a UniswapV2Pair.swap() call from a pair with
    ///         reserve0 = shortOptionTokens and reserve1 = underlyingTokens.
    ///         Uses underlyingTokens to mint long (option) + short (redeem) tokens.
    ///         Sends longOptionTokens to msg.sender, and pays back the UniswapV2Pair the shortOptionTokens,
    ///         AND any remainder quantity of underlyingTokens (paid by msg.sender).
    /// @notice If the first address in the path is not the shortOptionToken address, the tx will fail.
    /// @param optionAddress The address of the Option contract.
    /// @param flashLoanQuantity The quantity of options to mint using borrowed underlyingTokens.
    /// @param maxPremium The maximum quantity of underlyingTokens to pay for the optionTokens.
    /// @param path The token addresses to trade through using their Uniswap V2 pools. Assumes path[0] = shortOptionToken.
    /// @param to The address to send the shortOptionToken proceeds and longOptionTokens to.
    /// @return success bool Whether the transaction was successful or not.
    ///
    function flashMintShortOptionsThenSwap(
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 maxPremium,
        address[] memory path,
        address to
    ) public override returns (uint256, uint256) {
        (uint256 outputOptions, uint256 loanRemainder) =
            UniswapConnectorLib03.flashMintShortOptionsThenSwap(
                router,
                pairAddress,
                optionAddress,
                flashLoanQuantity,
                maxPremium,
                path,
                to
            );
        emit FlashOpened(msg.sender, outputOptions, loanRemainder);
        return (outputOptions, loanRemainder);
    }

    /// @dev    Sends shortOptionTokens to msg.sender, and pays back the UniswapV2Pair in underlyingTokens.
    /// @notice IMPORTANT: If minPayout is 0, the `to` address is liable for negative payouts *if* that occurs.
    /// @param pairAddress The address of the redeemToken<>underlyingToken UniswapV2Pair contract.
    /// @param optionAddress The address of the longOptionTokes to close.
    /// @param flashLoanQuantity The quantity of shortOptionTokens borrowed to use to close longOptionTokens.
    /// @param minPayout The minimum payout of underlyingTokens sent to the `to` address.
    /// @param path underlyingTokens -> shortOptionTokens, because we are paying the input of underlyingTokens.
    /// @param to The address which is sent the underlyingToken payout, or liable to pay for a negative payout.
    function flashCloseLongOptionsThenSwap(
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 minPayout,
        address[] memory path,
        address to
    ) public override returns (uint256, uint256) {
        (uint256 outputUnderlyings, uint256 underlyingPayout) =
            UniswapConnectorLib03.flashCloseLongOptionsThenSwap(
                router,
                pairAddress,
                optionAddress,
                flashLoanQuantity,
                minPayout,
                path,
                to
            );
        emit FlashClosed(msg.sender, outputUnderlyings, underlyingPayout);
        return (outputUnderlyings, underlyingPayout);
    }

    ///
    /// @dev    Opens a longOptionToken position by minting long + short tokens, then selling the short tokens.
    /// @notice IMPORTANT: amountOutMin parameter is the price to swap shortOptionTokens to underlyingTokens.
    ///         IMPORTANT: If the ratio between shortOptionTokens and underlyingTokens is 1:1, then only the swap fee (0.30%) has to be paid.
    /// @param optionToken The option address.
    /// @param amountOptions The quantity of longOptionTokens to purchase.
    /// @param maxPremium The maximum quantity of underlyingTokens to pay for the optionTokens.
    ///
    function openFlashLong(
        IOption optionToken,
        uint256 amountOptions,
        uint256 maxPremium
    ) external override nonReentrant returns (bool) {
        address redeemToken = optionToken.redeemToken();
        address underlyingToken = optionToken.getUnderlyingTokenAddress();
        address pairAddress = factory.getPair(redeemToken, underlyingToken);

        // Build the path to get the appropriate reserves to borrow from, and then pay back.
        // We are borrowing from reserve1 then paying it back mostly in reserve0.
        // Borrowing underlyingTokens, paying back in shortOptionTokens (normal swap). Pay any remainder in underlyingTokens.
        address[] memory path = new address[](2);
        path[0] = redeemToken;
        path[1] = underlyingToken;
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);

        bytes4 selector =
            bytes4(
                keccak256(
                    bytes(
                        "flashMintShortOptionsThenSwap(address,address,uint256,uint256,address[],address)"
                    )
                )
            );
        bytes memory params =
            abi.encodeWithSelector(
                selector, // function to call in this contract
                pairAddress, // pair contract we are borrowing from
                optionToken, // option token to mint with flash loaned tokens
                amountOptions, // quantity of underlyingTokens from flash loan to use to mint options
                maxPremium, // total price paid (in underlyingTokens) for selling shortOptionTokens
                path, // redeemToken -> underlyingToken
                msg.sender // address to pull the remainder loan amount to pay, and send longOptionTokens to.
            );

        // Receives 0 quoteTokens and `amountOptions` of underlyingTokens to `this` contract address.
        // Then executes `flashMintShortOptionsThenSwap`.
        uint256 amount0Out =
            pair.token0() == underlyingToken ? amountOptions : 0;
        uint256 amount1Out =
            pair.token0() == underlyingToken ? 0 : amountOptions;

        // Borrow the amountOptions quantity of underlyingTokens and execute the callback function using params.
        pair.swap(amount0Out, amount1Out, address(this), params);
        return true;
    }

    ///
    /// @dev    Closes a longOptionToken position by flash swapping in redeemTokens,
    ///         closing the option, and paying back in underlyingTokens.
    /// @notice IMPORTANT: If minPayout is 0, this function will cost the caller to close the option, for no gain.
    /// @param optionToken The address of the longOptionTokens to close.
    /// @param amountRedeems The quantity of redeemTokens to borrow to close the options.
    /// @param minPayout The minimum payout of underlyingTokens sent out to the user.
    ///
    function closeFlashLong(
        IOption optionToken,
        uint256 amountRedeems,
        uint256 minPayout
    ) public override nonReentrant returns (bool) {
        address redeemToken = optionToken.redeemToken();
        address underlyingToken = optionToken.getUnderlyingTokenAddress();
        address pairAddress = factory.getPair(redeemToken, underlyingToken);

        // Build the path to get the appropriate reserves to borrow from, and then pay back.
        // We are borrowing from reserve1 then paying it back mostly in reserve0.
        // Borrowing redeemTokens, paying back in underlyingTokens (normal swap).
        // Pay any remainder in underlyingTokens.
        address[] memory path = new address[](2);
        path[0] = underlyingToken;
        path[1] = redeemToken;
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);

        bytes4 selector =
            bytes4(
                keccak256(
                    bytes(
                        "flashCloseLongOptionsThenSwap(address,address,uint256,uint256,address[],address)"
                    )
                )
            );
        bytes memory params =
            abi.encodeWithSelector(
                selector, // function to call in this contract
                pairAddress, // pair contract we are borrowing from
                optionToken, // option token to close with flash loaned redeemTokens
                amountRedeems, // quantity of redeemTokens from flash loan to use to close options
                minPayout, // total remaining underlyingTokens after flash loan is paid
                path, // underlyingToken -> redeemToken
                msg.sender // address to send payout of underlyingTokens to. Will pull underlyingTokens if negative payout and minPayout <= 0.
            );

        // Receives 0 underlyingTokens and `amountRedeems` of redeemTokens to `this` contract address.
        // Then executes `flashCloseLongOptionsThenSwap`.
        uint256 amount0Out = pair.token0() == redeemToken ? amountRedeems : 0;
        uint256 amount1Out = pair.token0() == redeemToken ? 0 : amountRedeems;

        // Borrow the amountRedeems quantity of redeemTokens and execute the callback function using params.
        pair.swap(amount0Out, amount1Out, address(this), params);
        return true;
    }

    // ==== Liquidity Functions ====

    ///
    /// @dev    Adds redeemToken liquidity to a redeem<>token pair by minting shortOptionTokens with underlyingTokens.
    /// @notice Pulls underlying tokens from msg.sender and pushes UNI-V2 liquidity tokens to the "to" address.
    ///         underlyingToken -> redeemToken -> UNI-V2.
    /// @param optionAddress The address of the optionToken to get the redeemToken to mint then provide liquidity for.
    /// @param quantityOptions The quantity of underlyingTokens to use to mint option + redeem tokens.
    /// @param amountBMax The minimum quantity of shortOptionTokens expected to provide liquidity with.
    /// @param amountBMin The minimum quantity of otherTokens expected to provide liquidity with.
    /// @param to The address that receives UNI-V2 shares.
    /// @param deadline The timestamp to expire a pending transaction.
    ///
    function addShortLiquidityWithUnderlying(
        address optionAddress,
        uint256 quantityOptions,
        uint256 amountBMax,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        override
        nonReentrant
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        return
            UniswapConnectorLib03.addShortLiquidityWithUnderlying(
                router,
                optionAddress,
                quantityOptions,
                amountBMax,
                amountBMin,
                to,
                deadline
            );
    }

    ///
    /// @dev    Combines Uniswap V2 Router "removeLiquidity" function with Primitive "closeOptions" function.
    /// @notice Pulls UNI-V2 liquidity shares with shortOption<>underlying token, and optionTokens from msg.sender.
    ///         Then closes the longOptionTokens and withdraws underlyingTokens to the "to" address.
    ///         Sends underlyingTokens from the burned UNI-V2 liquidity shares to the "to" address.
    ///         UNI-V2 -> optionToken -> underlyingToken.
    /// @param optionAddress The address of the option that will be closed from burned UNI-V2 liquidity shares.
    /// @param liquidity The quantity of liquidity tokens to pull from msg.sender and burn.
    /// @param amountAMin The minimum quantity of shortOptionTokens to receive from removing liquidity.
    /// @param amountBMin The minimum quantity of underlyingTokens to receive from removing liquidity.
    /// @param to The address that receives underlyingTokens from burned UNI-V2, and underlyingTokens from closed options.
    /// @param deadline The timestamp to expire a pending transaction.
    ///
    function removeShortLiquidityThenCloseOptions(
        address optionAddress,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external override nonReentrant returns (uint256, uint256) {
        address redeemToken = IOption(optionAddress).redeemToken();
        address underlyingTokenAddress =
            IOption(optionAddress).getUnderlyingTokenAddress();

        // Check the short option tokens before and after, there could be dust.
        uint256 redeemBalance = IERC20(redeemToken).balanceOf(address(this));

        // Remove liquidity by burning lp tokens from msg.sender, withdraw tokens to this contract.
        // Notice: the `to` address is not passed into this function, because address(this) receives the withrawn tokens.
        (uint256 shortTokensWithdrawn, uint256 underlyingTokensWithdrawn) =
            UniswapConnectorLib03.removeLiquidity(
                router, // UniswapV2Router02
                redeemToken, // tokenA
                underlyingTokenAddress, // tokenB
                liquidity,
                amountAMin,
                amountBMin,
                deadline
            );

        // Burn option and redeem tokens from this contract then send underlyingTokens to the `to` address.
        (, , uint256 underlyingTokensFromClosedOptions) =
            UniswapConnectorLib03.closeOptionsWithShortTokens(
                trader, // Primitive V1 Trader
                IOption(optionAddress),
                shortTokensWithdrawn,
                to
            );

        // After the options were closed, calculate the dust by checking after balance against the before balance.
        redeemBalance = IERC20(redeemToken).balanceOf(address(this)).sub(
            redeemBalance
        );

        // If there is dust, send it out
        if (redeemBalance > 0) {
            IERC20(redeemToken).safeTransfer(to, redeemBalance);
        }

        // Send the UnderlyingTokens received from burning liquidity shares to the "to" address.
        IERC20(underlyingTokenAddress).safeTransfer(
            to,
            underlyingTokensWithdrawn
        );
        return (
            underlyingTokensWithdrawn.add(underlyingTokensFromClosedOptions),
            redeemBalance
        );
    }

    // ==== Callback Implementation ====

    ///
    /// @dev The callback function triggered in a UniswapV2Pair.swap() call when the `data` parameter has data.
    /// @param sender The original msg.sender of the UniswapV2Pair.swap() call.
    /// @param amount0 The quantity of token0 received to the `to` address in the swap() call.
    /// @param amount1 The quantity of token1 received to the `to` address in the swap() call.
    /// @param data The payload passed in the `data` parameter of the swap() call.
    ///
    function uniswapV2Call(
        address sender,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external override {
        address token0 = IUniswapV2Pair(msg.sender).token0();
        address token1 = IUniswapV2Pair(msg.sender).token1();
        assert(msg.sender == factory.getPair(token0, token1)); /// ensure that msg.sender is actually a V2 pair
        (bool success, bytes memory returnData) = address(this).call(data);
        require(
            success &&
                (returnData.length == 0 || abi.decode(returnData, (bool))),
            "ERR_UNISWAPV2_CALL_FAIL"
        );
    }

    // ==== Management Functions ====

    /// @dev Creates a UniswapV2Pair by calling `createPair` on the UniswapV2Factory.
    function deployUniswapMarket(address optionAddress, address otherToken)
        external
        override
        returns (address)
    {
        address uniswapPair = factory.createPair(optionAddress, otherToken);
        return uniswapPair;
    }

    // ==== View ====

    /// @dev Gets a UniswapV2Pair address for two tokens by calling the UniswapV2Factory.
    function getUniswapMarketForTokens(address token0, address token1)
        public
        view
        override
        returns (address)
    {
        address uniswapPair = factory.getPair(token0, token1);
        require(uniswapPair != address(0x0), "ERR_PAIR_DOES_NOT_EXIST");
        return uniswapPair;
    }

    /// @dev Gets the name of the contract.
    function getName() external pure override returns (string memory) {
        return "PrimitiveV1UniswapConnector03";
    }

    /// @dev Gets the version of the contract.
    function getVersion() external pure override returns (uint8) {
        return uint8(3);
    }

    /// @dev    Gets the total premium cost to buy `quantity` of `optionToken`s.
    /// @notice Also returns the negative premium, which will be 0 in most cases.
    /// @param  optionToken The option to get the close premium of.
    /// @param  quantityLong The quantity of long option tokens that will be closed.
    /// @return premiumCost, premiumPayout
    function getOpenPremium(IOption optionToken, uint256 quantityLong)
        external
        view
        returns (uint256, uint256)
    {
        return
            UniswapConnectorLib03.getOpenPremium(
                router,
                optionToken,
                quantityLong
            );
    }

    /// @dev    Gets the total premium payout to sell long option tokens proportional to `quantity` of `shortOptionToken`s.
    /// @notice Also gets the cost, a negative payout, which will be 0 unless the reserve ratio is incorrectly set.
    /// @param  optionToken The option to get the close premium of.
    /// @param  quantityShort The quantity of short option tokens that will be closed.
    /// @return premiumPayout, premumCost
    function getClosePremium(IOption optionToken, uint256 quantityShort)
        external
        view
        returns (uint256, uint256)
    {
        return
            UniswapConnectorLib03.getClosePremium(
                router,
                optionToken,
                quantityShort
            );
    }
}

File 13 of 24 : IUniswapV2Callee.sol
pragma solidity >=0.5.0;

interface IUniswapV2Callee {
    function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
}

File 14 of 24 : 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 15 of 24 : TraderLib.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.2;

/**
 * @title   Trader Library
 * @notice  Internal functions that can be used to safeTransfer
 *          tokens into the option contract then call respective option contract functions.
 * @author  Primitive
 */

import { IOption } from "../interfaces/IOption.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

library TraderLib {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /**
     * @dev Conducts important safety checks to safely mint option tokens.
     * @param optionToken The address of the option token to mint.
     * @param mintQuantity The quantity of option tokens to mint.
     * @param receiver The address which receives the minted option tokens.
     */
    function safeMint(
        IOption optionToken,
        uint256 mintQuantity,
        address receiver
    ) internal returns (uint256, uint256) {
        require(mintQuantity > 0, "ERR_ZERO");
        IERC20(optionToken.getUnderlyingTokenAddress()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            mintQuantity
        );
        (uint256 outputOptions, uint256 outputRedeems) = optionToken
            .mintOptions(receiver);
        return (outputOptions, outputRedeems);
    }

    /**
     * @dev Swaps strikeTokens to underlyingTokens using the strike ratio as the exchange rate.
     * @notice Burns optionTokens, option contract receives strikeTokens, user receives underlyingTokens.
     * @param optionToken The address of the option contract.
     * @param exerciseQuantity Quantity of optionTokens to exercise.
     * @param receiver The underlyingTokens are sent to the receiver address.
     */
    function safeExercise(
        IOption optionToken,
        uint256 exerciseQuantity,
        address receiver
    ) internal returns (uint256, uint256) {
        require(exerciseQuantity > 0, "ERR_ZERO");
        require(
            IERC20(address(optionToken)).balanceOf(msg.sender) >=
                exerciseQuantity,
            "ERR_BAL_OPTIONS"
        );

        // Calculate quantity of strikeTokens needed to exercise quantity of optionTokens.
        uint256 inputStrikes = exerciseQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());
        require(
            IERC20(optionToken.getStrikeTokenAddress()).balanceOf(msg.sender) >=
                inputStrikes,
            "ERR_BAL_STRIKE"
        );
        IERC20(optionToken.getStrikeTokenAddress()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputStrikes
        );
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(optionToken),
            exerciseQuantity
        );

        uint256 inputOptions;
        (inputStrikes, inputOptions) = optionToken.exerciseOptions(
            receiver,
            exerciseQuantity,
            new bytes(0)
        );
        return (inputStrikes, inputOptions);
    }

    /**
     * @dev Burns redeemTokens to withdraw available strikeTokens.
     * @notice inputRedeems = outputStrikes.
     * @param optionToken The address of the option contract.
     * @param redeemQuantity redeemQuantity of redeemTokens to burn.
     * @param receiver The strikeTokens are sent to the receiver address.
     */
    function safeRedeem(
        IOption optionToken,
        uint256 redeemQuantity,
        address receiver
    ) internal returns (uint256) {
        require(redeemQuantity > 0, "ERR_ZERO");
        require(
            IERC20(optionToken.redeemToken()).balanceOf(msg.sender) >=
                redeemQuantity,
            "ERR_BAL_REDEEM"
        );
        // There can be the case there is no available strikes to redeem, causing a revert.
        IERC20(optionToken.redeemToken()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            redeemQuantity
        );
        uint256 inputRedeems = optionToken.redeemStrikeTokens(receiver);
        return inputRedeems;
    }

    /**
     * @dev Burn optionTokens and redeemTokens to withdraw underlyingTokens.
     * @notice The redeemTokens to burn is equal to the optionTokens * strike ratio.
     * inputOptions = inputRedeems / strike ratio = outUnderlyings
     * @param optionToken The address of the option contract.
     * @param closeQuantity Quantity of optionTokens to burn.
     * (Implictly will burn the strike ratio quantity of redeemTokens).
     * @param receiver The underlyingTokens are sent to the receiver address.
     */
    function safeClose(
        IOption optionToken,
        uint256 closeQuantity,
        address receiver
    )
        internal
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        require(closeQuantity > 0, "ERR_ZERO");
        require(
            IERC20(address(optionToken)).balanceOf(msg.sender) >= closeQuantity,
            "ERR_BAL_OPTIONS"
        );

        // Calculate the quantity of redeemTokens that need to be burned. (What we mean by Implicit).
        uint256 inputRedeems = closeQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());
        require(
            IERC20(optionToken.redeemToken()).balanceOf(msg.sender) >=
                inputRedeems,
            "ERR_BAL_REDEEM"
        );
        IERC20(optionToken.redeemToken()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputRedeems
        );
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(optionToken),
            closeQuantity
        );

        uint256 inputOptions;
        uint256 outUnderlyings;
        (inputRedeems, inputOptions, outUnderlyings) = optionToken.closeOptions(
            receiver
        );
        return (inputRedeems, inputOptions, outUnderlyings);
    }

    /**
     * @dev Burn redeemTokens to withdraw underlyingTokens and strikeTokens from expired options.
     * @param optionToken The address of the option contract.
     * @param unwindQuantity Quantity of option tokens used to calculate the amount of redeem tokens to burn.
     * @param receiver The underlyingTokens are sent to the receiver address and the redeemTokens are burned.
     */
    function safeUnwind(
        IOption optionToken,
        uint256 unwindQuantity,
        address receiver
    )
        internal
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        // Checks
        require(unwindQuantity > 0, "ERR_ZERO");
        // solhint-disable-next-line not-rely-on-time
        require(
            optionToken.getExpiryTime() < block.timestamp,
            "ERR_NOT_EXPIRED"
        );

        // Calculate amount of redeems required
        uint256 inputRedeems = unwindQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());
        require(
            IERC20(optionToken.redeemToken()).balanceOf(msg.sender) >=
                inputRedeems,
            "ERR_BAL_REDEEM"
        );
        IERC20(optionToken.redeemToken()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputRedeems
        );

        uint256 inputOptions;
        uint256 outUnderlyings;
        (inputRedeems, inputOptions, outUnderlyings) = optionToken.closeOptions(
            receiver
        );

        return (inputRedeems, inputOptions, outUnderlyings);
    }
}

File 16 of 24 : UniswapConnectorLib03.sol
pragma solidity 0.6.2;

///
/// @title   Library for business logic for connecting Uniswap V2 Protocol functions with Primitive V1.
/// @notice  Primitive V1 UniswapConnectorLib03 - @primitivefi/[email protected]
/// @author  Primitive
///

// Uniswap
import {
    IUniswapV2Callee
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";
import {
    IUniswapV2Router02
} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import {
    IUniswapV2Factory
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import {
    IUniswapV2Pair
} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
// Primitive
import {
    ITrader,
    IOption
} from "@primitivefi/contracts/contracts/option/interfaces/ITrader.sol";
import {
    TraderLib,
    IERC20
} from "@primitivefi/contracts/contracts/option/libraries/TraderLib.sol";
import {IWethConnector01, IWETH} from "../interfaces/IWethConnector01.sol";
import {WethConnectorLib01} from "./WethConnectorLib01.sol";
// Open Zeppelin
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

library UniswapConnectorLib03 {
    using SafeERC20 for IERC20; // Reverts when `transfer` or `transferFrom` erc20 calls don't return proper data
    using SafeMath for uint256; // Reverts on math underflows/overflows

    /// ==== Combo Operations ====

    ///
    /// @dev    Mints long + short option tokens, then swaps the shortOptionTokens (redeem) for tokens.
    /// @notice If the first address in the path is not the shortOptionToken address, the tx will fail.
    ///         underlyingToken -> shortOptionToken -> quoteToken.
    ///         IMPORTANT: redeemTokens = shortOptionTokens
    /// @param optionToken The address of the Option contract.
    /// @param amountIn The quantity of options to mint.
    /// @param amountOutMin The minimum quantity of tokens to receive in exchange for the shortOptionTokens.
    /// @param path The token addresses to trade through using their Uniswap V2 pools. Assumes path[0] = shortOptionToken.
    /// @param to The address to send the shortOptionToken proceeds and longOptionTokens to.
    /// @param deadline The timestamp for a trade to fail at if not successful.
    /// @return bool Whether the transaction was successful or not.
    ///
    function mintShortOptionsThenSwapToTokens(
        IUniswapV2Router02 router,
        IOption optionToken,
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) internal returns (bool) {
        // Pulls underlyingTokens from msg.sender, then pushes underlyingTokens to option contract.
        // Mints long + short tokens to this contract.
        (uint256 outputOptions, uint256 outputRedeems) =
            mintOptionsKeepShortOptions(optionToken, amountIn);

        // Swaps shortOptionTokens to the token specified at the end of the path, then sends to msg.sender.
        // Reverts if the first address in the path is not the shortOptionToken address.
        address redeemToken = optionToken.redeemToken();
        (, bool success) =
            swapExactOptionsForTokens(
                router,
                redeemToken,
                outputRedeems, // shortOptionTokens = redeemTokens
                amountOutMin,
                path,
                to,
                deadline
            );
        // Fail early if the swap failed.
        require(success, "ERR_SWAP_FAILED");
        return success;
    }

    // ==== Flash Functions ====

    ///
    /// @dev    Receives underlyingTokens from a UniswapV2Pair.swap() call from a pair with
    ///         shortOptionTokens and underlyingTokens.
    ///         Uses underlyingTokens to mint long (option) + short (redeem) tokens.
    ///         Sends longOptionTokens to msg.sender, and pays back the UniswapV2Pair with shortOptionTokens,
    ///         AND any remainder quantity of underlyingTokens (paid by msg.sender).
    /// @notice If the first address in the path is not the shortOptionToken address, the tx will fail.
    ///         IMPORTANT: UniswapV2 adds a fee of 0.301% to the option premium cost.
    /// @param router The address of the UniswapV2Router02 contract.
    /// @param pairAddress The address of the redeemToken<>underlyingToken UniswapV2Pair contract.
    /// @param optionAddress The address of the Option contract.
    /// @param flashLoanQuantity The quantity of options to mint using borrowed underlyingTokens.
    /// @param maxPremium The maximum quantity of underlyingTokens to pay for the optionTokens.
    /// @param path The token addresses to trade through using their Uniswap V2 pools. Assumes path[0] = shortOptionToken.
    /// @param to The address to send the shortOptionToken proceeds and longOptionTokens to.
    /// @return success bool Whether the transaction was successful or not.
    ///
    function flashMintShortOptionsThenSwap(
        IUniswapV2Router02 router,
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 maxPremium,
        address[] memory path,
        address to
    ) internal returns (uint256, uint256) {
        require(msg.sender == address(this), "ERR_NOT_SELF");
        require(to != address(0x0), "ERR_TO_ADDRESS_ZERO");
        require(to != msg.sender, "ERR_TO_MSG_SENDER");
        require(
            pairFor(router.factory(), path[0], path[1]) == pairAddress,
            "ERR_INVALID_PAIR"
        );
        // IMPORTANT: Assume this contract has already received `flashLoanQuantity` of underlyingTokens.
        address underlyingToken =
            IOption(optionAddress).getUnderlyingTokenAddress();
        address redeemToken = IOption(optionAddress).redeemToken();
        require(path[1] == underlyingToken, "ERR_END_PATH_NOT_UNDERLYING");

        // Mint longOptionTokens using the underlyingTokens received from UniswapV2 flash swap to this contract.
        // Send underlyingTokens from this contract to the optionToken contract, then call mintOptions.
        (uint256 mintedOptions, uint256 mintedRedeems) =
            mintOptionsWithUnderlyingBalance(
                IOption(optionAddress),
                flashLoanQuantity
            );

        // The loanRemainder will be the amount of underlyingTokens that are needed from the original
        // transaction caller in order to pay the flash swap.
        // IMPORTANT: THIS IS EFFECTIVELY THE PREMIUM PAID IN UNDERLYINGTOKENS TO PURCHASE THE OPTIONTOKEN.
        uint256 loanRemainder;

        // Economically, negativePremiumPaymentInRedeems value should always be 0.
        // In the case that we minted more redeemTokens than are needed to pay back the flash swap,
        // (short -> underlying is a positive trade), there is an effective negative premium.
        // In that case, this function will send out `negativePremiumAmount` of redeemTokens to the original caller.
        // This means the user gets to keep the extra redeemTokens for free.
        // Negative premium amount is the opposite difference of the loan remainder: (paid - flash loan amount)
        uint256 negativePremiumPaymentInRedeems;
        (loanRemainder, negativePremiumPaymentInRedeems) = getOpenPremium(
            router,
            IOption(optionAddress),
            flashLoanQuantity
        );

        // In the case that more redeemTokens were minted than need to be sent back as payment,
        // calculate the new mintedRedeems value to send to the pair
        // (don't send all the minted redeemTokens).
        if (negativePremiumPaymentInRedeems > 0) {
            mintedRedeems = mintedRedeems.sub(negativePremiumPaymentInRedeems);
        }

        // In most cases, all of the minted redeemTokens will be sent to the pair as payment for the flash swap.
        if (mintedRedeems > 0) {
            IERC20(redeemToken).safeTransfer(pairAddress, mintedRedeems);
        }

        // If loanRemainder is non-zero and non-negative (most cases), send underlyingTokens to the pair as payment (premium).
        if (loanRemainder > 0) {
            // Pull underlyingTokens from the original msg.sender to pay the remainder of the flash swap.
            require(maxPremium >= loanRemainder, "ERR_PREMIUM_OVER_MAX"); // check for users to not pay over their max desired value.
            IERC20(underlyingToken).safeTransferFrom(
                to,
                pairAddress,
                loanRemainder
            );
        }

        // If negativePremiumAmount is non-zero and non-negative, send redeemTokens to the `to` address.
        if (negativePremiumPaymentInRedeems > 0) {
            IERC20(redeemToken).safeTransfer(
                to,
                negativePremiumPaymentInRedeems
            );
        }

        // Send minted longOptionTokens (option) to the original msg.sender.
        IERC20(optionAddress).safeTransfer(to, mintedOptions);
        return (mintedOptions, loanRemainder);
    }

    /// @dev    Sends shortOptionTokens to msg.sender, and pays back the UniswapV2Pair in underlyingTokens.
    /// @notice IMPORTANT: If minPayout is 0, the `to` address is liable for negative payouts *if* that occurs.
    /// @param router The UniswapV2Router02 contract.
    /// @param pairAddress The address of the redeemToken<>underlyingToken UniswapV2Pair contract.
    /// @param optionAddress The address of the longOptionTokes to close.
    /// @param flashLoanQuantity The quantity of shortOptionTokens borrowed to use to close longOptionTokens.
    /// @param minPayout The minimum payout of underlyingTokens sent to the `to` address.
    /// @param path underlyingTokens -> shortOptionTokens, because we are paying the input of underlyingTokens.
    /// @param to The address which is sent the underlyingToken payout, or liable to pay for a negative payout.
    function flashCloseLongOptionsThenSwap(
        IUniswapV2Router02 router,
        address pairAddress,
        address optionAddress,
        uint256 flashLoanQuantity,
        uint256 minPayout,
        address[] memory path,
        address to
    ) internal returns (uint256, uint256) {
        require(msg.sender == address(this), "ERR_NOT_SELF");
        require(to != address(0x0), "ERR_TO_ADDRESS_ZERO");
        require(to != msg.sender, "ERR_TO_MSG_SENDER");
        require(
            pairFor(router.factory(), path[0], path[1]) == pairAddress,
            "ERR_INVALID_PAIR"
        );

        // IMPORTANT: Assume this contract has already received `flashLoanQuantity` of redeemTokens.
        // We are flash swapping from an underlying <> shortOptionToken pair,
        // paying back a portion using underlyingTokens received from closing options.
        // In the flash open, we did redeemTokens to underlyingTokens.
        // In the flash close, we are doing underlyingTokens to redeemTokens and keeping the remainder.
        address underlyingToken =
            IOption(optionAddress).getUnderlyingTokenAddress();
        address redeemToken = IOption(optionAddress).redeemToken();
        require(path[1] == redeemToken, "ERR_END_PATH_NOT_REDEEM");

        // Quantity of underlyingTokens this contract receives from burning option + redeem tokens.
        uint256 outputUnderlyings =
            closeOptionsWithShortBalance(
                to,
                IOption(optionAddress),
                flashLoanQuantity
            );

        // Loan Remainder is the cost to pay out, should be 0 in most cases.
        // Underlying Payout is the `premium` that the original caller receives in underlyingTokens.
        // It's the remainder of underlyingTokens after the pair has been paid back underlyingTokens for the
        // flash swapped shortOptionTokens.
        (uint256 underlyingPayout, uint256 loanRemainder) =
            getClosePremium(router, IOption(optionAddress), flashLoanQuantity);

        // In most cases there will be an underlying payout, which is subtracted from the outputUnderlyings.
        if (underlyingPayout > 0) {
            outputUnderlyings = outputUnderlyings.sub(underlyingPayout);
        }

        // Pay back the pair in underlyingTokens.
        if (outputUnderlyings > 0) {
            IERC20(underlyingToken).safeTransfer(
                pairAddress,
                outputUnderlyings
            );
        }

        // If loanRemainder is non-zero and non-negative, send underlyingTokens to the pair as payment (premium).
        if (loanRemainder > 0) {
            // Pull underlyingTokens from the original msg.sender to pay the remainder of the flash swap.
            // Revert if the minPayout is less than or equal to the underlyingPayment of 0.
            // There is 0 underlyingPayment in the case that loanRemainder > 0.
            // This code branch can be successful by setting `minPayout` to 0.
            // This means the user is willing to pay to close the position.
            require(minPayout <= underlyingPayout, "ERR_NEGATIVE_PAYOUT");
            IERC20(underlyingToken).safeTransferFrom(
                to,
                pairAddress,
                loanRemainder
            );
        }

        // If underlyingPayout is non-zero and non-negative, send it to the `to` address.
        if (underlyingPayout > 0) {
            // Revert if minPayout is greater than the actual payout.
            require(underlyingPayout >= minPayout, "ERR_PREMIUM_UNDER_MIN");
            IERC20(underlyingToken).safeTransfer(to, underlyingPayout);
        }

        return (outputUnderlyings, underlyingPayout);
    }

    // ==== Liquidity Functions ====

    ///
    /// @dev    Adds redeemToken liquidity to a redeem<>underlyingToken pair by minting shortOptionTokens with underlyingTokens.
    /// @notice Pulls underlying tokens from msg.sender and pushes UNI-V2 liquidity tokens to the "to" address.
    ///         underlyingToken -> redeemToken -> UNI-V2.
    /// @param optionAddress The address of the optionToken to get the redeemToken to mint then provide liquidity for.
    /// @param quantityOptions The quantity of underlyingTokens to use to mint option + redeem tokens.
    /// @param amountBMax The quantity of underlyingTokens to add with shortOptionTokens to the Uniswap V2 Pair.
    /// @param amountBMin The minimum quantity of underlyingTokens expected to provide liquidity with.
    /// @param to The address that receives UNI-V2 shares.
    /// @param deadline The timestamp to expire a pending transaction.
    ///
    function addShortLiquidityWithUnderlying(
        IUniswapV2Router02 router,
        address optionAddress,
        uint256 quantityOptions,
        uint256 amountBMax,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        internal
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        uint256 amountA;
        uint256 amountB;
        uint256 liquidity;
        (, uint256 outputRedeems) =
            mintOptionsKeepShortOptions(
                IOption(optionAddress),
                quantityOptions
            );

        {
            // scope for adding exact liquidity, avoids stack too deep errors
            IOption optionToken = IOption(optionAddress);
            IUniswapV2Router02 router_ = router;
            address underlyingToken = optionToken.getUnderlyingTokenAddress();
            uint256 outputRedeems_ = outputRedeems;
            uint256 amountBMax_ = amountBMax;
            uint256 amountBMin_ = amountBMin;
            address to_ = to;
            uint256 deadline_ = deadline;

            // Adds liquidity to Uniswap V2 Pair and returns liquidity shares to the "to" address.
            (amountA, amountB, liquidity) = addExactShortLiquidity(
                router_,
                optionToken.redeemToken(),
                underlyingToken,
                outputRedeems_,
                amountBMax_,
                amountBMin_,
                to_,
                deadline_
            );
            // check for exact liquidity provided
            assert(amountA == outputRedeems);

            uint256 remainder =
                amountBMax_ > amountB ? amountBMax_.sub(amountB) : 0;
            if (remainder > 0) {
                IERC20(underlyingToken).safeTransfer(msg.sender, remainder);
            }
        }
        return (amountA, amountB, liquidity);
    }

    ///
    /// @dev    Calls the "swapExactTokensForTokens" function on the Uniswap V2 Router 02 Contract.
    /// @notice Fails early if the address in the beginning of the path is not the token address.
    /// @param tokenAddress The address of the token to swap from.
    /// @param amountIn The quantity of longOptionTokens to swap with.
    /// @param amountOutMin The minimum quantity of tokens to receive in exchange for the tokens swapped.
    /// @param path The token addresses to trade through using their Uniswap V2 pairs.
    /// @param to The address to send the token proceeds to.
    /// @param deadline The timestamp for a trade to fail at if not successful.
    ///
    function swapExactOptionsForTokens(
        IUniswapV2Router02 router,
        address tokenAddress,
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) internal returns (uint256[] memory amounts, bool success) {
        // Fails early if the token being swapped from is not the optionToken.
        require(path[0] == tokenAddress, "ERR_PATH_OPTION_START");

        // Approve the uniswap router to be able to transfer longOptionTokens from this contract.
        IERC20(tokenAddress).approve(address(router), uint256(-1));
        // Call the Uniswap V2 function to swap longOptionTokens to quoteTokens.
        (amounts) = router.swapExactTokensForTokens(
            amountIn,
            amountOutMin,
            path,
            to,
            deadline
        );
        success = true;
    }

    /// @dev Calls UniswapV2Router02 function addLiquidity, provides exact amount of `tokenA`.
    /// @notice Assumes this contract has a current balance of `tokenA`, pulls required underlyingTokens from `msg.sender`.
    function addExactShortLiquidity(
        IUniswapV2Router02 router,
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        internal
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        // Pull `tokenB` from msg.sender to add to Uniswap V2 Pair.
        // Warning: calls into msg.sender using `safeTransferFrom`. Msg.sender is not trusted.
        IERC20(tokenB).safeTransferFrom(
            msg.sender,
            address(this),
            amountBDesired
        );
        // Approves Uniswap V2 Pair pull tokens from this contract.
        IERC20(tokenA).approve(address(router), uint256(-1));
        IERC20(tokenB).approve(address(router), uint256(-1));

        // Adds liquidity to Uniswap V2 Pair and returns liquidity shares to the "to" address.
        return
            router.addLiquidity(
                tokenA,
                tokenB,
                amountADesired,
                amountBDesired,
                amountADesired, // notice how amountAMin === amountADesired
                amountBMin,
                to,
                deadline
            );
    }

    /// @dev    Mints long + short option tokens by pulling underlyingTokens from `msg.sender`.
    /// @notice Pushes minted longOptionTokens to `msg.sender`. Keeps shortOptionTokens in this contract.
    ///         IMPORTANT: Must be used in conjuction with a function that uses the shortOptionTokens blanace.
    /// @param optionToken The option token to mint.
    /// @param quantity The amount of longOptionTokens to mint.
    function mintOptionsKeepShortOptions(IOption optionToken, uint256 quantity)
        internal
        returns (uint256, uint256)
    {
        // Pulls underlyingTokens from msg.sender to this contract.
        // Pushes underlyingTokens to option contract and mints option + redeem tokens to this contract.
        // Warning: calls into msg.sender using `safeTransferFrom`. Msg.sender is not trusted.
        (uint256 outputOptions, uint256 outputRedeems) =
            TraderLib.safeMint(optionToken, quantity, address(this));
        // Send longOptionTokens from minting option operation to msg.sender.
        IERC20(address(optionToken)).safeTransfer(msg.sender, quantity);
        return (outputOptions, outputRedeems);
    }

    /// @dev    Mints long + short option tokens using this contract's underlyingToken balance.
    /// @notice Keeps minted tokens in this contract.
    /// @param optionToken The option token to mint.
    /// @param quantity The amount of longOptionTokens to mint.
    function mintOptionsWithUnderlyingBalance(
        IOption optionToken,
        uint256 quantity
    ) internal returns (uint256, uint256) {
        address underlyingToken = optionToken.getUnderlyingTokenAddress();
        // Mint longOptionTokens using the underlyingTokens received from UniswapV2 flash swap to this contract.
        // Send underlyingTokens from this contract to the optionToken contract, then call mintOptions.
        IERC20(underlyingToken).safeTransfer(address(optionToken), quantity);
        return optionToken.mintOptions(address(this));
    }

    /// @dev    Closes options using this contract's balance of shortOptionTokens (redeem), and pulls optionTokens from `from`.
    /// @notice IMPORTANT: pulls optionTokens from `from`, an untrusted address.
    /// @param from The address to pull optionTokens from which will be burned to release underlyingTokens.
    /// @param optionToken The options that will be closed.
    /// @param quantity The quantity of optionTokens to burn.
    /// @return The quantity of underlyingTokens released.
    function closeOptionsWithShortBalance(
        address from,
        IOption optionToken,
        uint256 quantity
    ) internal returns (uint256) {
        // Close longOptionTokens using the redeemToken balance of this contract.
        IERC20(optionToken.redeemToken()).safeTransfer(
            address(optionToken),
            quantity
        );
        uint256 requiredLongOptions =
            getProportionalLongOptions(optionToken, quantity);

        // Send out the required amount of options from the `from` address.
        // WARNING: CALLS TO UNTRUSTED ADDRESS.
        IERC20(address(optionToken)).safeTransferFrom(
            from,
            address(optionToken),
            requiredLongOptions
        );

        // Close the options.
        (, , uint256 outputUnderlyings) =
            optionToken.closeOptions(address(this));
        return outputUnderlyings;
    }

    /// @dev    Removes liquidity from a uniswap pair, using this contract's balance of LP tokens.
    ///         Withdrawn tokens are sent to this contract.
    /// @notice `tokenA` is the redeemToken and `tokenB` is the underlyingToken.
    function removeLiquidity(
        IUniswapV2Router02 router,
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        uint256 deadline
    ) internal returns (uint256, uint256) {
        // Gets the Uniswap V2 Pair address for shortOptionToken (redeem) and underlyingTokens.
        // Transfers the LP tokens of the pair to this contract.
        address pair =
            IUniswapV2Factory(router.factory()).getPair(tokenA, tokenB);
        // Warning: internal call to a non-trusted address `msg.sender`.
        IERC20(pair).safeTransferFrom(msg.sender, address(this), liquidity);
        IERC20(pair).approve(address(router), uint256(-1));

        // Remove liquidity from Uniswap V2 pool to receive the reserve tokens (shortOptionTokens + UnderlyingTokens).
        (uint256 amountShortOptions, uint256 amountUnderlyingTokens) =
            router.removeLiquidity(
                tokenA,
                tokenB,
                liquidity,
                amountAMin,
                amountBMin,
                address(this),
                deadline
            );
        return (amountShortOptions, amountUnderlyingTokens);
    }

    /// @dev    Closes option tokens by buring option and redeem tokens.
    /// @notice Pulls option tokens from `msg.sender`, uses this contract's balance of redeemTokens.
    /// @param trader The Primitive V1 trader contract to handle the option closing operation.
    /// @param optionToken The option to close.
    /// @param amountShortOptions The quantity of short option tokens that will be burned to close the options.
    /// @param receiver The address that will be sent the underlyingTokens from closed options.
    function closeOptionsWithShortTokens(
        ITrader trader,
        IOption optionToken,
        uint256 amountShortOptions,
        address receiver
    )
        internal
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        // Approves trader to pull longOptionTokens and shortOptionTokens from this contract to close options.
        IERC20(address(optionToken)).approve(address(trader), uint256(-1));
        IERC20(optionToken.redeemToken()).approve(address(trader), uint256(-1));
        // Calculate equivalent quantity of redeem (short option) tokens to close the long option position.
        // longOptions = shortOptions / strikeRatio
        uint256 requiredLongOptions =
            getProportionalLongOptions(optionToken, amountShortOptions);

        // Pull the required longOptionTokens from `msg.sender` to this contract.
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(this),
            requiredLongOptions
        );

        // Trader pulls option and redeem tokens from this contract and sends them to the option contract.
        // Option and redeem tokens are then burned to release underlyingTokens.
        // UnderlyingTokens are sent to the "receiver" address.
        return trader.safeClose(optionToken, requiredLongOptions, receiver);
    }

    // ====== View ======

    /// @dev    Calculates the effective premium, denominated in underlyingTokens, to "buy" `quantity` of optionTokens.
    /// @notice UniswapV2 adds a 0.3009027% fee which is applied to the premium as 0.301%.
    ///         IMPORTANT: If the pair's reserve ratio is incorrect, there could be a 'negative' premium.
    ///         Buying negative premium options will pay out redeemTokens.
    ///         An 'incorrect' ratio occurs when the (reserves of redeemTokens / strike ratio) >= reserves of underlyingTokens.
    ///         Implicitly uses the `optionToken`'s underlying and redeem tokens for the pair.
    /// @param  router The UniswapV2Router02 contract.
    /// @param  optionToken The optionToken to get the premium cost of purchasing.
    /// @param  quantity The quantity of long option tokens that will be purchased.
    function getOpenPremium(
        IUniswapV2Router02 router,
        IOption optionToken,
        uint256 quantity
    ) internal view returns (uint256, uint256) {
        // longOptionTokens are opened by doing a swap from redeemTokens to underlyingTokens effectively.
        address[] memory path = new address[](2);
        path[0] = optionToken.redeemToken();
        path[1] = optionToken.getUnderlyingTokenAddress();

        // `quantity` of underlyingTokens are output from the swap.
        // They are used to mint options, which will mint `quantity` * quoteValue / baseValue amount of redeemTokens.
        uint256 redeemsMinted =
            getProportionalShortOptions(optionToken, quantity);

        // The loanRemainderInUnderlyings will be the amount of underlyingTokens that are needed from the original
        // transaction caller in order to pay the flash swap.
        // IMPORTANT: THIS IS EFFECTIVELY THE PREMIUM PAID IN UNDERLYINGTOKENS TO PURCHASE THE OPTIONTOKEN.
        uint256 loanRemainderInUnderlyings;

        // Economically, negativePremiumPaymentInRedeems value should always be 0.
        // In the case that we minted more redeemTokens than are needed to pay back the flash swap,
        // (short -> underlying is a positive trade), there is an effective negative premium.
        // In that case, this function will send out `negativePremiumAmount` of redeemTokens to the original caller.
        // This means the user gets to keep the extra redeemTokens for free.
        // Negative premium amount is the opposite difference of the loan remainder: (paid - flash loan amount)
        uint256 negativePremiumPaymentInRedeems;

        // Need to return tokens from the flash swap by returning shortOptionTokens and any remainder of underlyingTokens.

        // Since the borrowed amount is underlyingTokens, and we are paying back in redeemTokens,
        // we need to see how much redeemTokens must be returned for the borrowed amount.
        // We can find that value by doing the normal swap math, getAmountsIn will give us the amount
        // of redeemTokens are needed for the output amount of the flash loan.
        // IMPORTANT: amountsIn[0] is how many short tokens we need to pay back.
        // This value is most likely greater than the amount of redeemTokens minted.
        uint256[] memory amountsIn = router.getAmountsIn(quantity, path);

        uint256 redeemsRequired = amountsIn[0]; // the amountIn of redeemTokens based on the amountOut of `quantity`.
        // If redeemsMinted is greater than redeems required, there is a cost of 0, implying a negative premium.
        uint256 redeemCostRemaining =
            redeemsRequired > redeemsMinted
                ? redeemsRequired.sub(redeemsMinted)
                : 0;
        // If there is a negative premium, calculate the quantity of remaining redeemTokens after the `redeemsMinted` is spent.
        negativePremiumPaymentInRedeems = redeemsMinted > redeemsRequired
            ? redeemsMinted.sub(redeemsRequired)
            : 0;

        // In most cases, there will be an outstanding cost (assuming we minted less redeemTokens than the
        // required amountIn of redeemTokens for the swap).
        if (redeemCostRemaining > 0) {
            // The user won't want to pay back the remaining cost in redeemTokens,
            // because they borrowed underlyingTokens to mint them in the first place.
            // So instead, we get the quantity of underlyingTokens that could be paid instead.
            // We can calculate this using normal swap math.
            // getAmountsOut will return the quantity of underlyingTokens that are output,
            // based on some input of redeemTokens.
            // The input redeemTokens is the remaining redeemToken cost, and the output
            // underlyingTokens is the proportional amount of underlyingTokens.
            // amountsOut[1] is then the outstanding flash loan value denominated in underlyingTokens.
            uint256[] memory amountsOut =
                router.getAmountsOut(redeemCostRemaining, path);

            // Returning withdrawn tokens to the pair has a fee of .003 / .997 = 0.3009027% which must be applied.
            loanRemainderInUnderlyings = (
                amountsOut[1].mul(100000).add(amountsOut[1].mul(301))
            )
                .div(100000);
        }
        return (loanRemainderInUnderlyings, negativePremiumPaymentInRedeems);
    }

    /// @dev    Calculates the effective premium, denominated in underlyingTokens, to "sell" option tokens.
    /// @param  router The UniswapV2Router02 contract.
    /// @param  optionToken The optionToken to get the premium cost of purchasing.
    /// @param  quantity The quantity of short option tokens that will be closed.
    function getClosePremium(
        IUniswapV2Router02 router,
        IOption optionToken,
        uint256 quantity
    ) internal view returns (uint256, uint256) {
        // longOptionTokens are closed by doing a swap from underlyingTokens to redeemTokens.
        address[] memory path = new address[](2);
        path[0] = optionToken.getUnderlyingTokenAddress();
        path[1] = optionToken.redeemToken();
        uint256 outputUnderlyings =
            getProportionalLongOptions(optionToken, quantity);
        // The loanRemainder will be the amount of underlyingTokens that are needed from the original
        // transaction caller in order to pay the flash swap.
        uint256 loanRemainder;

        // Economically, underlyingPayout value should always be greater than 0, or this trade shouldn't be made.
        // If an underlyingPayout is greater than 0, it means that the redeemTokens borrowed are worth less than the
        // underlyingTokens received from closing the redeemToken<>optionTokens.
        // If the redeemTokens are worth more than the underlyingTokens they are entitled to,
        // then closing the redeemTokens will cost additional underlyingTokens. In this case,
        // the transaction should be reverted. Or else, the user is paying extra at the expense of
        // rebalancing the pool.
        uint256 underlyingPayout;

        // Need to return tokens from the flash swap by returning underlyingTokens.

        // Since the borrowed amount is redeemTokens, and we are paying back in underlyingTokens,
        // we need to see how much underlyingTokens must be returned for the borrowed amount.
        // We can find that value by doing the normal swap math, getAmountsIn will give us the amount
        // of underlyingTokens are needed for the output amount of the flash loan.
        // IMPORTANT: amountsIn 0 is how many underlyingTokens we need to pay back.
        // This value is most likely greater than the amount of underlyingTokens received from closing.
        uint256[] memory amountsIn = router.getAmountsIn(quantity, path);

        uint256 underlyingsRequired = amountsIn[0]; // the amountIn required of underlyingTokens based on the amountOut of flashloanQuantity
        // If outputUnderlyings (received from closing) is greater than underlyings required,
        // there is a positive payout.
        underlyingPayout = outputUnderlyings > underlyingsRequired
            ? outputUnderlyings.sub(underlyingsRequired)
            : 0;

        // If there is a negative payout, calculate the remaining cost of underlyingTokens.
        uint256 underlyingCostRemaining =
            underlyingsRequired > outputUnderlyings
                ? underlyingsRequired.sub(outputUnderlyings)
                : 0;

        // In the case that there is a negative payout (additional underlyingTokens are required),
        // get the remaining cost into the `loanRemainder` variable and also check to see
        // if a user is willing to pay the negative cost. There is no rational economic incentive for this.
        if (underlyingCostRemaining > 0) {
            loanRemainder = underlyingCostRemaining;
        }

        return (underlyingPayout, loanRemainder);
    }

    // ==== Primitive V1 =====

    /// @dev    Calculates the proportional quantity of long option tokens per short option token.
    /// @notice For each long option token, there is quoteValue / baseValue quantity of short option tokens.
    function getProportionalLongOptions(
        IOption optionToken,
        uint256 quantityShort
    ) internal view returns (uint256) {
        uint256 quantityLong =
            quantityShort.mul(optionToken.getBaseValue()).div(
                optionToken.getQuoteValue()
            );

        return quantityLong;
    }

    /// @dev    Calculates the proportional quantity of short option tokens per long option token.
    /// @notice For each short option token, there is baseValue / quoteValue quantity of long option tokens.
    function getProportionalShortOptions(
        IOption optionToken,
        uint256 quantityLong
    ) internal view returns (uint256) {
        uint256 quantityShort =
            quantityLong.mul(optionToken.getQuoteValue()).div(
                optionToken.getBaseValue()
            );

        return quantityShort;
    }

    // ==== Uniswap V2 Library =====

    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    function sortTokens(address tokenA, address tokenB)
        internal
        pure
        returns (address token0, address token1)
    {
        require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA < tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);
        require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS");
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(
        address factory,
        address tokenA,
        address tokenB
    ) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(
            uint256(
                keccak256(
                    abi.encodePacked(
                        hex"ff",
                        factory,
                        keccak256(abi.encodePacked(token0, token1)),
                        hex"e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" // init code hash
                    )
                )
            )
        );
    }
}

File 17 of 24 : SafeERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

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

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 18 of 24 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 19 of 24 : console.sol
// SPDX-License-Identifier: MIT
pragma solidity >= 0.4.22 <0.8.0;

library console {
	address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);

	function _sendLogPayload(bytes memory payload) private view {
		uint256 payloadLength = payload.length;
		address consoleAddress = CONSOLE_ADDRESS;
		assembly {
			let payloadStart := add(payload, 32)
			let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)
		}
	}

	function log() internal view {
		_sendLogPayload(abi.encodeWithSignature("log()"));
	}

	function logInt(int p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(int)", p0));
	}

	function logUint(uint p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint)", p0));
	}

	function logString(string memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
	}

	function logBool(bool p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
	}

	function logAddress(address p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
	}

	function logBytes(bytes memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes)", p0));
	}

	function logByte(byte p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(byte)", p0));
	}

	function logBytes1(bytes1 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0));
	}

	function logBytes2(bytes2 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0));
	}

	function logBytes3(bytes3 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0));
	}

	function logBytes4(bytes4 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0));
	}

	function logBytes5(bytes5 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0));
	}

	function logBytes6(bytes6 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0));
	}

	function logBytes7(bytes7 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0));
	}

	function logBytes8(bytes8 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0));
	}

	function logBytes9(bytes9 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0));
	}

	function logBytes10(bytes10 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0));
	}

	function logBytes11(bytes11 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0));
	}

	function logBytes12(bytes12 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0));
	}

	function logBytes13(bytes13 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0));
	}

	function logBytes14(bytes14 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0));
	}

	function logBytes15(bytes15 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0));
	}

	function logBytes16(bytes16 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0));
	}

	function logBytes17(bytes17 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0));
	}

	function logBytes18(bytes18 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0));
	}

	function logBytes19(bytes19 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0));
	}

	function logBytes20(bytes20 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0));
	}

	function logBytes21(bytes21 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0));
	}

	function logBytes22(bytes22 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0));
	}

	function logBytes23(bytes23 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0));
	}

	function logBytes24(bytes24 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0));
	}

	function logBytes25(bytes25 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0));
	}

	function logBytes26(bytes26 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0));
	}

	function logBytes27(bytes27 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0));
	}

	function logBytes28(bytes28 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0));
	}

	function logBytes29(bytes29 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0));
	}

	function logBytes30(bytes30 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0));
	}

	function logBytes31(bytes31 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0));
	}

	function logBytes32(bytes32 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0));
	}

	function log(uint p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint)", p0));
	}

	function log(string memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
	}

	function log(bool p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
	}

	function log(address p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
	}

	function log(uint p0, uint p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint)", p0, p1));
	}

	function log(uint p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string)", p0, p1));
	}

	function log(uint p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool)", p0, p1));
	}

	function log(uint p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address)", p0, p1));
	}

	function log(string memory p0, uint p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint)", p0, p1));
	}

	function log(string memory p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1));
	}

	function log(string memory p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1));
	}

	function log(string memory p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1));
	}

	function log(bool p0, uint p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint)", p0, p1));
	}

	function log(bool p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1));
	}

	function log(bool p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1));
	}

	function log(bool p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1));
	}

	function log(address p0, uint p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint)", p0, p1));
	}

	function log(address p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1));
	}

	function log(address p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1));
	}

	function log(address p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1));
	}

	function log(uint p0, uint p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2));
	}

	function log(uint p0, uint p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2));
	}

	function log(uint p0, uint p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2));
	}

	function log(uint p0, uint p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2));
	}

	function log(uint p0, string memory p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2));
	}

	function log(uint p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2));
	}

	function log(uint p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2));
	}

	function log(uint p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2));
	}

	function log(uint p0, bool p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2));
	}

	function log(uint p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2));
	}

	function log(uint p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2));
	}

	function log(uint p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2));
	}

	function log(uint p0, address p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2));
	}

	function log(uint p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2));
	}

	function log(uint p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2));
	}

	function log(uint p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2));
	}

	function log(string memory p0, uint p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2));
	}

	function log(string memory p0, uint p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2));
	}

	function log(string memory p0, uint p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2));
	}

	function log(string memory p0, uint p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2));
	}

	function log(string memory p0, address p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2));
	}

	function log(string memory p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2));
	}

	function log(string memory p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2));
	}

	function log(string memory p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2));
	}

	function log(bool p0, uint p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2));
	}

	function log(bool p0, uint p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2));
	}

	function log(bool p0, uint p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2));
	}

	function log(bool p0, uint p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2));
	}

	function log(bool p0, bool p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2));
	}

	function log(bool p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2));
	}

	function log(bool p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2));
	}

	function log(bool p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2));
	}

	function log(bool p0, address p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2));
	}

	function log(bool p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2));
	}

	function log(bool p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2));
	}

	function log(bool p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2));
	}

	function log(address p0, uint p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2));
	}

	function log(address p0, uint p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2));
	}

	function log(address p0, uint p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2));
	}

	function log(address p0, uint p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2));
	}

	function log(address p0, string memory p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2));
	}

	function log(address p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2));
	}

	function log(address p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2));
	}

	function log(address p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2));
	}

	function log(address p0, bool p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2));
	}

	function log(address p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2));
	}

	function log(address p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2));
	}

	function log(address p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2));
	}

	function log(address p0, address p1, uint p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2));
	}

	function log(address p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2));
	}

	function log(address p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2));
	}

	function log(address p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2));
	}

	function log(uint p0, uint p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, uint p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3));
	}

	function log(uint p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, uint p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3));
	}

}

File 20 of 24 : IWethConnector01.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.2;

// Primitive
import { IOption } from "@primitivefi/contracts/contracts/option/interfaces/IOption.sol";
import { IWETH } from "./IWETH.sol";

interface IWethConnector01 {
    function weth() external view returns (IWETH);

    function safeMintWithETH(IOption optionToken, address receiver)
        external
        payable
        returns (uint256, uint256);

    function safeExerciseWithETH(IOption optionToken, address receiver)
        external
        payable
        returns (uint256, uint256);

    function safeExerciseForETH(
        IOption optionToken,
        uint256 exerciseQuantity,
        address receiver
    ) external returns (uint256, uint256);

    function safeRedeemForETH(
        IOption optionToken,
        uint256 redeemQuantity,
        address receiver
    ) external returns (uint256);

    function safeCloseForETH(
        IOption optionToken,
        uint256 closeQuantity,
        address receiver
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function safeUnwindForETH(
        IOption optionToken,
        uint256 unwindQuantity,
        address receiver
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function getName() external pure returns (string memory);

    function getVersion() external pure returns (uint8);
}

File 21 of 24 : WethConnectorLib01.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.2;

///
/// @title   Weth Connector for bridging ether to WETH Primitive options.
/// @notice  Abstracts the interfacing with the protocol's option contract for ease-of-use.
///          Manages operations involving options with WETH as the underlying or strike asset.
///          Accepts deposits in ethers and withdraws ethers.
///          Primitive V1 WethConnectorLib01 - @primitivefi/[email protected]
/// @author  Primitive
///

// WETH Interface
import { IWETH } from "../interfaces/IWETH.sol";
// Primitive
import { IOption, IERC20 } from "@primitivefi/contracts/contracts/option/interfaces/IOption.sol";
// Open Zeppelin
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

library WethConnectorLib01 {
    using SafeERC20 for IERC20; // Reverts when `transfer` or `transferFrom` erc20 calls don't return proper data
    using SafeMath for uint256; // Reverts on math underflows/overflows

    ///
    /// @dev Checks the quantity of an operation to make sure its not zero. Fails early.
    ///
    modifier nonZero(uint256 quantity) {
        require(quantity > 0, "ERR_ZERO");
        _;
    }

    // ==== Operation Functions ====

    ///
    ///@dev Mints msg.value quantity of options and "quote" (option parameter) quantity of redeem tokens.
    ///@notice This function is for options that have WETH as the underlying asset.
    ///@param optionToken The address of the option token to mint.
    ///@param receiver The address which receives the minted option and redeem tokens.
    ///
    function safeMintWithETH(
        IWETH weth,
        IOption optionToken,
        address receiver
    ) internal nonZero(msg.value) returns (uint256, uint256) {
        // Check to make sure we are minting a WETH call option.
        address underlyingAddress = optionToken.getUnderlyingTokenAddress();
        require(address(weth) == underlyingAddress, "ERR_NOT_WETH");

        // Convert ethers into WETH, then send WETH to option contract in preparation of calling mintOptions().
        _depositEthSendWeth(weth, address(optionToken));

        // Mint the option and redeem tokens.
        (uint256 outputOptions, uint256 outputRedeems) = optionToken
            .mintOptions(receiver);

        return (outputOptions, outputRedeems);
    }

    ///
    /// @dev Swaps msg.value of strikeTokens (ethers) to underlyingTokens.
    /// Uses the strike ratio as the exchange rate. Strike ratio = base / quote.
    /// Msg.value (quote units) * base / quote = base units (underlyingTokens) to withdraw.
    /// @notice This function is for options with WETH as the strike asset.
    /// Burns option tokens, accepts ethers, and pushes out underlyingTokens.
    /// @param optionToken The address of the option contract.
    /// @param receiver The underlyingTokens are sent to the receiver address.
    ///
    function safeExerciseWithETH(
        IWETH weth,
        IOption optionToken,
        address receiver
    ) internal nonZero(msg.value) returns (uint256, uint256) {
        // Require one of the option's assets to be WETH.
        address strikeAddress = optionToken.getStrikeTokenAddress();
        require(strikeAddress == address(weth), "ERR_NOT_WETH");

        uint256 inputStrikes = msg.value;
        // Calculate quantity of optionTokens needed to burn.
        // An ether put option with strike price $300 has a "base" value of 300, and a "quote" value of 1.
        // To calculate how many options are needed to be burned, we need to cancel out the "quote" units.
        // The input strike quantity can be multiplied by the strike ratio to cancel out "quote" units.
        // 1 ether (quote units) * 300 (base units) / 1 (quote units) = 300 inputOptions
        uint256 inputOptions = inputStrikes.mul(optionToken.getBaseValue()).div(
            optionToken.getQuoteValue()
        );

        // Fail early if msg.sender does not have enough optionTokens to burn.
        require(
            IERC20(address(optionToken)).balanceOf(msg.sender) >= inputOptions,
            "ERR_BAL_OPTIONS"
        );

        // Wrap the ethers into WETH, and send the WETH to the option contract to prepare for calling exerciseOptions().
        _depositEthSendWeth(weth, address(optionToken));

        // Send the option tokens required to prepare for calling exerciseOptions().
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputOptions
        );

        // Burns the transferred option tokens, stores the strike asset (ether), and pushes underlyingTokens
        // to the receiver address.
        (inputStrikes, inputOptions) = optionToken.exerciseOptions(
            receiver,
            inputOptions,
            new bytes(0)
        );

        return (inputStrikes, inputOptions);
    }

    ///
    /// @dev Swaps strikeTokens to underlyingTokens, WETH, which is converted to ethers before withdrawn.
    /// Uses the strike ratio as the exchange rate. Strike ratio = base / quote.
    /// @notice This function is for options with WETH as the underlying asset.
    /// Burns option tokens, pulls strikeTokens, and pushes out ethers.
    /// @param optionToken The address of the option contract.
    /// @param exerciseQuantity Quantity of optionTokens to exercise.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeExerciseForETH(
        IWETH weth,
        IOption optionToken,
        uint256 exerciseQuantity,
        address receiver
    ) internal nonZero(exerciseQuantity) returns (uint256, uint256) {
        // Require one of the option's assets to be WETH.
        address underlyingAddress = optionToken.getUnderlyingTokenAddress();
        address strikeAddress = optionToken.getStrikeTokenAddress();
        require(underlyingAddress == address(weth), "ERR_NOT_WETH");

        // Fails early if msg.sender does not have enough optionTokens.
        require(
            IERC20(address(optionToken)).balanceOf(msg.sender) >=
                exerciseQuantity,
            "ERR_BAL_OPTIONS"
        );

        // Calculate quantity of strikeTokens needed to exercise quantity of optionTokens.
        uint256 inputStrikes = exerciseQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());

        // Fails early if msg.sender does not have enough strikeTokens.
        require(
            IERC20(strikeAddress).balanceOf(msg.sender) >= inputStrikes,
            "ERR_BAL_STRIKE"
        );

        // Send strikeTokens to option contract to prepare for calling exerciseOptions().
        IERC20(strikeAddress).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputStrikes
        );

        // Send the option tokens to prepare for calling exerciseOptions().
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(optionToken),
            exerciseQuantity
        );

        // Burns the optionTokens sent, stores the strikeTokens sent, and pushes underlyingTokens
        // to this contract.
        uint256 inputOptions;
        (inputStrikes, inputOptions) = optionToken.exerciseOptions(
            address(this),
            exerciseQuantity,
            new bytes(0)
        );

        // Converts the withdrawn WETH to ethers, then sends the ethers to the receiver address.
        _withdrawEthAndSend(weth, receiver, exerciseQuantity);

        return (inputStrikes, inputOptions);
    }

    ///
    /// @dev Burns redeem tokens to withdraw strike tokens (ethers) at a 1:1 ratio.
    /// @notice This function is for options that have WETH as the strike asset.
    /// Converts WETH to ethers, and withdraws ethers to the receiver address.
    /// @param optionToken The address of the option contract.
    /// @param redeemQuantity The quantity of redeemTokens to burn.
    /// @param receiver The strikeTokens (ethers) are sent to the receiver address.
    ///
    function safeRedeemForETH(
        IWETH weth,
        IOption optionToken,
        uint256 redeemQuantity,
        address receiver
    ) internal nonZero(redeemQuantity) returns (uint256) {
        // Require strikeToken to be WETH.
        address strikeAddress = optionToken.getStrikeTokenAddress();
        require(strikeAddress == address(weth), "ERR_NOT_WETH");

        // Fail early if msg.sender does not have enough redeemTokens.
        address redeemAddress = optionToken.redeemToken();
        require(
            IERC20(redeemAddress).balanceOf(msg.sender) >= redeemQuantity,
            "ERR_BAL_REDEEM"
        );

        // Send redeemTokens to option contract in preparation for calling redeemStrikeTokens().
        IERC20(redeemAddress).safeTransferFrom(
            msg.sender,
            address(optionToken),
            redeemQuantity
        );

        // If options have not been exercised, there will be no strikeTokens to redeem, causing a revert.
        // Burns the redeem tokens that were sent to the contract, and withdraws the same quantity of WETH.
        // Sends the withdrawn WETH to this contract, so that it can be unwrapped prior to being sent to receiver.
        uint256 inputRedeems = optionToken.redeemStrikeTokens(address(this));

        // Unwrap the redeemed WETH and then send the ethers to the receiver.
        _withdrawEthAndSend(weth, receiver, redeemQuantity);

        return inputRedeems;
    }

    ///
    /// @dev Burn optionTokens and redeemTokens to withdraw underlyingTokens (ethers).
    /// @notice This function is for options with WETH as the underlying asset.
    /// WETH underlyingTokens are converted to ethers before being sent to receiver.
    /// The redeemTokens to burn is equal to the optionTokens * strike ratio.
    /// inputOptions = inputRedeems / strike ratio = outUnderlyings
    /// @param optionToken The address of the option contract.
    /// @param closeQuantity Quantity of optionTokens to burn and an input to calculate how many redeems to burn.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeCloseForETH(
        IWETH weth,
        IOption optionToken,
        uint256 closeQuantity,
        address receiver
    )
        internal
        nonZero(closeQuantity)
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        // Require the optionToken to have WETH as the underlying asset.
        address underlyingAddress = optionToken.getUnderlyingTokenAddress();
        require(address(weth) == underlyingAddress, "ERR_NOT_WETH");

        // Fail early if msg.sender does not have enough optionTokens to burn.
        require(
            IERC20(address(optionToken)).balanceOf(msg.sender) >= closeQuantity,
            "ERR_BAL_OPTIONS"
        );

        // Calculate the quantity of redeemTokens that need to be burned.
        uint256 inputRedeems = closeQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());

        // Fail early is msg.sender does not have enough redeemTokens to burn.
        require(
            IERC20(optionToken.redeemToken()).balanceOf(msg.sender) >=
                inputRedeems,
            "ERR_BAL_REDEEM"
        );

        // Send redeem and option tokens in preparation of calling closeOptions().
        IERC20(optionToken.redeemToken()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputRedeems
        );
        IERC20(address(optionToken)).safeTransferFrom(
            msg.sender,
            address(optionToken),
            closeQuantity
        );

        // Call the closeOptions() function to burn option and redeem tokens and withdraw underlyingTokens.
        uint256 inputOptions;
        uint256 outUnderlyings;
        (inputRedeems, inputOptions, outUnderlyings) = optionToken.closeOptions(
            address(this)
        );

        // Since underlyngTokens are WETH, unwrap them then send the ethers to the receiver.
        _withdrawEthAndSend(weth, receiver, closeQuantity);

        return (inputRedeems, inputOptions, outUnderlyings);
    }

    ///
    /// @dev Burn redeemTokens to withdraw underlyingTokens (ethers) from expired options.
    /// This function is for options with WETH as the underlying asset.
    /// The underlyingTokens are WETH, which are converted to ethers prior to being sent to receiver.
    /// @param optionToken The address of the option contract.
    /// @param unwindQuantity Quantity of underlyingTokens (ethers) to withdraw.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeUnwindForETH(
        IWETH weth,
        IOption optionToken,
        uint256 unwindQuantity,
        address receiver
    )
        internal
        nonZero(unwindQuantity)
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        // Require the optionToken to have WETH as the underlying asset.
        address underlyingAddress = optionToken.getUnderlyingTokenAddress();
        require(address(weth) == underlyingAddress, "ERR_NOT_WETH");

        // If the option is not expired, fail early.
        // solhint-disable-next-line not-rely-on-time
        require(optionToken.getExpiryTime() < now, "ERR_NOT_EXPIRED");

        // Calculate the quantity of redeemTokens that need to be burned.
        uint256 inputRedeems = unwindQuantity
            .mul(optionToken.getQuoteValue())
            .div(optionToken.getBaseValue());

        // Fail early if msg.sender does not have enough redeemTokens to burn.
        require(
            IERC20(optionToken.redeemToken()).balanceOf(msg.sender) >=
                inputRedeems,
            "ERR_BAL_REDEEM"
        );

        // Send redeem in preparation of calling closeOptions().
        IERC20(optionToken.redeemToken()).safeTransferFrom(
            msg.sender,
            address(optionToken),
            inputRedeems
        );

        // Call the closeOptions() function to burn redeem tokens and withdraw underlyingTokens.
        uint256 inputOptions;
        uint256 outUnderlyings;
        (inputRedeems, inputOptions, outUnderlyings) = optionToken.closeOptions(
            address(this)
        );

        // Since underlyngTokens are WETH, unwrap them to ethers then send the ethers to the receiver.
        _withdrawEthAndSend(weth, receiver, unwindQuantity);
        return (inputRedeems, inputOptions, outUnderlyings);
    }

    // ==== WETH Operations ====

    ///
    /// @dev Deposits msg.value of ethers into WETH contract. Then sends WETH to "to".
    /// @param to The address to send WETH ERC-20 tokens to.
    ///
    function _depositEthSendWeth(IWETH weth, address to) internal {
        // Deposit the ethers received from msg.value into the WETH contract.
        weth.deposit.value(msg.value)();

        // Send WETH.
        weth.transfer(to, msg.value);
    }

    ///
    /// @dev Unwraps WETH to withrdaw ethers, which are then sent to the "to" address.
    /// @param to The address to send withdrawn ethers to.
    /// @param quantity The quantity of WETH to unwrap.
    ///
    function _withdrawEthAndSend(
        IWETH weth,
        address to,
        uint256 quantity
    ) internal {
        // Withdraw ethers with weth.
        weth.withdraw(quantity);

        // Send ether.
        (bool success, ) = to.call.value(quantity)("");

        // Revert is call is unsuccessful.
        require(success, "ERR_SENDING_ETHER");
    }
}

File 22 of 24 : IWETH.sol
pragma solidity >=0.5.0;

interface IWETH {
    function deposit() external payable;

    function transfer(address to, uint256 value) external returns (bool);

    function withdraw(uint256) external;
}

File 23 of 24 : WethConnector01.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.6.2;

///
/// @title   Weth Connector for bridging ether to WETH Primitive options.
/// @notice  Abstracts the interfacing with the protocol's option contract for ease-of-use.
///          Manages operations involving options with WETH as the underlying or strike asset.
///          Accepts deposits in ethers and withdraws ethers.
///          Primitive V1 WethConnector01 - @primitivefi/[email protected]
/// @author  Primitive
///

// Primitive
import { IWethConnector01, IOption, IWETH } from "./interfaces/IWethConnector01.sol";
import { WethConnectorLib01, IERC20 } from "./libraries/WethConnectorLib01.sol";
// Open Zeppelin
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import {
    ReentrancyGuard
} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract WethConnector01 is IWethConnector01, ReentrancyGuard {
    using SafeERC20 for IERC20; // Reverts when `transfer` or `transferFrom` erc20 calls don't return proper data
    using SafeMath for uint256; // Reverts on math underflows/overflows

    IWETH public override weth;

    event WethConnectorMint(
        address indexed from,
        address indexed option,
        uint256 outputOptions,
        uint256 outputRedeems
    );
    event WethConnectorExercise(
        address indexed from,
        address indexed option,
        uint256 outUnderlyings,
        uint256 inStrikes
    );
    event WethConnectorRedeem(
        address indexed from,
        address indexed option,
        uint256 inRedeems
    );
    event WethConnectorClose(
        address indexed from,
        address indexed option,
        uint256 inOptions
    );

    event WethConnectorUnwind(
        address indexed from,
        address indexed option,
        uint256 inOptions
    );

    ///
    /// @dev Checks the quantity of an operation to make sure its not zero. Fails early.
    ///
    modifier nonZero(uint256 quantity) {
        require(quantity > 0, "ERR_ZERO");
        _;
    }

    ///
    /// @dev Since the WethConnector contract is responsible for converting between ethers and WETH,
    /// the contract is initialized with the address for WETH.
    ///
    constructor(address payable _weth) public {
        weth = IWETH(_weth);
    }

    ///
    /// @dev If ether is sent to this contract through a normal transaction, it will fail, unless
    /// it was the WETH contract who sent it.
    ///
    receive() external payable {
        assert(msg.sender == address(weth));
    }

    // ==== Operation Functions ====

    ///
    /// @dev Mints msg.value quantity of options and "quote" (option parameter) quantity of redeem tokens.
    /// @notice This function is for options that have WETH as the underlying asset.
    /// @param optionToken The address of the option token to mint.
    /// @param receiver The address which receives the minted option and redeem tokens.
    ///
    function safeMintWithETH(IOption optionToken, address receiver)
        external
        override
        payable
        nonReentrant
        nonZero(msg.value)
        returns (uint256, uint256)
    {
        (uint256 outputOptions, uint256 outputRedeems) = WethConnectorLib01
            .safeMintWithETH(weth, optionToken, receiver);
        emit WethConnectorMint(
            msg.sender,
            address(optionToken),
            outputOptions,
            outputRedeems
        );

        return (outputOptions, outputRedeems);
    }

    ///
    /// @dev Swaps msg.value of strikeTokens (ethers) to underlyingTokens.
    /// Uses the strike ratio as the exchange rate. Strike ratio = base / quote.
    /// Msg.value (quote units) * base / quote = base units (underlyingTokens) to withdraw.
    /// @notice This function is for options with WETH as the strike asset.
    /// Burns option tokens, accepts ethers, and pushes out underlyingTokens.
    /// @param optionToken The address of the option contract.
    /// @param receiver The underlyingTokens are sent to the receiver address.
    ///
    function safeExerciseWithETH(IOption optionToken, address receiver)
        external
        override
        payable
        nonReentrant
        nonZero(msg.value)
        returns (uint256, uint256)
    {
        (uint256 inputStrikes, uint256 inputOptions) = WethConnectorLib01
            .safeExerciseWithETH(weth, optionToken, receiver);

        emit WethConnectorExercise(
            msg.sender,
            address(optionToken),
            inputOptions,
            inputStrikes
        );

        return (inputStrikes, inputOptions);
    }

    ///
    /// @dev Swaps strikeTokens to underlyingTokens, WETH, which is converted to ethers before withdrawn.
    /// Uses the strike ratio as the exchange rate. Strike ratio = base / quote.
    /// @notice This function is for options with WETH as the underlying asset.
    /// Burns option tokens, pulls strikeTokens, and pushes out ethers.
    /// @param optionToken The address of the option contract.
    /// @param exerciseQuantity Quantity of optionTokens to exercise.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeExerciseForETH(
        IOption optionToken,
        uint256 exerciseQuantity,
        address receiver
    )
        external
        override
        nonReentrant
        nonZero(exerciseQuantity)
        returns (uint256, uint256)
    {
        (uint256 inputStrikes, uint256 inputOptions) = WethConnectorLib01
            .safeExerciseForETH(weth, optionToken, exerciseQuantity, receiver);

        emit WethConnectorExercise(
            msg.sender,
            address(optionToken),
            exerciseQuantity,
            inputStrikes
        );

        return (inputStrikes, inputOptions);
    }

    ///
    /// @dev Burns redeem tokens to withdraw strike tokens (ethers) at a 1:1 ratio.
    /// @notice This function is for options that have WETH as the strike asset.
    /// Converts WETH to ethers, and withdraws ethers to the receiver address.
    /// @param optionToken The address of the option contract.
    /// @param redeemQuantity The quantity of redeemTokens to burn.
    /// @param receiver The strikeTokens (ethers) are sent to the receiver address.
    ///
    function safeRedeemForETH(
        IOption optionToken,
        uint256 redeemQuantity,
        address receiver
    ) external override nonReentrant nonZero(redeemQuantity) returns (uint256) {
        uint256 inputRedeems = WethConnectorLib01.safeRedeemForETH(
            weth,
            optionToken,
            redeemQuantity,
            receiver
        );

        emit WethConnectorRedeem(
            msg.sender,
            address(optionToken),
            inputRedeems
        );
        return inputRedeems;
    }

    ///
    /// @dev Burn optionTokens and redeemTokens to withdraw underlyingTokens (ethers).
    /// @notice This function is for options with WETH as the underlying asset.
    /// WETH underlyingTokens are converted to ethers before being sent to receiver.
    /// The redeemTokens to burn is equal to the optionTokens * strike ratio.
    /// inputOptions = inputRedeems / strike ratio = outUnderlyings
    /// @param optionToken The address of the option contract.
    /// @param closeQuantity Quantity of optionTokens to burn and an input to calculate how many redeems to burn.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeCloseForETH(
        IOption optionToken,
        uint256 closeQuantity,
        address receiver
    )
        external
        override
        nonReentrant
        nonZero(closeQuantity)
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        (
            uint256 inputRedeems,
            uint256 inputOptions,
            uint256 outUnderlyings
        ) = WethConnectorLib01.safeCloseForETH(
            weth,
            optionToken,
            closeQuantity,
            receiver
        );
        emit WethConnectorClose(msg.sender, address(optionToken), inputOptions);
        return (inputRedeems, inputOptions, outUnderlyings);
    }

    ///
    /// @dev Burn redeemTokens to withdraw underlyingTokens (ethers) from expired options.
    /// This function is for options with WETH as the underlying asset.
    /// The underlyingTokens are WETH, which are converted to ethers prior to being sent to receiver.
    /// @param optionToken The address of the option contract.
    /// @param unwindQuantity Quantity of underlyingTokens (ethers) to withdraw.
    /// @param receiver The underlyingTokens (ethers) are sent to the receiver address.
    ///
    function safeUnwindForETH(
        IOption optionToken,
        uint256 unwindQuantity,
        address receiver
    )
        external
        override
        nonReentrant
        nonZero(unwindQuantity)
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        (
            uint256 inputRedeems,
            uint256 inputOptions,
            uint256 outUnderlyings
        ) = WethConnectorLib01.safeUnwindForETH(
            weth,
            optionToken,
            unwindQuantity,
            receiver
        );

        emit WethConnectorUnwind(
            msg.sender,
            address(optionToken),
            inputOptions
        );
        return (inputRedeems, inputOptions, outUnderlyings);
    }

    // ==== View ====

    /// @dev Gets the name of the contract.
    function getName() external override pure returns (string memory) {
        return "PrimitiveV1WethConnector01";
    }

    /// @dev Gets the version of the contract.
    function getVersion() external override pure returns (uint8) {
        return uint8(1);
    }
}

File 24 of 24 : TestERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.2;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) public ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
    }

    /**
     * @dev Function to mint tokens
     * @param to The address that will receive the minted tokens.
     * @param value The amount of tokens to mint.
     * @return A boolean that indicates if the operation was successful.
     */
    function mint(address to, uint256 value) public returns (bool) {
        _mint(to, value);
        return true;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"router_","type":"address"},{"internalType":"address","name":"factory_","type":"address"},{"internalType":"address","name":"trader_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"payout","type":"uint256"}],"name":"FlashClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"premium","type":"uint256"}],"name":"FlashOpened","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"WroteOption","type":"event"},{"inputs":[{"internalType":"address","name":"optionAddress","type":"address"},{"internalType":"uint256","name":"quantityOptions","type":"uint256"},{"internalType":"uint256","name":"amountBMax","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addShortLiquidityWithUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"amountRedeems","type":"uint256"},{"internalType":"uint256","name":"minPayout","type":"uint256"}],"name":"closeFlashLong","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"optionAddress","type":"address"},{"internalType":"address","name":"otherToken","type":"address"}],"name":"deployUniswapMarket","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract IUniswapV2Factory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pairAddress","type":"address"},{"internalType":"address","name":"optionAddress","type":"address"},{"internalType":"uint256","name":"flashLoanQuantity","type":"uint256"},{"internalType":"uint256","name":"minPayout","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"flashCloseLongOptionsThenSwap","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pairAddress","type":"address"},{"internalType":"address","name":"optionAddress","type":"address"},{"internalType":"uint256","name":"flashLoanQuantity","type":"uint256"},{"internalType":"uint256","name":"maxPremium","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"flashMintShortOptionsThenSwap","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"quantityShort","type":"uint256"}],"name":"getClosePremium","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"quantityLong","type":"uint256"}],"name":"getOpenPremium","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"}],"name":"getUniswapMarketForTokens","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVersion","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"writeQuantity","type":"uint256"},{"internalType":"uint256","name":"minPayout","type":"uint256"}],"name":"mintOptionsThenFlashCloseLong","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"mintShortOptionsThenSwapToTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOption","name":"optionToken","type":"address"},{"internalType":"uint256","name":"amountOptions","type":"uint256"},{"internalType":"uint256","name":"maxPremium","type":"uint256"}],"name":"openFlashLong","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"optionAddress","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeShortLiquidityThenCloseOptions","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"contract IUniswapV2Router02","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trader","outputs":[{"internalType":"contract ITrader","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV2Call","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60806040523480156200001157600080fd5b506040516200456138038062004561833981810160405260608110156200003757600080fd5b508051602082015160409092015160016000556003549192916001600160a01b0316156200009e576040805162461bcd60e51b815260206004820152600f60248201526e11549497d253925512505312569151608a1b604482015290519081900360640190fd5b6002546001600160a01b031615620000ef576040805162461bcd60e51b815260206004820152600f60248201526e11549497d253925512505312569151608a1b604482015290519081900360640190fd5b6001546001600160a01b03161562000140576040805162461bcd60e51b815260206004820152600f60248201526e11549497d253925512505312569151608a1b604482015290519081900360640190fd5b600380546001600160a01b038086166001600160a01b03199283161790925560028054858416908316179055600180549284169290911691909117905560405133907f908408e307fc569b417f6cbec5d5a06f44a0a505ac0479b47d421a4b2fd6a1e690600090a25050506143a680620001bb6000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c8063904f91dc116100a2578063c45a015511610071578063c45a015514610636578063cd0455241461063e578063dd2c704e14610670578063f887ea401461069c578063fd52c6bb146106a457610116565b8063904f91dc1461046e5780639245d5831461049a57806395f0ae76146104cc578063b17d05701461056557610116565b80631758078b116100e95780631758078b1461026a57806317d7de7c1461028e5780632e16cab31461030b57806360060ccd1461036f57806369bd26cd1461044057610116565b80630398a6131461011b57806307f24906146101615780630d8e6e2c146101c057806310d1e85c146101de575b600080fd5b61014d6004803603606081101561013157600080fd5b506001600160a01b0381351690602081013590604001356106d2565b604080519115158252519081900360200190f35b6101a7600480360360c081101561017757600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135610775565b6040805192835260208301919091528051918290030190f35b6101c8610a55565b6040805160ff9092168252519081900360200190f35b610268600480360360808110156101f457600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561022a57600080fd5b82018360208201111561023c57600080fd5b803590602001918460018302840111600160201b8311171561025d57600080fd5b509092509050610a5a565b005b610272610cad565b604080516001600160a01b039092168252519081900360200190f35b610296610cbc565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102d05781810151838201526020016102b8565b50505050905090810190601f1680156102fd5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610351600480360360c081101561032157600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135610cf3565b60408051938452602084019290925282820152519081900360600190f35b6101a7600480360360c081101561038557600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b8111156103c457600080fd5b8201836020820111156103d657600080fd5b803590602001918460208302840111600160201b831117156103f757600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505090356001600160a01b03169150610d759050565b6102726004803603604081101561045657600080fd5b506001600160a01b0381358116916020013516610dea565b6101a76004803603604081101561048457600080fd5b506001600160a01b038135169060200135610e78565b61014d600480360360608110156104b057600080fd5b506001600160a01b038135169060208101359060400135610ea0565b61014d600480360360c08110156104e257600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561051857600080fd5b82018360208201111561052a57600080fd5b803590602001918460208302840111600160201b8311171561054b57600080fd5b91935091506001600160a01b0381351690602001356113f5565b6101a7600480360360c081101561057b57600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b8111156105ba57600080fd5b8201836020820111156105cc57600080fd5b803590602001918460208302840111600160201b831117156105ed57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550505090356001600160a01b031691506114a69050565b61027261151b565b61014d6004803603606081101561065457600080fd5b506001600160a01b03813516906020810135906040013561152a565b6101a76004803603604081101561068657600080fd5b506001600160a01b038135169060200135611937565b610272611953565b610272600480360360408110156106ba57600080fd5b506001600160a01b0381358116916020013516611962565b6000806106e0858533611a49565b91505060006106f0868386610ea0565b905080610736576040805162461bcd60e51b815260206004820152600f60248201526e4552525f464c4153485f434c4f534560881b604482015290519081900360640190fd5b60408051868152905133917f4150f3753a230033657cb8a559a7d2a6a6110a7326d898ac1f07de6ae5c6785b919081900360200190a295945050505050565b600080600260005414156107be576040805162461bcd60e51b815260206004820152601f6024820152600080516020614241833981519152604482015290519081900360640190fd5b60026000819055506000886001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b15801561080157600080fd5b505afa158015610815573d6000803e3d6000fd5b505050506040513d602081101561082b57600080fd5b505160408051632207afe960e11b815290519192506000916001600160a01b038c169163440f5fd2916004808301926020929190829003018186803b15801561087357600080fd5b505afa158015610887573d6000803e3d6000fd5b505050506040513d602081101561089d57600080fd5b5051604080516370a0823160e01b815230600482015290519192506000916001600160a01b038516916370a08231916024808301926020929190829003018186803b1580156108eb57600080fd5b505afa1580156108ff573d6000803e3d6000fd5b505050506040513d602081101561091557600080fd5b5051600354909150600090819061093a906001600160a01b031686868f8f8f8e611b9a565b600154919350915060009061095a906001600160a01b03168f858d611dda565b925050506109f084876001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156109b857600080fd5b505afa1580156109cc573d6000803e3d6000fd5b505050506040513d60208110156109e257600080fd5b50519063ffffffff61202616565b93508315610a1257610a126001600160a01b0387168b8663ffffffff61206816565b610a2c6001600160a01b0386168b8463ffffffff61206816565b610a3c828263ffffffff6120bf16565b60016000559e939d50929b505050505050505050505050565b600390565b6000336001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b158015610a9557600080fd5b505afa158015610aa9573d6000803e3d6000fd5b505050506040513d6020811015610abf57600080fd5b50516040805163d21220a760e01b81529051919250600091339163d21220a7916004808301926020929190829003018186803b158015610afe57600080fd5b505afa158015610b12573d6000803e3d6000fd5b505050506040513d6020811015610b2857600080fd5b50516002546040805163e6a4390560e01b81526001600160a01b0386811660048301528085166024830152915193945091169163e6a4390591604480820192602092909190829003018186803b158015610b8157600080fd5b505afa158015610b95573d6000803e3d6000fd5b505050506040513d6020811015610bab57600080fd5b50516001600160a01b03163314610bbe57fe5b60006060306001600160a01b03168686604051808383808284376040519201945060009350909150508083038183865af19150503d8060008114610c1e576040519150601f19603f3d011682016040523d82523d6000602084013e610c23565b606091505b5091509150818015610c51575080511580610c515750808060200190516020811015610c4e57600080fd5b50515b610ca2576040805162461bcd60e51b815260206004820152601760248201527f4552525f554e495357415056325f43414c4c5f4641494c000000000000000000604482015290519081900360640190fd5b505050505050505050565b6001546001600160a01b031681565b60408051808201909152601d81527f5072696d69746976655631556e6973776170436f6e6e6563746f723033000000602082015290565b600080600060026000541415610d3e576040805162461bcd60e51b815260206004820152601f6024820152600080516020614241833981519152604482015290519081900360640190fd5b6002600055600354610d5e906001600160a01b03168a8a8a8a8a8a612119565b6001600055919b909a509098509650505050505050565b600354600090819081908190610d99906001600160a01b03168b8b8b8b8b8b6122b2565b6040805183815260208101839052815193955091935033927f3725145ca6a6d192a761e3b6dbcf42cdc13147dd45a65fd1b29737ac584b557e9281900390910190a290999098509650505050505050565b600254604080516364e329cb60e11b81526001600160a01b038581166004830152848116602483015291516000938493169163c9c6539691604480830192602092919082900301818787803b158015610e4257600080fd5b505af1158015610e56573d6000803e3d6000fd5b505050506040513d6020811015610e6c57600080fd5b50519150505b92915050565b6003546000908190610e94906001600160a01b0316858561272a565b915091505b9250929050565b600060026000541415610ee8576040805162461bcd60e51b815260206004820152601f6024820152600080516020614241833981519152604482015290519081900360640190fd5b60026000819055506000846001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f2b57600080fd5b505afa158015610f3f573d6000803e3d6000fd5b505050506040513d6020811015610f5557600080fd5b505160408051632207afe960e11b815290519192506000916001600160a01b0388169163440f5fd2916004808301926020929190829003018186803b158015610f9d57600080fd5b505afa158015610fb1573d6000803e3d6000fd5b505050506040513d6020811015610fc757600080fd5b50516002546040805163e6a4390560e01b81526001600160a01b03868116600483015280851660248301529151939450600093919092169163e6a43905916044808301926020929190829003018186803b15801561102457600080fd5b505afa158015611038573d6000803e3d6000fd5b505050506040513d602081101561104e57600080fd5b5051604080516002808252606080830184529394509091602083019080388339019050509050828160008151811061108257fe5b60200260200101906001600160a01b031690816001600160a01b03168152505083816001815181106110b057fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506000829050600060405180608001604052806050815260200161428660509139805190602001209050606081858c8c8c883360405160240180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200180602001836001600160a01b03166001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b83811015611199578181015183820152602001611181565b50505050905001975050505050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b03838183161783525050505090506000876001600160a01b0316846001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b15801561122357600080fd5b505afa158015611237573d6000803e3d6000fd5b505050506040513d602081101561124d57600080fd5b50516001600160a01b031614611264576000611266565b8a5b90506000886001600160a01b0316856001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ad57600080fd5b505afa1580156112c1573d6000803e3d6000fd5b505050506040513d60208110156112d757600080fd5b50516001600160a01b0316146112ed578b6112f0565b60005b9050846001600160a01b031663022c0d9f838330876040518563ffffffff1660e01b815260040180858152602001848152602001836001600160a01b03166001600160a01b0316815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561137557818101518382015260200161135d565b50505050905090810190601f1680156113a25780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156113c457600080fd5b505af11580156113d8573d6000803e3d6000fd5b505050506001995050505050505050505060016000559392505050565b60006002600054141561143d576040805162461bcd60e51b815260206004820152601f6024820152600080516020614241833981519152604482015290519081900360640190fd5b600260009081556003546040805160208089028281018201909352888252611494936001600160a01b0316928d928d928d928d918d9182918501908490808284376000920191909152508c92508b9150612a549050565b60016000559998505050505050505050565b6003546000908190819081906114ca906001600160a01b03168b8b8b8b8b8b612b37565b6040805183815260208101839052815193955091935033927ff17aa7749e04732ce7bde5ee88376144e6c5669bd4d8c80358119eada62a1d009281900390910190a290999098509650505050505050565b6002546001600160a01b031681565b600060026000541415611572576040805162461bcd60e51b815260206004820152601f6024820152600080516020614241833981519152604482015290519081900360640190fd5b60026000819055506000846001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b1580156115b557600080fd5b505afa1580156115c9573d6000803e3d6000fd5b505050506040513d60208110156115df57600080fd5b505160408051632207afe960e11b815290519192506000916001600160a01b0388169163440f5fd2916004808301926020929190829003018186803b15801561162757600080fd5b505afa15801561163b573d6000803e3d6000fd5b505050506040513d602081101561165157600080fd5b50516002546040805163e6a4390560e01b81526001600160a01b03868116600483015280851660248301529151939450600093919092169163e6a43905916044808301926020929190829003018186803b1580156116ae57600080fd5b505afa1580156116c2573d6000803e3d6000fd5b505050506040513d60208110156116d857600080fd5b5051604080516002808252606080830184529394509091602083019080388339019050509050838160008151811061170c57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050828160018151811061173a57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050600082905060006040518060800160405280605081526020016142f760509139805190602001209050606081858c8c8c883360405160240180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200180602001836001600160a01b03166001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b8381101561182357818101518382015260200161180b565b50505050905001975050505050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b03838183161783525050505090506000866001600160a01b0316846001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b1580156118ad57600080fd5b505afa1580156118c1573d6000803e3d6000fd5b505050506040513d60208110156118d757600080fd5b50516001600160a01b0316146118ee5760006118f0565b8a5b90506000876001600160a01b0316856001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ad57600080fd5b6003546000908190610e94906001600160a01b03168585612f2b565b6003546001600160a01b031681565b6002546040805163e6a4390560e01b81526001600160a01b038581166004830152848116602483015291516000938493169163e6a43905916044808301926020929190829003018186803b1580156119b957600080fd5b505afa1580156119cd573d6000803e3d6000fd5b505050506040513d60208110156119e357600080fd5b505190506001600160a01b038116611a42576040805162461bcd60e51b815260206004820152601760248201527f4552525f504149525f444f45535f4e4f545f4558495354000000000000000000604482015290519081900360640190fd5b9392505050565b60008060008411611a8c576040805162461bcd60e51b81526020600482015260086024820152674552525f5a45524f60c01b604482015290519081900360640190fd5b611b0e338686886001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b158015611acb57600080fd5b505afa158015611adf573d6000803e3d6000fd5b505050506040513d6020811015611af557600080fd5b50516001600160a01b031692919063ffffffff61340916565b60408051633ea6b5f160e21b81526001600160a01b03858116600483015282516000938493928a169263fa9ad7c4926024808301939282900301818787803b158015611b5957600080fd5b505af1158015611b6d573d6000803e3d6000fd5b505050506040513d6040811015611b8357600080fd5b508051602090910151909890975095505050505050565b6000806000896001600160a01b031663c45a01556040518163ffffffff1660e01b815260040160206040518083038186803b158015611bd857600080fd5b505afa158015611bec573d6000803e3d6000fd5b505050506040513d6020811015611c0257600080fd5b50516040805163e6a4390560e01b81526001600160a01b038c811660048301528b811660248301529151919092169163e6a43905916044808301926020929190829003018186803b158015611c5657600080fd5b505afa158015611c6a573d6000803e3d6000fd5b505050506040513d6020811015611c8057600080fd5b50519050611c9f6001600160a01b03821633308a63ffffffff61340916565b6040805163095ea7b360e01b81526001600160a01b038c81166004830152600019602483015291519183169163095ea7b3916044808201926020929091908290030181600087803b158015611cf357600080fd5b505af1158015611d07573d6000803e3d6000fd5b505050506040513d6020811015611d1d57600080fd5b505060408051635d5155ef60e11b81526001600160a01b038b811660048301528a81166024830152604482018a905260648201899052608482018890523060a483015260c4820187905282516000938493928f169263baa2abde9260e4808301939282900301818787803b158015611d9457600080fd5b505af1158015611da8573d6000803e3d6000fd5b505050506040513d6040811015611dbe57600080fd5b508051602090910151909d909c509a5050505050505050505050565b6000806000856001600160a01b031663095ea7b3886000196040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015611e4157600080fd5b505af1158015611e55573d6000803e3d6000fd5b505050506040513d6020811015611e6b57600080fd5b505060408051632f310bad60e01b815290516001600160a01b03881691632f310bad916004808301926020929190829003018186803b158015611ead57600080fd5b505afa158015611ec1573d6000803e3d6000fd5b505050506040513d6020811015611ed757600080fd5b50516040805163095ea7b360e01b81526001600160a01b038a8116600483015260001960248301529151919092169163095ea7b39160448083019260209291908290030181600087803b158015611f2d57600080fd5b505af1158015611f41573d6000803e3d6000fd5b505050506040513d6020811015611f5757600080fd5b5060009050611f668787613469565b9050611f836001600160a01b03881633308463ffffffff61340916565b6040805163b8a6bb3b60e01b81526001600160a01b0389811660048301526024820184905287811660448301529151918a169163b8a6bb3b916064808201926060929091908290030181600087803b158015611fde57600080fd5b505af1158015611ff2573d6000803e3d6000fd5b505050506040513d606081101561200857600080fd5b5080516020820151604090920151909a919950975095505050505050565b6000611a4283836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613559565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526120ba9084906135f0565b505050565b600082820183811015611a42576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b600080600080600080600061212e8d8d6136a1565b91505060008d905060008f90506000826001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b15801561217657600080fd5b505afa15801561218a573d6000803e3d6000fd5b505050506040513d60208110156121a057600080fd5b81019080805190602001909291905050509050600084905060008f905060008f905060008f905060008f905061224087896001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b15801561220957600080fd5b505afa15801561221d573d6000803e3d6000fd5b505050506040513d602081101561223357600080fd5b50518888888888886136dc565b919d509b509950888c1461225057fe5b60008b8511612260576000612270565b612270858d63ffffffff61202616565b90508015612292576122926001600160a01b038816338363ffffffff61206816565b509a9d50989b509699505050505050505050509750975097945050505050565b6000803330146122f8576040805162461bcd60e51b815260206004820152600c60248201526b22a9292fa727aa2fa9a2a62360a11b604482015290519081900360640190fd5b6001600160a01b038316612349576040805162461bcd60e51b81526020600482015260136024820152724552525f544f5f414444524553535f5a45524f60681b604482015290519081900360640190fd5b6001600160a01b03831633141561239b576040805162461bcd60e51b815260206004820152601160248201527022a9292faa27afa6a9a3afa9a2a72222a960791b604482015290519081900360640190fd5b876001600160a01b031661243c8a6001600160a01b031663c45a01556040518163ffffffff1660e01b815260040160206040518083038186803b1580156123e157600080fd5b505afa1580156123f5573d6000803e3d6000fd5b505050506040513d602081101561240b57600080fd5b50518651879060009061241a57fe5b60200260200101518760018151811061242f57fe5b60200260200101516138c5565b6001600160a01b03161461248a576040805162461bcd60e51b815260206004820152601060248201526f22a9292fa4a72b20a624a22fa820a4a960811b604482015290519081900360640190fd5b6000876001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b1580156124c557600080fd5b505afa1580156124d9573d6000803e3d6000fd5b505050506040513d60208110156124ef57600080fd5b505160408051632f310bad60e01b815290519192506000916001600160a01b038b1691632f310bad916004808301926020929190829003018186803b15801561253757600080fd5b505afa15801561254b573d6000803e3d6000fd5b505050506040513d602081101561256157600080fd5b505186519091506001600160a01b038216908790600190811061258057fe5b60200260200101516001600160a01b0316146125e3576040805162461bcd60e51b815260206004820152601760248201527f4552525f454e445f504154485f4e4f545f52454445454d000000000000000000604482015290519081900360640190fd5b60006125f0868b8b613985565b90506000806126008e8d8d61272a565b9092509050811561261e5761261b838363ffffffff61202616565b92505b821561263e5761263e6001600160a01b0386168e8563ffffffff61206816565b80156126aa57818a111561268f576040805162461bcd60e51b815260206004820152601360248201527211549497d39151d05512559157d4105653d555606a1b604482015290519081900360640190fd5b6126aa6001600160a01b038616898f8463ffffffff61340916565b811561271757898210156126fd576040805162461bcd60e51b815260206004820152601560248201527422a9292fa82922a6a4aaa6afaaa72222a92fa6a4a760591b604482015290519081900360640190fd5b6127176001600160a01b038616898463ffffffff61206816565b50909c909b509950505050505050505050565b604080516002808252606080830184526000938493919290602083019080388339019050509050846001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b15801561278a57600080fd5b505afa15801561279e573d6000803e3d6000fd5b505050506040513d60208110156127b457600080fd5b5051815182906000906127c357fe5b60200260200101906001600160a01b031690816001600160a01b031681525050846001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b15801561281c57600080fd5b505afa158015612830573d6000803e3d6000fd5b505050506040513d602081101561284657600080fd5b505181518290600190811061285757fe5b60200260200101906001600160a01b031690816001600160a01b03168152505060006128838686613469565b90506000806060896001600160a01b0316631f00ca7489876040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156128f05781810151838201526020016128d8565b50505050905001935050505060006040518083038186803b15801561291457600080fd5b505afa158015612928573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561295157600080fd5b8101908080516040519392919084600160201b82111561297057600080fd5b90830190602082018581111561298557600080fd5b82518660208202830111600160201b821117156129a157600080fd5b82525081516020918201928201910280838360005b838110156129ce5781810151838201526020016129b6565b5050505090500160405250505090506000816000815181106129ec57fe5b60200260200101519050808511612a04576000612a14565b612a14858263ffffffff61202616565b92506000858211612a26576000612a36565b612a36828763ffffffff61202616565b90508015612a42578094505b50919a92995091975050505050505050565b6000806000612a6389896136a1565b915091506000896001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b158015612aa257600080fd5b505afa158015612ab6573d6000803e3d6000fd5b505050506040513d6020811015612acc57600080fd5b505190506000612ae18c83858c8c8c8c613ab4565b91505080612b28576040805162461bcd60e51b815260206004820152600f60248201526e11549497d4d5d05417d19052531151608a1b604482015290519081900360640190fd5b9b9a5050505050505050505050565b600080333014612b7d576040805162461bcd60e51b815260206004820152600c60248201526b22a9292fa727aa2fa9a2a62360a11b604482015290519081900360640190fd5b6001600160a01b038316612bce576040805162461bcd60e51b81526020600482015260136024820152724552525f544f5f414444524553535f5a45524f60681b604482015290519081900360640190fd5b6001600160a01b038316331415612c20576040805162461bcd60e51b815260206004820152601160248201527022a9292faa27afa6a9a3afa9a2a72222a960791b604482015290519081900360640190fd5b876001600160a01b0316612c668a6001600160a01b031663c45a01556040518163ffffffff1660e01b815260040160206040518083038186803b1580156123e157600080fd5b6001600160a01b031614612cb4576040805162461bcd60e51b815260206004820152601060248201526f22a9292fa4a72b20a624a22fa820a4a960811b604482015290519081900360640190fd5b6000876001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b158015612cef57600080fd5b505afa158015612d03573d6000803e3d6000fd5b505050506040513d6020811015612d1957600080fd5b505160408051632f310bad60e01b815290519192506000916001600160a01b038b1691632f310bad916004808301926020929190829003018186803b158015612d6157600080fd5b505afa158015612d75573d6000803e3d6000fd5b505050506040513d6020811015612d8b57600080fd5b505186519091506001600160a01b0383169087906001908110612daa57fe5b60200260200101516001600160a01b031614612e0d576040805162461bcd60e51b815260206004820152601b60248201527f4552525f454e445f504154485f4e4f545f554e4445524c59494e470000000000604482015290519081900360640190fd5b600080612e1a8b8b613d30565b91509150600080612e2c8f8e8e612f2b565b90925090508015612e4a57612e47838263ffffffff61202616565b92505b8215612e6a57612e6a6001600160a01b0386168f8563ffffffff61206816565b8115612edc57818b1015612ebc576040805162461bcd60e51b815260206004820152601460248201527308aa4a4bea0a48a9a92aa9abe9eac8aa4be9a82b60631b604482015290519081900360640190fd5b612edc898f84896001600160a01b0316613409909392919063ffffffff16565b8015612efc57612efc6001600160a01b0386168a8363ffffffff61206816565b612f166001600160a01b038e168a8663ffffffff61206816565b50919d919c50909a5050505050505050505050565b604080516002808252606080830184526000938493919290602083019080388339019050509050846001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f8b57600080fd5b505afa158015612f9f573d6000803e3d6000fd5b505050506040513d6020811015612fb557600080fd5b505181518290600090612fc457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050846001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b15801561301d57600080fd5b505afa158015613031573d6000803e3d6000fd5b505050506040513d602081101561304757600080fd5b505181518290600190811061305857fe5b60200260200101906001600160a01b031690816001600160a01b03168152505060006130848686613e39565b90506000806060896001600160a01b0316631f00ca7489876040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156130f15781810151838201526020016130d9565b50505050905001935050505060006040518083038186803b15801561311557600080fd5b505afa158015613129573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561315257600080fd5b8101908080516040519392919084600160201b82111561317157600080fd5b90830190602082018581111561318657600080fd5b82518660208202830111600160201b821117156131a257600080fd5b82525081516020918201928201910280838360005b838110156131cf5781810151838201526020016131b7565b5050505090500160405250505090506000816000815181106131ed57fe5b602002602001015190506000858211613207576000613217565b613217828763ffffffff61202616565b9050818611613227576000613237565b613237868363ffffffff61202616565b935080156133f75760608c6001600160a01b031663d06ca61f838a6040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156132a757818101518382015260200161328f565b50505050905001935050505060006040518083038186803b1580156132cb57600080fd5b505afa1580156132df573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561330857600080fd5b8101908080516040519392919084600160201b82111561332757600080fd5b90830190602082018581111561333c57600080fd5b82518660208202830111600160201b8211171561335857600080fd5b82525081516020918201928201910280838360005b8381101561338557818101518382015260200161336d565b5050505090500160405250505090506133f3620186a06133e76133c761012d856001815181106133b157fe5b6020026020010151613ee790919063ffffffff16565b6133db620186a0866001815181106133b157fe5b9063ffffffff6120bf16565b9063ffffffff613f4016565b9550505b50929a91995090975050505050505050565b604080516001600160a01b0380861660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b1790526134639085906135f0565b50505050565b600080613551846001600160a01b031662fe19fa6040518163ffffffff1660e01b815260040160206040518083038186803b1580156134a757600080fd5b505afa1580156134bb573d6000803e3d6000fd5b505050506040513d60208110156134d157600080fd5b5051604080516316b2542760e31b815290516133e7916001600160a01b0389169163b592a13891600480820192602092909190829003018186803b15801561351857600080fd5b505afa15801561352c573d6000803e3d6000fd5b505050506040513d602081101561354257600080fd5b5051869063ffffffff613ee716565b949350505050565b600081848411156135e85760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156135ad578181015183820152602001613595565b50505050905090810190601f1680156135da5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6060613645826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316613f829092919063ffffffff16565b8051909150156120ba5780806020019051602081101561366457600080fd5b50516120ba5760405162461bcd60e51b815260040180806020018281038252602a815260200180614347602a913960400191505060405180910390fd5b6000806000806136b2868630611a49565b90925090506136d16001600160a01b038716338763ffffffff61206816565b909590945092505050565b600080806136fb6001600160a01b038a1633308a63ffffffff61340916565b6040805163095ea7b360e01b81526001600160a01b038d8116600483015260001960248301529151918c169163095ea7b3916044808201926020929091908290030181600087803b15801561374f57600080fd5b505af1158015613763573d6000803e3d6000fd5b505050506040513d602081101561377957600080fd5b50506040805163095ea7b360e01b81526001600160a01b038d8116600483015260001960248301529151918b169163095ea7b3916044808201926020929091908290030181600087803b1580156137cf57600080fd5b505af11580156137e3573d6000803e3d6000fd5b505050506040513d60208110156137f957600080fd5b50506040805162e8e33760e81b81526001600160a01b038c811660048301528b81166024830152604482018b9052606482018a9052608482018b905260a4820189905287811660c483015260e482018790529151918d169163e8e3370091610104808201926060929091908290030181600087803b15801561387a57600080fd5b505af115801561388e573d6000803e3d6000fd5b505050506040513d60608110156138a457600080fd5b5080516020820151604090920151909d919c509a5098505050505050505050565b60008060006138d48585613f91565b604080516bffffffffffffffffffffffff19606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501206001600160f81b031960688401529a90941b9093166069840152607d8301989098527fe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b6000613a078383856001600160a01b0316632f310bad6040518163ffffffff1660e01b815260040160206040518083038186803b1580156139c557600080fd5b505afa1580156139d9573d6000803e3d6000fd5b505050506040513d60208110156139ef57600080fd5b50516001600160a01b0316919063ffffffff61206816565b6000613a138484613469565b9050613a306001600160a01b03851686868463ffffffff61340916565b60408051638349980560e01b815230600482015290516000916001600160a01b0387169163834998059160248082019260609290919082900301818787803b158015613a7b57600080fd5b505af1158015613a8f573d6000803e3d6000fd5b505050506040513d6060811015613aa557600080fd5b50604001519695505050505050565b60606000876001600160a01b031685600081518110613acf57fe5b60200260200101516001600160a01b031614613b2a576040805162461bcd60e51b815260206004820152601560248201527411549497d410551217d3d4151253d397d4d5105495605a1b604482015290519081900360640190fd5b6040805163095ea7b360e01b81526001600160a01b038b8116600483015260001960248301529151918a169163095ea7b3916044808201926020929091908290030181600087803b158015613b7e57600080fd5b505af1158015613b92573d6000803e3d6000fd5b505050506040513d6020811015613ba857600080fd5b50506040516338ed173960e01b815260048101888152602482018890526001600160a01b0386811660648401526084830186905260a060448401908152885160a48501528851918d16936338ed1739938c938c938c938c938c9390929160c401906020878101910280838360005b83811015613c2e578181015183820152602001613c16565b505050509050019650505050505050600060405180830381600087803b158015613c5757600080fd5b505af1158015613c6b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015613c9457600080fd5b8101908080516040519392919084600160201b821115613cb357600080fd5b908301906020820185811115613cc857600080fd5b82518660208202830111600160201b82111715613ce457600080fd5b82525081516020918201928201910280838360005b83811015613d11578181015183820152602001613cf9565b5050505090500160405250505091506001905097509795505050505050565b6000806000846001600160a01b031663440f5fd26040518163ffffffff1660e01b815260040160206040518083038186803b158015613d6e57600080fd5b505afa158015613d82573d6000803e3d6000fd5b505050506040513d6020811015613d9857600080fd5b50519050613db66001600160a01b038216868663ffffffff61206816565b60408051633ea6b5f160e21b815230600482015281516001600160a01b0388169263fa9ad7c492602480820193918290030181600087803b158015613dfa57600080fd5b505af1158015613e0e573d6000803e3d6000fd5b505050506040513d6040811015613e2457600080fd5b50805160209091015190969095509350505050565b600080613551846001600160a01b031663b592a1386040518163ffffffff1660e01b815260040160206040518083038186803b158015613e7857600080fd5b505afa158015613e8c573d6000803e3d6000fd5b505050506040513d6020811015613ea257600080fd5b505160408051627f0cfd60e11b815290516133e7916001600160a01b0389169162fe19fa91600480820192602092909190829003018186803b15801561351857600080fd5b600082613ef657506000610e72565b82820282848281613f0357fe5b0414611a425760405162461bcd60e51b81526004018080602001828103825260218152602001806142d66021913960400191505060405180910390fd5b6000611a4283836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250614068565b606061355184846000856140cd565b600080826001600160a01b0316846001600160a01b03161415613fe55760405162461bcd60e51b81526004018080602001828103825260258152602001806142616025913960400191505060405180910390fd5b826001600160a01b0316846001600160a01b031610614005578284614008565b83835b90925090506001600160a01b038216610e99576040805162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b600081836140b75760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156135ad578181015183820152602001613595565b5060008385816140c357fe5b0495945050505050565b60606140d88561423a565b614129576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b60006060866001600160a01b031685876040518082805190602001908083835b602083106141685780518252601f199092019160209182019101614149565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d80600081146141ca576040519150601f19603f3d011682016040523d82523d6000602084013e6141cf565b606091505b509150915081156141e35791506135519050565b8051156141f35780518082602001fd5b60405162461bcd60e51b81526020600482018181528651602484015286518793919283926044019190850190808383600083156135ad578181015183820152602001613595565b3b15159056fe5265656e7472616e637947756172643a207265656e7472616e742063616c6c00556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553666c617368436c6f73654c6f6e674f7074696f6e735468656e5377617028616464726573732c616464726573732c75696e743235362c75696e743235362c616464726573735b5d2c6164647265737329536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77666c6173684d696e7453686f72744f7074696f6e735468656e5377617028616464726573732c616464726573732c75696e743235362c75696e743235362c616464726573735b5d2c61646472657373295361666545524332303a204552433230206f7065726174696f6e20646964206e6f742073756363656564a2646970667358221220051394a9e94f5fc08a5386ae76b01641aaccf3163fe37fec5417c31a702a75bc64736f6c63430006020033000000000000000000000000d9e1ce17f2641f24ae83637ab66a2cca9c378b9f000000000000000000000000c0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac000000000000000000000000c1de48e9577a7cf18594323a73cdcf1ee19962e8

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000d9e1ce17f2641f24ae83637ab66a2cca9c378b9f000000000000000000000000c0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac000000000000000000000000c1de48e9577a7cf18594323a73cdcf1ee19962e8

-----Decoded View---------------
Arg [0] : router_ (address): 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F
Arg [1] : factory_ (address): 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac
Arg [2] : trader_ (address): 0xc1de48E9577A7CF18594323A73CDcF1EE19962E8

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000d9e1ce17f2641f24ae83637ab66a2cca9c378b9f
Arg [1] : 000000000000000000000000c0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac
Arg [2] : 000000000000000000000000c1de48e9577a7cf18594323a73cdcf1ee19962e8


Block Transaction Difficulty Gas Used Reward
Block Uncle Number Difficulty Gas Used Reward
Loading
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.