ETH Price: $3,463.37 (+1.92%)
Gas: 8 Gwei

Contract

0x21b6e092a03456871cE14493C526832305618E27
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Multicall202601042024-07-08 6:48:119 days ago1720421291IN
0x21b6e092...305618E27
0 ETH0.001265861.95150157
Multicall202455162024-07-06 5:55:1111 days ago1720245311IN
0x21b6e092...305618E27
0 ETH0.000539731.32
Multicall198549752024-05-12 15:57:2365 days ago1715529443IN
0x21b6e092...305618E27
0 ETH0.001934474.72891214
Multicall198406332024-05-10 15:48:4767 days ago1715356127IN
0x21b6e092...305618E27
0 ETH0.002722776.55421233
Multicall196522992024-04-14 7:32:1194 days ago1713079931IN
0x21b6e092...305618E27
0 ETH0.00786111.34096017
Multicall196020892024-04-07 6:40:11101 days ago1712472011IN
0x21b6e092...305618E27
0 ETH0.0068282310.68994742
Multicall196020852024-04-07 6:39:23101 days ago1712471963IN
0x21b6e092...305618E27
0 ETH0.0066508110.26270992
Multicall195980092024-04-06 16:54:35101 days ago1712422475IN
0x21b6e092...305618E27
0 ETH0.0095968215.12842834
Multicall195979892024-04-06 16:50:35101 days ago1712422235IN
0x21b6e092...305618E27
0 ETH0.0100229215.45155205
Multicall193313102024-02-29 6:12:47139 days ago1709187167IN
0x21b6e092...305618E27
0 ETH0.0217186653.18964151
Multicall193313072024-02-29 6:12:11139 days ago1709187131IN
0x21b6e092...305618E27
0 ETH0.0216576953.9073135
Multicall192176232024-02-13 7:31:23155 days ago1707809483IN
0x21b6e092...305618E27
0 ETH0.0089507722.03967294
Multicall192174122024-02-13 6:49:11155 days ago1707806951IN
0x21b6e092...305618E27
0 ETH0.007116317.98309864
Multicall192173632024-02-13 6:39:23155 days ago1707806363IN
0x21b6e092...305618E27
0 ETH0.0075245517.91352668
Multicall191299832024-02-01 0:19:11167 days ago1706746751IN
0x21b6e092...305618E27
0 ETH0.0285120318.66703777
Multicall191299792024-02-01 0:18:11167 days ago1706746691IN
0x21b6e092...305618E27
0 ETH0.0312197818.96740783
Multicall191091642024-01-29 2:20:23170 days ago1706494823IN
0x21b6e092...305618E27
0 ETH0.005994589.32601686
Multicall191091582024-01-29 2:19:11170 days ago1706494751IN
0x21b6e092...305618E27
0 ETH0.0065761110.00924599
Multicall190921562024-01-26 17:06:11172 days ago1706288771IN
0x21b6e092...305618E27
0 ETH0.0178191528.79594096
Multicall190872752024-01-26 0:42:11173 days ago1706229731IN
0x21b6e092...305618E27
0 ETH0.006084715.83449943
Swap Nyt To Unde...190478512024-01-20 11:42:47179 days ago1705750967IN
0x21b6e092...305618E27
0 ETH0.0069179112.7978333
Swap Underlying ...190088642024-01-15 1:00:11184 days ago1705280411IN
0x21b6e092...305618E27
0 ETH0.008246219.11888839
Multicall189855842024-01-11 18:52:11187 days ago1704999131IN
0x21b6e092...305618E27
0 ETH0.0102677625.14742146
Multicall189855782024-01-11 18:50:59187 days ago1704999059IN
0x21b6e092...305618E27
0 ETH0.0113975527.92484217
Multicall189407642024-01-05 11:26:35194 days ago1704453995IN
0x21b6e092...305618E27
0 ETH0.0068830316.7372629
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block From To
181587732023-09-17 22:44:11303 days ago1694990651
0x21b6e092...305618E27
0.01 ETH
181587672023-09-17 22:42:59303 days ago1694990579
0x21b6e092...305618E27
0.01 ETH
170438572023-04-14 7:08:11460 days ago1681456091
0x21b6e092...305618E27
0.18 ETH
169844712023-04-05 18:56:11468 days ago1680720971
0x21b6e092...305618E27
0.5 ETH
169844682023-04-05 18:55:35468 days ago1680720935
0x21b6e092...305618E27
0.5 ETH
168947702023-03-24 3:45:23481 days ago1679629523
0x21b6e092...305618E27
0.2 ETH
167567812023-03-04 18:13:11500 days ago1677953591
0x21b6e092...305618E27
0.321 ETH
167431772023-03-02 20:16:11502 days ago1677788171
0x21b6e092...305618E27
0.7 ETH
166048202023-02-11 10:07:23522 days ago1676110043
0x21b6e092...305618E27
0.00065 ETH
166048132023-02-11 10:05:59522 days ago1676109959
0x21b6e092...305618E27
0.00065 ETH
165302422023-01-31 23:58:59532 days ago1675209539
0x21b6e092...305618E27
0.02 ETH
164899042023-01-26 8:50:35538 days ago1674723035
0x21b6e092...305618E27
0.28 ETH
164474422023-01-20 10:32:59544 days ago1674210779
0x21b6e092...305618E27
0.005 ETH
164469252023-01-20 8:49:23544 days ago1674204563
0x21b6e092...305618E27
0.3 ETH
164274492023-01-17 15:34:35546 days ago1673969675
0x21b6e092...305618E27
0.12 ETH
164234512023-01-17 2:12:47547 days ago1673921567
0x21b6e092...305618E27
0.25 ETH
162671472022-12-26 6:30:47569 days ago1672036247
0x21b6e092...305618E27
0.005 ETH
162671462022-12-26 6:30:35569 days ago1672036235
0x21b6e092...305618E27
0.005 ETH
158911862022-11-03 18:09:59621 days ago1667498999
0x21b6e092...305618E27
0.0001 ETH
158738272022-11-01 7:54:47624 days ago1667289287
0x21b6e092...305618E27
0.00064 ETH
158613782022-10-30 14:08:47625 days ago1667138927
0x21b6e092...305618E27
0.071 ETH
158613722022-10-30 14:07:35625 days ago1667138855
0x21b6e092...305618E27
0.07 ETH
158613052022-10-30 13:54:11625 days ago1667138051
0x21b6e092...305618E27
0.075 ETH
158612442022-10-30 13:41:59625 days ago1667137319
0x21b6e092...305618E27
0.07 ETH
158610332022-10-30 12:59:47626 days ago1667134787
0x21b6e092...305618E27
0.069 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
UniswapV3Swapper

Compiler Version
v0.8.13+commit.abaa5c0e

Optimization Enabled:
Yes with 1000000 runs

Other Settings:
default evmVersion, GNU AGPLv3 license

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2022-07-08
*/

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.4;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    event Debug(bool one, bool two, uint256 retsize);

    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
    using SafeTransferLib for address;

    event Deposit(address indexed from, uint256 amount);

    event Withdrawal(address indexed to, uint256 amount);

    function deposit() public payable virtual {
        _mint(msg.sender, msg.value);

        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) public virtual {
        _burn(msg.sender, amount);

        emit Withdrawal(msg.sender, amount);

        msg.sender.safeTransferETH(amount);
    }

    receive() external payable virtual {
        deposit();
    }
}

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
                revert(0, 0)
            }

            // Divide z by the denominator.
            z := div(z, denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
                revert(0, 0)
            }

            // First, divide z - 1 by the denominator and add 1.
            // We allow z - 1 to underflow if z is 0, because we multiply the
            // end result by 0 if z is zero, ensuring we return 0 if z is zero.
            z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        assembly {
            // Start off with z at 1.
            z := 1

            // Used below to help find a nearby power of 2.
            let y := x

            // Find the lowest power of 2 that is at least sqrt(x).
            if iszero(lt(y, 0x100000000000000000000000000000000)) {
                y := shr(128, y) // Like dividing by 2 ** 128.
                z := shl(64, z) // Like multiplying by 2 ** 64.
            }
            if iszero(lt(y, 0x10000000000000000)) {
                y := shr(64, y) // Like dividing by 2 ** 64.
                z := shl(32, z) // Like multiplying by 2 ** 32.
            }
            if iszero(lt(y, 0x100000000)) {
                y := shr(32, y) // Like dividing by 2 ** 32.
                z := shl(16, z) // Like multiplying by 2 ** 16.
            }
            if iszero(lt(y, 0x10000)) {
                y := shr(16, y) // Like dividing by 2 ** 16.
                z := shl(8, z) // Like multiplying by 2 ** 8.
            }
            if iszero(lt(y, 0x100)) {
                y := shr(8, y) // Like dividing by 2 ** 8.
                z := shl(4, z) // Like multiplying by 2 ** 4.
            }
            if iszero(lt(y, 0x10)) {
                y := shr(4, y) // Like dividing by 2 ** 4.
                z := shl(2, z) // Like multiplying by 2 ** 2.
            }
            if iszero(lt(y, 0x8)) {
                // Equivalent to 2 ** z.
                z := shl(1, z)
            }

            // Shifting right by 1 is like dividing by 2.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // Compute a rounded down version of z.
            let zRoundDown := div(x, z)

            // If zRoundDown is smaller, use it.
            if lt(zRoundDown, z) {
                z := zRoundDown
            }
        }
    }
}

/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
    using SafeTransferLib for ERC20;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    //////////////////////////////////////////////////////////////*/

    ERC20 public immutable asset;

    constructor(
        ERC20 _asset,
        string memory _name,
        string memory _symbol
    ) ERC20(_name, _symbol, _asset.decimals()) {
        asset = _asset;
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    //////////////////////////////////////////////////////////////*/

    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
        // Check for rounding error since we round down in previewDeposit.
        require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

        // Need to transfer before minting or ERC777s could reenter.
        asset.safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
        assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.

        // Need to transfer before minting or ERC777s could reenter.
        asset.safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public virtual returns (uint256 shares) {
        shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.

        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

        emit Withdraw(msg.sender, receiver, owner, assets, shares);

        asset.safeTransfer(receiver, assets);
    }

    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public virtual returns (uint256 assets) {
        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

        // Check for rounding error since we round down in previewRedeem.
        require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

        emit Withdraw(msg.sender, receiver, owner, assets, shares);

        asset.safeTransfer(receiver, assets);
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    function totalAssets() public view virtual returns (uint256);

    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
    }

    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
    }

    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    function previewMint(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
    }

    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
    }

    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf[owner]);
    }

    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    //////////////////////////////////////////////////////////////*/

    function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}

    function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}

// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
// Simplified by BoringCrypto

contract BoringOwnableData {
    address public owner;
    address public pendingOwner;
}

contract BoringOwnable is BoringOwnableData {
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice `owner` defaults to msg.sender on construction.
    constructor() {
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    /// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
    /// Can only be invoked by the current `owner`.
    /// @param newOwner Address of the new owner.
    /// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
    /// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
    function transferOwnership(
        address newOwner,
        bool direct,
        bool renounce
    ) public onlyOwner {
        if (direct) {
            // Checks
            require(newOwner != address(0) || renounce, "Ownable: zero address");

            // Effects
            emit OwnershipTransferred(owner, newOwner);
            owner = newOwner;
            pendingOwner = address(0);
        } else {
            // Effects
            pendingOwner = newOwner;
        }
    }

    /// @notice Needs to be called by `pendingOwner` to claim ownership.
    function claimOwnership() public {
        address _pendingOwner = pendingOwner;

        // Checks
        require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");

        // Effects
        emit OwnershipTransferred(owner, _pendingOwner);
        owner = _pendingOwner;
        pendingOwner = address(0);
    }

    /// @notice Only allows the `owner` to execute the function.
    modifier onlyOwner() {
        require(msg.sender == owner, "Ownable: caller is not the owner");
        _;
    }
}

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

/// @notice Library for converting between addresses and bytes32 values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/Bytes32AddressLib.sol)
library Bytes32AddressLib {
    function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) {
        return address(uint160(uint256(bytesValue)));
    }

    function fillLast12Bytes(address addressValue) internal pure returns (bytes32) {
        return bytes32(bytes20(addressValue));
    }
}

/// @title BaseERC20
/// @author zefram.eth
/// @notice The base ERC20 contract used by NegativeYieldToken and PerpetualYieldToken
/// @dev Uses the same number of decimals as the vault's underlying token
contract BaseERC20 is ERC20 {
    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error Error_NotGate();

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    Gate public immutable gate;
    address public immutable vault;

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(
        string memory name_,
        string memory symbol_,
        Gate gate_,
        address vault_
    ) ERC20(name_, symbol_, gate_.getUnderlyingOfVault(vault_).decimals()) {
        gate = gate_;
        vault = vault_;
    }

    /// -----------------------------------------------------------------------
    /// Gate-callable functions
    /// -----------------------------------------------------------------------

    function gateMint(address to, uint256 amount) external virtual {
        if (msg.sender != address(gate)) {
            revert Error_NotGate();
        }

        _mint(to, amount);
    }

    function gateBurn(address from, uint256 amount) external virtual {
        if (msg.sender != address(gate)) {
            revert Error_NotGate();
        }

        _burn(from, amount);
    }
}

/// @title NegativeYieldToken
/// @author zefram.eth
/// @notice The ERC20 contract representing negative yield tokens
contract NegativeYieldToken is BaseERC20 {
    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(Gate gate_, address vault_)
        BaseERC20(
            gate_.negativeYieldTokenName(vault_),
            gate_.negativeYieldTokenSymbol(vault_),
            gate_,
            vault_
        )
    {}
}

/// @title PerpetualYieldToken
/// @author zefram.eth
/// @notice The ERC20 contract representing perpetual yield tokens
contract PerpetualYieldToken is BaseERC20 {
    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(Gate gate_, address vault_)
        BaseERC20(
            gate_.perpetualYieldTokenName(vault_),
            gate_.perpetualYieldTokenSymbol(vault_),
            gate_,
            vault_
        )
    {}

    /// -----------------------------------------------------------------------
    /// ERC20 overrides
    /// -----------------------------------------------------------------------

    function transfer(address to, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        // load balances to save gas
        uint256 fromBalance = balanceOf[msg.sender];
        uint256 toBalance = balanceOf[to];

        // call transfer hook
        gate.beforePerpetualYieldTokenTransfer(
            msg.sender,
            to,
            amount,
            fromBalance,
            toBalance
        );

        // do transfer
        // skip during self transfers since toBalance is cached
        // which leads to free minting, a critical issue
        if (msg.sender != to) {
            balanceOf[msg.sender] = fromBalance - amount;

            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] = toBalance + amount;
            }
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        // load balances to save gas
        uint256 fromBalance = balanceOf[from];
        uint256 toBalance = balanceOf[to];

        // call transfer hook
        gate.beforePerpetualYieldTokenTransfer(
            from,
            to,
            amount,
            fromBalance,
            toBalance
        );

        // update allowance
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max)
            allowance[from][msg.sender] = allowed - amount;

        // do transfer
        // skip during self transfers since toBalance is cached
        // which leads to free minting, a critical issue
        if (from != to) {
            balanceOf[from] = fromBalance - amount;

            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] = toBalance + amount;
            }
        }

        emit Transfer(from, to, amount);

        return true;
    }
}

contract Factory is BoringOwnable {
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using Bytes32AddressLib for address;
    using Bytes32AddressLib for bytes32;

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error Error_ProtocolFeeRecipientIsZero();

    /// -----------------------------------------------------------------------
    /// Events
    /// -----------------------------------------------------------------------

    event SetProtocolFee(ProtocolFeeInfo protocolFeeInfo_);
    event DeployYieldTokenPair(
        Gate indexed gate,
        address indexed vault,
        NegativeYieldToken nyt,
        PerpetualYieldToken pyt
    );

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    struct ProtocolFeeInfo {
        uint8 fee; // each increment represents 0.1%, so max is 25.5%
        address recipient;
    }
    /// @notice The protocol fee and the fee recipient address.
    ProtocolFeeInfo public protocolFeeInfo;

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(ProtocolFeeInfo memory protocolFeeInfo_) {
        if (
            protocolFeeInfo_.fee != 0 &&
            protocolFeeInfo_.recipient == address(0)
        ) {
            revert Error_ProtocolFeeRecipientIsZero();
        }
        protocolFeeInfo = protocolFeeInfo_;
        emit SetProtocolFee(protocolFeeInfo_);
    }

    /// -----------------------------------------------------------------------
    /// User actions
    /// -----------------------------------------------------------------------

    /// @notice Deploys the NegativeYieldToken and PerpetualYieldToken associated with a vault.
    /// @dev Will revert if they have already been deployed.
    /// @param gate The gate that will use the NYT and PYT
    /// @param vault The vault to deploy NYT and PYT for
    /// @return nyt The deployed NegativeYieldToken
    /// @return pyt The deployed PerpetualYieldToken
    function deployYieldTokenPair(Gate gate, address vault)
        public
        virtual
        returns (NegativeYieldToken nyt, PerpetualYieldToken pyt)
    {
        // Use the CREATE2 opcode to deploy new NegativeYieldToken and PerpetualYieldToken contracts.
        // This will revert if the contracts have already been deployed,
        // as the salt & bytecode hash would be the same and we can't deploy with it twice.
        nyt = new NegativeYieldToken{salt: bytes32(0)}(gate, vault);
        pyt = new PerpetualYieldToken{salt: bytes32(0)}(gate, vault);

        emit DeployYieldTokenPair(gate, vault, nyt, pyt);
    }

    /// -----------------------------------------------------------------------
    /// Getters
    /// -----------------------------------------------------------------------

    /// @notice Returns the NegativeYieldToken associated with a gate & vault pair.
    /// @dev Returns non-zero value even if the contract hasn't been deployed yet.
    /// @param gate The gate to query
    /// @param vault The vault to query
    /// @return The NegativeYieldToken address
    function getNegativeYieldToken(Gate gate, address vault)
        public
        view
        virtual
        returns (NegativeYieldToken)
    {
        return
            NegativeYieldToken(_computeYieldTokenAddress(gate, vault, false));
    }

    /// @notice Returns the PerpetualYieldToken associated with a gate & vault pair.
    /// @dev Returns non-zero value even if the contract hasn't been deployed yet.
    /// @param gate The gate to query
    /// @param vault The vault to query
    /// @return The PerpetualYieldToken address
    function getPerpetualYieldToken(Gate gate, address vault)
        public
        view
        virtual
        returns (PerpetualYieldToken)
    {
        return
            PerpetualYieldToken(_computeYieldTokenAddress(gate, vault, true));
    }

    /// -----------------------------------------------------------------------
    /// Owner functions
    /// -----------------------------------------------------------------------

    /// @notice Updates the protocol fee and/or the protocol fee recipient.
    /// Only callable by the owner.
    /// @param protocolFeeInfo_ The new protocol fee info
    function ownerSetProtocolFee(ProtocolFeeInfo calldata protocolFeeInfo_)
        external
        virtual
        onlyOwner
    {
        if (
            protocolFeeInfo_.fee != 0 &&
            protocolFeeInfo_.recipient == address(0)
        ) {
            revert Error_ProtocolFeeRecipientIsZero();
        }
        protocolFeeInfo = protocolFeeInfo_;

        emit SetProtocolFee(protocolFeeInfo_);
    }

    /// -----------------------------------------------------------------------
    /// Internal utilities
    /// -----------------------------------------------------------------------

    /// @dev Computes the address of PYTs and NYTs using CREATE2.
    function _computeYieldTokenAddress(
        Gate gate,
        address vault,
        bool isPerpetualYieldToken
    ) internal view virtual returns (address) {
        return
            keccak256(
                abi.encodePacked(
                    // Prefix:
                    bytes1(0xFF),
                    // Creator:
                    address(this),
                    // Salt:
                    bytes32(0),
                    // Bytecode hash:
                    keccak256(
                        abi.encodePacked(
                            // Deployment bytecode:
                            isPerpetualYieldToken
                                ? type(PerpetualYieldToken).creationCode
                                : type(NegativeYieldToken).creationCode,
                            // Constructor arguments:
                            abi.encode(gate, vault)
                        )
                    )
                )
            ).fromLast20Bytes(); // Convert the CREATE2 hash into an address.
    }
}

abstract contract IxPYT is ERC4626 {
    function sweep(address receiver) external virtual returns (uint256 shares);
}

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (0 - denominator) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // Now use Newton-Raphson iteration to improve the precision.
            // Thanks to Hensel's lifting lemma, this also works in modular
            // arithmetic, doubling the correct bits in each step.
            inv *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

            // Because the division is now exact we can divide by multiplying
            // with the modular inverse of denominator. This will give us the
            // correct result modulo 2**256. Since the precoditions guarantee
            // that the outcome is less than 2**256, this is the final result.
            // We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inv;
            return result;
        }
    }

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            result = mulDiv(a, b, denominator);
            if (mulmod(a, b, denominator) > 0) {
                require(result < type(uint256).max);
                result++;
            }
        }
    }
}

/// @title Multicall
/// @notice Enables calling multiple methods in a single call to the contract
abstract contract Multicall {
    function multicall(bytes[] calldata data)
        external
        payable
        returns (bytes[] memory results)
    {
        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(
                data[i]
            );

            if (!success) {
                // Next 5 lines from https://ethereum.stackexchange.com/a/83577
                if (result.length < 68) revert();
                assembly {
                    result := add(result, 0x04)
                }
                revert(abi.decode(result, (string)));
            }

            results[i] = result;
        }
    }
}

/// @title Self Permit
/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route
/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function
/// that requires an approval in a single transaction.
abstract contract SelfPermit {
    function selfPermit(
        ERC20 token,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public payable {
        token.permit(msg.sender, address(this), value, deadline, v, r, s);
    }

    function selfPermitIfNecessary(
        ERC20 token,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable {
        if (token.allowance(msg.sender, address(this)) < value)
            selfPermit(token, value, deadline, v, r, s);
    }
}

/// @title Gate
/// @author zefram.eth
/// @notice Gate is the main contract users interact with to mint/burn NegativeYieldToken
/// and PerpetualYieldToken, as well as claim the yield earned by PYTs.
/// @dev Gate is an abstract contract that should be inherited from in order to support
/// a specific vault protocol (e.g. YearnGate supports YearnVault). Each Gate handles
/// all vaults & associated NYTs/PYTs of a specific vault protocol.
///
/// Vaults are yield-generating contracts used by Gate. Gate makes several assumptions about
/// a vault:
/// 1) A vault has a single associated underlying token that is immutable.
/// 2) A vault gives depositors yield denominated in the underlying token.
/// 3) A vault depositor owns shares in the vault, which represents their deposit.
/// 4) Vaults have a notion of "price per share", which is the amount of underlying tokens
///    each vault share can be redeemed for.
/// 5) If vault shares are represented using an ERC20 token, then the ERC20 token contract must be
///    the vault contract itself.
abstract contract Gate is
    ReentrancyGuard,
    Multicall,
    SelfPermit,
    BoringOwnable
{
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeTransferLib for ERC20;
    using SafeTransferLib for ERC4626;

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error Error_InvalidInput();
    error Error_VaultSharesNotERC20();
    error Error_TokenPairNotDeployed();
    error Error_EmergencyExitNotActivated();
    error Error_SenderNotPerpetualYieldToken();
    error Error_EmergencyExitAlreadyActivated();

    /// -----------------------------------------------------------------------
    /// Events
    /// -----------------------------------------------------------------------

    event EnterWithUnderlying(
        address sender,
        address indexed nytRecipient,
        address indexed pytRecipient,
        address indexed vault,
        IxPYT xPYT,
        uint256 underlyingAmount
    );
    event EnterWithVaultShares(
        address sender,
        address indexed nytRecipient,
        address indexed pytRecipient,
        address indexed vault,
        IxPYT xPYT,
        uint256 vaultSharesAmount
    );
    event ExitToUnderlying(
        address indexed sender,
        address indexed recipient,
        address indexed vault,
        IxPYT xPYT,
        uint256 underlyingAmount
    );
    event ExitToVaultShares(
        address indexed sender,
        address indexed recipient,
        address indexed vault,
        IxPYT xPYT,
        uint256 vaultSharesAmount
    );
    event ClaimYieldInUnderlying(
        address indexed sender,
        address indexed recipient,
        address indexed vault,
        uint256 underlyingAmount
    );
    event ClaimYieldInVaultShares(
        address indexed sender,
        address indexed recipient,
        address indexed vault,
        uint256 vaultSharesAmount
    );
    event ClaimYieldAndEnter(
        address sender,
        address indexed nytRecipient,
        address indexed pytRecipient,
        address indexed vault,
        IxPYT xPYT,
        uint256 amount
    );

    /// -----------------------------------------------------------------------
    /// Structs
    /// -----------------------------------------------------------------------

    /// @param activated True if emergency exit has been activated, false if not
    /// @param pytPriceInUnderlying The amount of underlying assets each PYT can redeem for.
    /// Should be a value in the range [0, PRECISION]
    struct EmergencyExitStatus {
        bool activated;
        uint96 pytPriceInUnderlying;
    }

    /// -----------------------------------------------------------------------
    /// Constants
    /// -----------------------------------------------------------------------

    /// @notice The decimals of precision used by yieldPerTokenStored and pricePerVaultShareStored
    uint256 internal constant PRECISION_DECIMALS = 27;

    /// @notice The precision used by yieldPerTokenStored and pricePerVaultShareStored
    uint256 internal constant PRECISION = 10**PRECISION_DECIMALS;

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    Factory public immutable factory;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    /// @notice The amount of underlying tokens each vault share is worth, at the time of the last update.
    /// Uses PRECISION.
    /// @dev vault => value
    mapping(address => uint256) public pricePerVaultShareStored;

    /// @notice The amount of yield each PYT has accrued, at the time of the last update.
    /// Scaled by PRECISION.
    /// @dev vault => value
    mapping(address => uint256) public yieldPerTokenStored;

    /// @notice The amount of yield each PYT has accrued, at the time when a user has last interacted
    /// with the gate/PYT. Shifted by 1, so e.g. 3 represents 2, 10 represents 9.
    /// @dev vault => user => value
    /// The value is shifted to use 0 for representing uninitialized users.
    mapping(address => mapping(address => uint256))
        public userYieldPerTokenStored;

    /// @notice The amount of yield a user has accrued, at the time when they last interacted
    /// with the gate/PYT (without calling claimYieldInUnderlying()).
    /// Shifted by 1, so e.g. 3 represents 2, 10 represents 9.
    /// @dev vault => user => value
    mapping(address => mapping(address => uint256)) public userAccruedYield;

    /// @notice Stores info relevant to emergency exits of a vault.
    /// @dev vault => value
    mapping(address => EmergencyExitStatus) public emergencyExitStatusOfVault;

    /// -----------------------------------------------------------------------
    /// Initialization
    /// -----------------------------------------------------------------------

    constructor(Factory factory_) {
        factory = factory_;
    }

    /// -----------------------------------------------------------------------
    /// User actions
    /// -----------------------------------------------------------------------

    /// @notice Converts underlying tokens into NegativeYieldToken and PerpetualYieldToken.
    /// The amount of NYT and PYT minted will be equal to the underlying token amount.
    /// @dev The underlying tokens will be immediately deposited into the specified vault.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// deploy them before proceeding, which will increase the gas cost significantly.
    /// @param nytRecipient The recipient of the minted NYT
    /// @param pytRecipient The recipient of the minted PYT
    /// @param vault The vault to mint NYT and PYT for
    /// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
    /// @param underlyingAmount The amount of underlying tokens to use
    /// @return mintAmount The amount of NYT and PYT minted (the amounts are equal)
    function enterWithUnderlying(
        address nytRecipient,
        address pytRecipient,
        address vault,
        IxPYT xPYT,
        uint256 underlyingAmount
    ) external virtual nonReentrant returns (uint256 mintAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        if (underlyingAmount == 0) {
            return 0;
        }

        /// -----------------------------------------------------------------------
        /// State updates & effects
        /// -----------------------------------------------------------------------

        // mint PYT and NYT
        mintAmount = underlyingAmount;
        _enter(
            nytRecipient,
            pytRecipient,
            vault,
            xPYT,
            underlyingAmount,
            getPricePerVaultShare(vault)
        );

        // transfer underlying from msg.sender to address(this)
        ERC20 underlying = getUnderlyingOfVault(vault);
        underlying.safeTransferFrom(
            msg.sender,
            address(this),
            underlyingAmount
        );

        // deposit underlying into vault
        _depositIntoVault(underlying, underlyingAmount, vault);

        emit EnterWithUnderlying(
            msg.sender,
            nytRecipient,
            pytRecipient,
            vault,
            xPYT,
            underlyingAmount
        );
    }

    /// @notice Converts vault share tokens into NegativeYieldToken and PerpetualYieldToken.
    /// @dev Only available if vault shares are transferrable ERC20 tokens.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// deploy them before proceeding, which will increase the gas cost significantly.
    /// @param nytRecipient The recipient of the minted NYT
    /// @param pytRecipient The recipient of the minted PYT
    /// @param vault The vault to mint NYT and PYT for
    /// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
    /// @param vaultSharesAmount The amount of vault share tokens to use
    /// @return mintAmount The amount of NYT and PYT minted (the amounts are equal)
    function enterWithVaultShares(
        address nytRecipient,
        address pytRecipient,
        address vault,
        IxPYT xPYT,
        uint256 vaultSharesAmount
    ) external virtual nonReentrant returns (uint256 mintAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        if (vaultSharesAmount == 0) {
            return 0;
        }

        // only supported if vault shares are ERC20
        if (!vaultSharesIsERC20()) {
            revert Error_VaultSharesNotERC20();
        }

        /// -----------------------------------------------------------------------
        /// State updates & effects
        /// -----------------------------------------------------------------------

        // mint PYT and NYT
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        mintAmount = _vaultSharesAmountToUnderlyingAmount(
            vault,
            vaultSharesAmount,
            updatedPricePerVaultShare
        );
        _enter(
            nytRecipient,
            pytRecipient,
            vault,
            xPYT,
            mintAmount,
            updatedPricePerVaultShare
        );

        // transfer vault tokens from msg.sender to address(this)
        ERC20(vault).safeTransferFrom(
            msg.sender,
            address(this),
            vaultSharesAmount
        );

        emit EnterWithVaultShares(
            msg.sender,
            nytRecipient,
            pytRecipient,
            vault,
            xPYT,
            vaultSharesAmount
        );
    }

    /// @notice Converts NegativeYieldToken and PerpetualYieldToken to underlying tokens.
    /// The amount of NYT and PYT burned will be equal to the underlying token amount.
    /// @dev The underlying tokens will be immediately withdrawn from the specified vault.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// revert.
    /// @param recipient The recipient of the minted NYT and PYT
    /// @param vault The vault to mint NYT and PYT for
    /// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
    /// @param underlyingAmount The amount of underlying tokens requested
    /// @return burnAmount The amount of NYT and PYT burned (the amounts are equal)
    function exitToUnderlying(
        address recipient,
        address vault,
        IxPYT xPYT,
        uint256 underlyingAmount
    ) external virtual nonReentrant returns (uint256 burnAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        if (underlyingAmount == 0) {
            return 0;
        }

        /// -----------------------------------------------------------------------
        /// State updates & effects
        /// -----------------------------------------------------------------------

        // burn PYT and NYT
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        burnAmount = underlyingAmount;
        _exit(vault, xPYT, underlyingAmount, updatedPricePerVaultShare);

        // withdraw underlying from vault to recipient
        // don't check balance since user can just withdraw slightly less
        // saves gas this way
        underlyingAmount = _withdrawFromVault(
            recipient,
            vault,
            underlyingAmount,
            updatedPricePerVaultShare,
            false
        );

        emit ExitToUnderlying(
            msg.sender,
            recipient,
            vault,
            xPYT,
            underlyingAmount
        );
    }

    /// @notice Converts NegativeYieldToken and PerpetualYieldToken to vault share tokens.
    /// The amount of NYT and PYT burned will be equal to the underlying token amount.
    /// @dev Only available if vault shares are transferrable ERC20 tokens.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// revert.
    /// @param recipient The recipient of the minted NYT and PYT
    /// @param vault The vault to mint NYT and PYT for
    /// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
    /// @param vaultSharesAmount The amount of vault share tokens requested
    /// @return burnAmount The amount of NYT and PYT burned (the amounts are equal)
    function exitToVaultShares(
        address recipient,
        address vault,
        IxPYT xPYT,
        uint256 vaultSharesAmount
    ) external virtual nonReentrant returns (uint256 burnAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        if (vaultSharesAmount == 0) {
            return 0;
        }

        // only supported if vault shares are ERC20
        if (!vaultSharesIsERC20()) {
            revert Error_VaultSharesNotERC20();
        }

        /// -----------------------------------------------------------------------
        /// State updates & effects
        /// -----------------------------------------------------------------------

        // burn PYT and NYT
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        burnAmount = _vaultSharesAmountToUnderlyingAmountRoundingUp(
            vault,
            vaultSharesAmount,
            updatedPricePerVaultShare
        );
        _exit(vault, xPYT, burnAmount, updatedPricePerVaultShare);

        // transfer vault tokens to recipient
        ERC20(vault).safeTransfer(recipient, vaultSharesAmount);

        emit ExitToVaultShares(
            msg.sender,
            recipient,
            vault,
            xPYT,
            vaultSharesAmount
        );
    }

    /// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, in the underlying token.
    /// @dev If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// revert.
    /// @param recipient The recipient of the yield
    /// @param vault The vault to claim yield from
    /// @return yieldAmount The amount of yield claimed, in underlying tokens
    function claimYieldInUnderlying(address recipient, address vault)
        external
        virtual
        nonReentrant
        returns (uint256 yieldAmount)
    {
        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // update storage variables and compute yield amount
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        yieldAmount = _claimYield(vault, updatedPricePerVaultShare);

        // withdraw yield
        if (yieldAmount != 0) {
            /// -----------------------------------------------------------------------
            /// Effects
            /// -----------------------------------------------------------------------

            (uint8 fee, address protocolFeeRecipient) = factory
                .protocolFeeInfo();

            if (fee != 0) {
                uint256 protocolFee = (yieldAmount * fee) / 1000;
                unchecked {
                    // can't underflow since fee < 256
                    yieldAmount -= protocolFee;
                }

                if (vaultSharesIsERC20()) {
                    // vault shares are in ERC20
                    // do share transfer
                    protocolFee = _underlyingAmountToVaultSharesAmount(
                        vault,
                        protocolFee,
                        updatedPricePerVaultShare
                    );
                    uint256 vaultSharesBalance = ERC20(vault).balanceOf(
                        address(this)
                    );
                    if (protocolFee > vaultSharesBalance) {
                        protocolFee = vaultSharesBalance;
                    }
                    if (protocolFee != 0) {
                        ERC20(vault).safeTransfer(
                            protocolFeeRecipient,
                            protocolFee
                        );
                    }
                } else {
                    // vault shares are not in ERC20
                    // withdraw underlying from vault
                    // checkBalance is set to true to prevent getting stuck
                    // due to rounding errors
                    if (protocolFee != 0) {
                        _withdrawFromVault(
                            protocolFeeRecipient,
                            vault,
                            protocolFee,
                            updatedPricePerVaultShare,
                            true
                        );
                    }
                }
            }

            // withdraw underlying to recipient
            // checkBalance is set to true to prevent getting stuck
            // due to rounding errors
            yieldAmount = _withdrawFromVault(
                recipient,
                vault,
                yieldAmount,
                updatedPricePerVaultShare,
                true
            );

            emit ClaimYieldInUnderlying(
                msg.sender,
                recipient,
                vault,
                yieldAmount
            );
        }
    }

    /// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, in vault shares.
    /// @dev Only available if vault shares are transferrable ERC20 tokens.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// revert.
    /// @param recipient The recipient of the yield
    /// @param vault The vault to claim yield from
    /// @return yieldAmount The amount of yield claimed, in vault shares
    function claimYieldInVaultShares(address recipient, address vault)
        external
        virtual
        nonReentrant
        returns (uint256 yieldAmount)
    {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        // only supported if vault shares are ERC20
        if (!vaultSharesIsERC20()) {
            revert Error_VaultSharesNotERC20();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // update storage variables and compute yield amount
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        yieldAmount = _claimYield(vault, updatedPricePerVaultShare);

        // withdraw yield
        if (yieldAmount != 0) {
            /// -----------------------------------------------------------------------
            /// Effects
            /// -----------------------------------------------------------------------

            // convert yieldAmount to be denominated in vault shares
            yieldAmount = _underlyingAmountToVaultSharesAmount(
                vault,
                yieldAmount,
                updatedPricePerVaultShare
            );

            (uint8 fee, address protocolFeeRecipient) = factory
                .protocolFeeInfo();
            uint256 vaultSharesBalance = getVaultShareBalance(vault);
            if (fee != 0) {
                uint256 protocolFee = (yieldAmount * fee) / 1000;
                protocolFee = protocolFee > vaultSharesBalance
                    ? vaultSharesBalance
                    : protocolFee;
                unchecked {
                    // can't underflow since fee < 256
                    yieldAmount -= protocolFee;
                }

                if (protocolFee > 0) {
                    ERC20(vault).safeTransfer(
                        protocolFeeRecipient,
                        protocolFee
                    );

                    vaultSharesBalance -= protocolFee;
                }
            }

            // transfer vault shares to recipient
            // check if vault shares is enough to prevent getting stuck
            // from rounding errors
            yieldAmount = yieldAmount > vaultSharesBalance
                ? vaultSharesBalance
                : yieldAmount;
            if (yieldAmount > 0) {
                ERC20(vault).safeTransfer(recipient, yieldAmount);
            }

            emit ClaimYieldInVaultShares(
                msg.sender,
                recipient,
                vault,
                yieldAmount
            );
        }
    }

    /// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, and immediately
    /// use the yield to mint NYT and PYT.
    /// @dev Introduced to save gas for xPYT compounding, since it avoids vault withdraws/transfers.
    /// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
    /// revert.
    /// @param nytRecipient The recipient of the minted NYT
    /// @param pytRecipient The recipient of the minted PYT
    /// @param vault The vault to claim yield from
    /// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
    /// @return yieldAmount The amount of yield claimed, in underlying tokens
    function claimYieldAndEnter(
        address nytRecipient,
        address pytRecipient,
        address vault,
        IxPYT xPYT
    ) external virtual nonReentrant returns (uint256 yieldAmount) {
        // update storage variables and compute yield amount
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        yieldAmount = _claimYield(vault, updatedPricePerVaultShare);

        // use yield to mint NYT and PYT
        if (yieldAmount != 0) {
            (uint8 fee, address protocolFeeRecipient) = factory
                .protocolFeeInfo();

            if (fee != 0) {
                uint256 protocolFee = (yieldAmount * fee) / 1000;
                unchecked {
                    // can't underflow since fee < 256
                    yieldAmount -= protocolFee;
                }

                if (vaultSharesIsERC20()) {
                    // vault shares are in ERC20
                    // do share transfer
                    protocolFee = _underlyingAmountToVaultSharesAmount(
                        vault,
                        protocolFee,
                        updatedPricePerVaultShare
                    );
                    uint256 vaultSharesBalance = ERC20(vault).balanceOf(
                        address(this)
                    );
                    if (protocolFee > vaultSharesBalance) {
                        protocolFee = vaultSharesBalance;
                    }
                    if (protocolFee != 0) {
                        ERC20(vault).safeTransfer(
                            protocolFeeRecipient,
                            protocolFee
                        );
                    }
                } else {
                    // vault shares are not in ERC20
                    // withdraw underlying from vault
                    // checkBalance is set to true to prevent getting stuck
                    // due to rounding errors
                    if (protocolFee != 0) {
                        _withdrawFromVault(
                            protocolFeeRecipient,
                            vault,
                            protocolFee,
                            updatedPricePerVaultShare,
                            true
                        );
                    }
                }
            }

            NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
            PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);

            if (address(xPYT) == address(0)) {
                // accrue yield to pytRecipient if they're not msg.sender
                // no need to do it if the recipient is msg.sender, since
                // we already accrued yield in _claimYield
                if (pytRecipient != msg.sender) {
                    _accrueYield(
                        vault,
                        pyt,
                        pytRecipient,
                        updatedPricePerVaultShare
                    );
                }
            } else {
                // accrue yield to xPYT contract since it gets minted PYT
                _accrueYield(
                    vault,
                    pyt,
                    address(xPYT),
                    updatedPricePerVaultShare
                );
            }

            // mint NYTs and PYTs
            nyt.gateMint(nytRecipient, yieldAmount);
            if (address(xPYT) == address(0)) {
                // mint raw PYT to recipient
                pyt.gateMint(pytRecipient, yieldAmount);
            } else {
                // mint PYT to xPYT contract
                pyt.gateMint(address(xPYT), yieldAmount);

                /// -----------------------------------------------------------------------
                /// Effects
                /// -----------------------------------------------------------------------

                // call sweep to mint xPYT using the PYT
                xPYT.sweep(pytRecipient);
            }

            emit ClaimYieldAndEnter(
                msg.sender,
                nytRecipient,
                pytRecipient,
                vault,
                xPYT,
                yieldAmount
            );
        }
    }

    /// -----------------------------------------------------------------------
    /// Getters
    /// -----------------------------------------------------------------------

    /// @notice Returns the NegativeYieldToken associated with a vault.
    /// @dev Returns non-zero value even if the contract hasn't been deployed yet.
    /// @param vault The vault to query
    /// @return The NegativeYieldToken address
    function getNegativeYieldTokenForVault(address vault)
        public
        view
        virtual
        returns (NegativeYieldToken)
    {
        return factory.getNegativeYieldToken(this, vault);
    }

    /// @notice Returns the PerpetualYieldToken associated with a vault.
    /// @dev Returns non-zero value even if the contract hasn't been deployed yet.
    /// @param vault The vault to query
    /// @return The PerpetualYieldToken address
    function getPerpetualYieldTokenForVault(address vault)
        public
        view
        virtual
        returns (PerpetualYieldToken)
    {
        return factory.getPerpetualYieldToken(this, vault);
    }

    /// @notice Returns the amount of yield claimable by a PerpetualYieldToken holder from a vault.
    /// Accounts for protocol fees.
    /// @param vault The vault to query
    /// @param user The PYT holder to query
    /// @return yieldAmount The amount of yield claimable
    function getClaimableYieldAmount(address vault, address user)
        external
        view
        virtual
        returns (uint256 yieldAmount)
    {
        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][user];
        if (userYieldPerTokenStored_ == 0) {
            // uninitialized account
            return 0;
        }
        yieldAmount = _getClaimableYieldAmount(
            vault,
            user,
            _computeYieldPerToken(vault, pyt, getPricePerVaultShare(vault)),
            userYieldPerTokenStored_,
            pyt.balanceOf(user)
        );
        (uint8 fee, ) = factory.protocolFeeInfo();
        if (fee != 0) {
            uint256 protocolFee = (yieldAmount * fee) / 1000;
            unchecked {
                // can't underflow since fee < 256
                yieldAmount -= protocolFee;
            }
        }
    }

    /// @notice Computes the latest yieldPerToken value for a vault.
    /// @param vault The vault to query
    /// @return The latest yieldPerToken value
    function computeYieldPerToken(address vault)
        external
        view
        virtual
        returns (uint256)
    {
        return
            _computeYieldPerToken(
                vault,
                getPerpetualYieldTokenForVault(vault),
                getPricePerVaultShare(vault)
            );
    }

    /// @notice Returns the underlying token of a vault.
    /// @param vault The vault to query
    /// @return The underlying token
    function getUnderlyingOfVault(address vault)
        public
        view
        virtual
        returns (ERC20);

    /// @notice Returns the amount of underlying tokens each share of a vault is worth.
    /// @param vault The vault to query
    /// @return The pricePerVaultShare value
    function getPricePerVaultShare(address vault)
        public
        view
        virtual
        returns (uint256);

    /// @notice Returns the amount of vault shares owned by the gate.
    /// @param vault The vault to query
    /// @return The gate's vault share balance
    function getVaultShareBalance(address vault)
        public
        view
        virtual
        returns (uint256);

    /// @return True if the vaults supported by this gate use transferrable ERC20 tokens
    /// to represent shares, false otherwise.
    function vaultSharesIsERC20() public pure virtual returns (bool);

    /// @notice Computes the ERC20 name of the NegativeYieldToken of a vault.
    /// @param vault The vault to query
    /// @return The ERC20 name
    function negativeYieldTokenName(address vault)
        external
        view
        virtual
        returns (string memory);

    /// @notice Computes the ERC20 symbol of the NegativeYieldToken of a vault.
    /// @param vault The vault to query
    /// @return The ERC20 symbol
    function negativeYieldTokenSymbol(address vault)
        external
        view
        virtual
        returns (string memory);

    /// @notice Computes the ERC20 name of the PerpetualYieldToken of a vault.
    /// @param vault The vault to query
    /// @return The ERC20 name
    function perpetualYieldTokenName(address vault)
        external
        view
        virtual
        returns (string memory);

    /// @notice Computes the ERC20 symbol of the NegativeYieldToken of a vault.
    /// @param vault The vault to query
    /// @return The ERC20 symbol
    function perpetualYieldTokenSymbol(address vault)
        external
        view
        virtual
        returns (string memory);

    /// -----------------------------------------------------------------------
    /// PYT transfer hook
    /// -----------------------------------------------------------------------

    /// @notice SHOULD NOT BE CALLED BY USERS, ONLY CALLED BY PERPETUAL YIELD TOKEN CONTRACTS
    /// @dev Called by PYT contracts deployed by this gate before each token transfer, in order to
    /// accrue the yield earned by the from & to accounts
    /// @param from The token transfer from account
    /// @param to The token transfer to account
    /// @param fromBalance The token balance of the from account before the transfer
    /// @param toBalance The token balance of the to account before the transfer
    function beforePerpetualYieldTokenTransfer(
        address from,
        address to,
        uint256 amount,
        uint256 fromBalance,
        uint256 toBalance
    ) external virtual {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        if (amount == 0) {
            return;
        }

        address vault = PerpetualYieldToken(msg.sender).vault();
        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        if (msg.sender != address(pyt)) {
            revert Error_SenderNotPerpetualYieldToken();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // accrue yield
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
        uint256 updatedYieldPerToken = _computeYieldPerToken(
            vault,
            pyt,
            updatedPricePerVaultShare
        );
        yieldPerTokenStored[vault] = updatedYieldPerToken;
        pricePerVaultShareStored[vault] = updatedPricePerVaultShare;

        // we know the from account must have held PYTs before
        // so we will always accrue the yield earned by the from account
        userAccruedYield[vault][from] =
            _getClaimableYieldAmount(
                vault,
                from,
                updatedYieldPerToken,
                userYieldPerTokenStored[vault][from],
                fromBalance
            ) +
            1;
        userYieldPerTokenStored[vault][from] = updatedYieldPerToken + 1;

        // the to account might not have held PYTs before
        // we only accrue yield if they have
        uint256 toUserYieldPerTokenStored = userYieldPerTokenStored[vault][to];
        if (toUserYieldPerTokenStored != 0) {
            // to account has held PYTs before
            userAccruedYield[vault][to] =
                _getClaimableYieldAmount(
                    vault,
                    to,
                    updatedYieldPerToken,
                    toUserYieldPerTokenStored,
                    toBalance
                ) +
                1;
        }
        userYieldPerTokenStored[vault][to] = updatedYieldPerToken + 1;
    }

    /// -----------------------------------------------------------------------
    /// Emergency exit
    /// -----------------------------------------------------------------------

    /// @notice Activates the emergency exit mode for a certain vault. Only callable by owner.
    /// @dev Activating emergency exit allows PYT/NYT holders to do single-sided burns to redeem the underlying
    /// collateral. This is to prevent cases where a large portion of PYT/NYT is locked up in a buggy/malicious contract
    /// and locks up the underlying collateral forever.
    /// @param vault The vault to activate emergency exit for
    /// @param pytPriceInUnderlying The amount of underlying asset burning each PYT can redeem. Scaled by PRECISION.
    function ownerActivateEmergencyExitForVault(
        address vault,
        uint96 pytPriceInUnderlying
    ) external virtual onlyOwner {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        // we only allow emergency exit to be activated once (until deactivation)
        // because if pytPriceInUnderlying is ever modified after activation
        // then PYT/NYT will become unbacked
        if (emergencyExitStatusOfVault[vault].activated) {
            revert Error_EmergencyExitAlreadyActivated();
        }

        // we need to ensure the PYT price value is within the range [0, PRECISION]
        if (pytPriceInUnderlying > PRECISION) {
            revert Error_InvalidInput();
        }

        // the PYT & NYT must have already been deployed
        NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
        if (address(nyt).code.length == 0) {
            revert Error_TokenPairNotDeployed();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        emergencyExitStatusOfVault[vault] = EmergencyExitStatus({
            activated: true,
            pytPriceInUnderlying: pytPriceInUnderlying
        });
    }

    /// @notice Deactivates the emergency exit mode for a certain vault. Only callable by owner.
    /// @param vault The vault to deactivate emergency exit for
    function ownerDeactivateEmergencyExitForVault(address vault)
        external
        virtual
        onlyOwner
    {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        // can only deactivate emergency exit when it's already activated
        if (!emergencyExitStatusOfVault[vault].activated) {
            revert Error_EmergencyExitNotActivated();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // reset the emergency exit status
        delete emergencyExitStatusOfVault[vault];
    }

    /// @notice Emergency exit NYTs into the underlying asset. Only callable when emergency exit has
    /// been activated for the vault.
    /// @param vault The vault to exit NYT for
    /// @param amount The amount of NYT to exit
    /// @param recipient The recipient of the underlying asset
    /// @return underlyingAmount The amount of underlying asset exited
    function emergencyExitNegativeYieldToken(
        address vault,
        uint256 amount,
        address recipient
    ) external virtual returns (uint256 underlyingAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        // ensure emergency exit is active
        EmergencyExitStatus memory status = emergencyExitStatusOfVault[vault];
        if (!status.activated) {
            revert Error_EmergencyExitNotActivated();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);

        // accrue yield
        _accrueYield(vault, pyt, msg.sender, updatedPricePerVaultShare);

        // burn NYT from the sender
        NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
        nyt.gateBurn(msg.sender, amount);

        /// -----------------------------------------------------------------------
        /// Effects
        /// -----------------------------------------------------------------------

        // compute how much of the underlying assets to give the recipient
        // rounds down
        underlyingAmount = FullMath.mulDiv(
            amount,
            PRECISION - status.pytPriceInUnderlying,
            PRECISION
        );

        // withdraw underlying from vault to recipient
        // don't check balance since user can just withdraw slightly less
        // saves gas this way
        underlyingAmount = _withdrawFromVault(
            recipient,
            vault,
            underlyingAmount,
            updatedPricePerVaultShare,
            false
        );
    }

    /// @notice Emergency exit PYTs into the underlying asset. Only callable when emergency exit has
    /// been activated for the vault.
    /// @param vault The vault to exit PYT for
    /// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
    /// @param amount The amount of PYT to exit
    /// @param recipient The recipient of the underlying asset
    /// @return underlyingAmount The amount of underlying asset exited
    function emergencyExitPerpetualYieldToken(
        address vault,
        IxPYT xPYT,
        uint256 amount,
        address recipient
    ) external virtual returns (uint256 underlyingAmount) {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        // ensure emergency exit is active
        EmergencyExitStatus memory status = emergencyExitStatusOfVault[vault];
        if (!status.activated) {
            revert Error_EmergencyExitNotActivated();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);

        // accrue yield
        _accrueYield(vault, pyt, msg.sender, updatedPricePerVaultShare);

        if (address(xPYT) == address(0)) {
            // burn raw PYT from sender
            pyt.gateBurn(msg.sender, amount);
        } else {
            /// -----------------------------------------------------------------------
            /// Effects
            /// -----------------------------------------------------------------------

            // convert xPYT to PYT then burn
            xPYT.withdraw(amount, address(this), msg.sender);
            pyt.gateBurn(address(this), amount);
        }

        /// -----------------------------------------------------------------------
        /// Effects
        /// -----------------------------------------------------------------------

        // compute how much of the underlying assets to give the recipient
        // rounds down
        underlyingAmount = FullMath.mulDiv(
            amount,
            status.pytPriceInUnderlying,
            PRECISION
        );

        // withdraw underlying from vault to recipient
        // don't check balance since user can just withdraw slightly less
        // saves gas this way
        underlyingAmount = _withdrawFromVault(
            recipient,
            vault,
            underlyingAmount,
            updatedPricePerVaultShare,
            false
        );
    }

    /// -----------------------------------------------------------------------
    /// Internal utilities
    /// -----------------------------------------------------------------------

    /// @dev Updates the yield earned globally and for a particular user.
    function _accrueYield(
        address vault,
        PerpetualYieldToken pyt,
        address user,
        uint256 updatedPricePerVaultShare
    ) internal virtual {
        uint256 updatedYieldPerToken = _computeYieldPerToken(
            vault,
            pyt,
            updatedPricePerVaultShare
        );
        uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][user];
        if (userYieldPerTokenStored_ != 0) {
            userAccruedYield[vault][user] =
                _getClaimableYieldAmount(
                    vault,
                    user,
                    updatedYieldPerToken,
                    userYieldPerTokenStored_,
                    pyt.balanceOf(user)
                ) +
                1;
        }
        yieldPerTokenStored[vault] = updatedYieldPerToken;
        pricePerVaultShareStored[vault] = updatedPricePerVaultShare;
        userYieldPerTokenStored[vault][user] = updatedYieldPerToken + 1;
    }

    /// @dev Mints PYTs and NYTs to the recipient given the amount of underlying deposited.
    function _enter(
        address nytRecipient,
        address pytRecipient,
        address vault,
        IxPYT xPYT,
        uint256 underlyingAmount,
        uint256 updatedPricePerVaultShare
    ) internal virtual {
        NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
        if (address(nyt).code.length == 0) {
            // token pair hasn't been deployed yet
            // do the deployment now
            // only need to check nyt since nyt and pyt are always deployed in pairs
            factory.deployYieldTokenPair(this, vault);
        }
        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // accrue yield
        _accrueYield(
            vault,
            pyt,
            address(xPYT) == address(0) ? pytRecipient : address(xPYT),
            updatedPricePerVaultShare
        );

        // mint NYTs and PYTs
        nyt.gateMint(nytRecipient, underlyingAmount);
        if (address(xPYT) == address(0)) {
            // mint raw PYT to recipient
            pyt.gateMint(pytRecipient, underlyingAmount);
        } else {
            // mint PYT to xPYT contract
            pyt.gateMint(address(xPYT), underlyingAmount);

            /// -----------------------------------------------------------------------
            /// Effects
            /// -----------------------------------------------------------------------

            // call sweep to mint xPYT using the PYT
            xPYT.sweep(pytRecipient);
        }
    }

    /// @dev Burns PYTs and NYTs from msg.sender given the amount of underlying withdrawn.
    function _exit(
        address vault,
        IxPYT xPYT,
        uint256 underlyingAmount,
        uint256 updatedPricePerVaultShare
    ) internal virtual {
        NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        if (address(nyt).code.length == 0) {
            revert Error_TokenPairNotDeployed();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // accrue yield
        _accrueYield(
            vault,
            pyt,
            address(xPYT) == address(0) ? msg.sender : address(this),
            updatedPricePerVaultShare
        );

        // burn NYTs and PYTs
        nyt.gateBurn(msg.sender, underlyingAmount);
        if (address(xPYT) == address(0)) {
            // burn raw PYT from sender
            pyt.gateBurn(msg.sender, underlyingAmount);
        } else {
            /// -----------------------------------------------------------------------
            /// Effects
            /// -----------------------------------------------------------------------

            // convert xPYT to PYT then burn
            xPYT.withdraw(underlyingAmount, address(this), msg.sender);
            pyt.gateBurn(address(this), underlyingAmount);
        }
    }

    /// @dev Updates storage variables for when a PYT holder claims the accrued yield.
    function _claimYield(address vault, uint256 updatedPricePerVaultShare)
        internal
        virtual
        returns (uint256 yieldAmount)
    {
        /// -----------------------------------------------------------------------
        /// Validation
        /// -----------------------------------------------------------------------

        PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
        if (address(pyt).code.length == 0) {
            revert Error_TokenPairNotDeployed();
        }

        /// -----------------------------------------------------------------------
        /// State updates
        /// -----------------------------------------------------------------------

        // accrue yield
        uint256 updatedYieldPerToken = _computeYieldPerToken(
            vault,
            pyt,
            updatedPricePerVaultShare
        );
        uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][
            msg.sender
        ];
        if (userYieldPerTokenStored_ != 0) {
            yieldAmount = _getClaimableYieldAmount(
                vault,
                msg.sender,
                updatedYieldPerToken,
                userYieldPerTokenStored_,
                pyt.balanceOf(msg.sender)
            );
        }
        yieldPerTokenStored[vault] = updatedYieldPerToken;
        pricePerVaultShareStored[vault] = updatedPricePerVaultShare;
        userYieldPerTokenStored[vault][msg.sender] = updatedYieldPerToken + 1;
        if (yieldAmount != 0) {
            userAccruedYield[vault][msg.sender] = 1;
        }
    }

    /// @dev Returns the amount of yield claimable by a PerpetualYieldToken holder from a vault.
    /// Assumes userYieldPerTokenStored_ != 0. Does not account for protocol fees.
    function _getClaimableYieldAmount(
        address vault,
        address user,
        uint256 updatedYieldPerToken,
        uint256 userYieldPerTokenStored_,
        uint256 userPYTBalance
    ) internal view virtual returns (uint256 yieldAmount) {
        unchecked {
            // the stored value is shifted by one
            uint256 actualUserYieldPerToken = userYieldPerTokenStored_ - 1;

            // updatedYieldPerToken - actualUserYieldPerToken won't underflow since we check updatedYieldPerToken > actualUserYieldPerToken
            yieldAmount = FullMath.mulDiv(
                userPYTBalance,
                updatedYieldPerToken > actualUserYieldPerToken
                    ? updatedYieldPerToken - actualUserYieldPerToken
                    : 0,
                PRECISION
            );

            uint256 accruedYield = userAccruedYield[vault][user];
            if (accruedYield > 1) {
                // won't overflow since the sum is at most the totalSupply of the vault's underlying, which
                // is at most 256 bits.
                // the stored accruedYield value is shifted by one
                yieldAmount += accruedYield - 1;
            }
        }
    }

    /// @dev Deposits underlying tokens into a vault
    /// @param underlying The underlying token to deposit
    /// @param underlyingAmount The amount of tokens to deposit
    /// @param vault The vault to deposit into
    function _depositIntoVault(
        ERC20 underlying,
        uint256 underlyingAmount,
        address vault
    ) internal virtual;

    /// @dev Withdraws underlying tokens from a vault
    /// @param recipient The recipient of the underlying tokens
    /// @param vault The vault to withdraw from
    /// @param underlyingAmount The amount of tokens to withdraw
    /// @param pricePerVaultShare The latest price per vault share value
    /// @param checkBalance Set to true to withdraw the entire balance if we're trying
    /// to withdraw more than the balance (due to rounding errors)
    /// @return withdrawnUnderlyingAmount The amount of underlying tokens withdrawn
    function _withdrawFromVault(
        address recipient,
        address vault,
        uint256 underlyingAmount,
        uint256 pricePerVaultShare,
        bool checkBalance
    ) internal virtual returns (uint256 withdrawnUnderlyingAmount);

    /// @dev Converts a vault share amount into an equivalent underlying asset amount
    function _vaultSharesAmountToUnderlyingAmount(
        address vault,
        uint256 vaultSharesAmount,
        uint256 pricePerVaultShare
    ) internal view virtual returns (uint256);

    /// @dev Converts a vault share amount into an equivalent underlying asset amount, rounding up
    function _vaultSharesAmountToUnderlyingAmountRoundingUp(
        address vault,
        uint256 vaultSharesAmount,
        uint256 pricePerVaultShare
    ) internal view virtual returns (uint256);

    /// @dev Converts an underlying asset amount into an equivalent vault shares amount
    function _underlyingAmountToVaultSharesAmount(
        address vault,
        uint256 underlyingAmount,
        uint256 pricePerVaultShare
    ) internal view virtual returns (uint256);

    /// @dev Computes the latest yieldPerToken value for a vault.
    function _computeYieldPerToken(
        address vault,
        PerpetualYieldToken pyt,
        uint256 updatedPricePerVaultShare
    ) internal view virtual returns (uint256) {
        uint256 pytTotalSupply = pyt.totalSupply();
        if (pytTotalSupply == 0) {
            return yieldPerTokenStored[vault];
        }
        uint256 pricePerVaultShareStored_ = pricePerVaultShareStored[vault];
        if (updatedPricePerVaultShare <= pricePerVaultShareStored_) {
            // rounding error in vault share or no yield accrued
            return yieldPerTokenStored[vault];
        }
        uint256 newYieldPerTokenAccrued;
        unchecked {
            // can't underflow since we know updatedPricePerVaultShare > pricePerVaultShareStored_
            newYieldPerTokenAccrued = FullMath.mulDiv(
                updatedPricePerVaultShare - pricePerVaultShareStored_,
                getVaultShareBalance(vault),
                pytTotalSupply
            );
        }
        return yieldPerTokenStored[vault] + newYieldPerTokenAccrued;
    }
}

/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
    /// @notice Cast a uint256 to a uint160, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint160
    function toUint160(uint256 y) internal pure returns (uint160 z) {
        require((z = uint160(y)) == y);
    }

    /// @notice Cast a int256 to a int128, revert on overflow or underflow
    /// @param y The int256 to be downcasted
    /// @return z The downcasted integer, now type int128
    function toInt128(int256 y) internal pure returns (int128 z) {
        require((z = int128(y)) == y);
    }

    /// @notice Cast a uint256 to a int256, revert on overflow
    /// @param y The uint256 to be casted
    /// @return z The casted integer, now type int256
    function toInt256(uint256 y) internal pure returns (int256 z) {
        require(y < 2**255);
        z = int256(y);
    }
}

/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);

    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);

    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);

    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);

    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);

    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}

/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
    /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
    /// when accessed externally.
    /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
    /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
    /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
    /// boundary.
    /// observationIndex The index of the last oracle observation that was written,
    /// observationCardinality The current maximum number of observations stored in the pool,
    /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
    /// feeProtocol The protocol fee for both tokens of the pool.
    /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
    /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
    /// unlocked Whether the pool is currently locked to reentrancy
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal0X128() external view returns (uint256);

    /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal1X128() external view returns (uint256);

    /// @notice The amounts of token0 and token1 that are owed to the protocol
    /// @dev Protocol fees will never exceed uint128 max in either token
    function protocolFees() external view returns (uint128 token0, uint128 token1);

    /// @notice The currently in range liquidity available to the pool
    /// @dev This value has no relationship to the total liquidity across all ticks
    function liquidity() external view returns (uint128);

    /// @notice Look up information about a specific tick in the pool
    /// @param tick The tick to look up
    /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
    /// tick upper,
    /// liquidityNet how much liquidity changes when the pool price crosses the tick,
    /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
    /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
    /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
    /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
    /// secondsOutside the seconds spent on the other side of the tick from the current tick,
    /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
    /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
    /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
    /// a specific position.
    function ticks(int24 tick)
        external
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint256 feeGrowthOutside0X128,
            uint256 feeGrowthOutside1X128,
            int56 tickCumulativeOutside,
            uint160 secondsPerLiquidityOutsideX128,
            uint32 secondsOutside,
            bool initialized
        );

    /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
    function tickBitmap(int16 wordPosition) external view returns (uint256);

    /// @notice Returns the information about a position by the position's key
    /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
    /// @return _liquidity The amount of liquidity in the position,
    /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
    /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
    /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
    /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    /// @notice Returns data about a specific observation index
    /// @param index The element of the observations array to fetch
    /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
    /// ago, rather than at a specific index in the array.
    /// @return blockTimestamp The timestamp of the observation,
    /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
    /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
    /// Returns initialized whether the observation has been initialized and the values are safe to use
    function observations(uint256 index)
        external
        view
        returns (
            uint32 blockTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,
            bool initialized
        );
}

/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
    /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
    /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
    /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
    /// you must call it with secondsAgos = [3600, 0].
    /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
    /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
    /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
    /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
    /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
    /// timestamp
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
    /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
    /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
    /// snapshot is taken and the second snapshot is taken.
    /// @param tickLower The lower tick of the range
    /// @param tickUpper The upper tick of the range
    /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
    /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
    /// @return secondsInside The snapshot of seconds per liquidity for the range
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        returns (
            int56 tickCumulativeInside,
            uint160 secondsPerLiquidityInsideX128,
            uint32 secondsInside
        );
}

/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
    /// @notice Sets the initial price for the pool
    /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
    /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
    function initialize(uint160 sqrtPriceX96) external;

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Collects tokens owed to a position
    /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
    /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
    /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
    /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
    /// @param recipient The address which should receive the fees collected
    /// @param tickLower The lower tick of the position for which to collect fees
    /// @param tickUpper The upper tick of the position for which to collect fees
    /// @param amount0Requested How much token0 should be withdrawn from the fees owed
    /// @param amount1Requested How much token1 should be withdrawn from the fees owed
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);

    /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
    /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
    /// @dev Fees must be collected separately via a call to #collect
    /// @param tickLower The lower tick of the position for which to burn liquidity
    /// @param tickUpper The upper tick of the position for which to burn liquidity
    /// @param amount How much liquidity to burn
    /// @return amount0 The amount of token0 sent to the recipient
    /// @return amount1 The amount of token1 sent to the recipient
    function burn(
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Swap token0 for token1, or token1 for token0
    /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
    /// @param recipient The address to receive the output of the swap
    /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
    /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
    /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
    /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
    /// @param data Any data to be passed through to the callback
    /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
    /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
    /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
    /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
    /// with 0 amount{0,1} and sending the donation amount(s) from the callback
    /// @param recipient The address which will receive the token0 and token1 amounts
    /// @param amount0 The amount of token0 to send
    /// @param amount1 The amount of token1 to send
    /// @param data Any data to be passed through to the callback
    function flash(
        address recipient,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external;

    /// @notice Increase the maximum number of price and liquidity observations that this pool will store
    /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
    /// the input observationCardinalityNext.
    /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
    /// @notice Set the denominator of the protocol's % share of the fees
    /// @param feeProtocol0 new protocol fee for token0 of the pool
    /// @param feeProtocol1 new protocol fee for token1 of the pool
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

    /// @notice Collect the protocol fee accrued to the pool
    /// @param recipient The address to which collected protocol fees should be sent
    /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
    /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
    /// @return amount0 The protocol fee collected in token0
    /// @return amount1 The protocol fee collected in token1
    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);
}

/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
    /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
    /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
    /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
    /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
    event Initialize(uint160 sqrtPriceX96, int24 tick);

    /// @notice Emitted when liquidity is minted for a given position
    /// @param sender The address that minted the liquidity
    /// @param owner The owner of the position and recipient of any minted liquidity
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity minted to the position range
    /// @param amount0 How much token0 was required for the minted liquidity
    /// @param amount1 How much token1 was required for the minted liquidity
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when fees are collected by the owner of a position
    /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
    /// @param owner The owner of the position for which fees are collected
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount0 The amount of token0 fees collected
    /// @param amount1 The amount of token1 fees collected
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    /// @notice Emitted when a position's liquidity is removed
    /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
    /// @param owner The owner of the position for which liquidity is removed
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity to remove
    /// @param amount0 The amount of token0 withdrawn
    /// @param amount1 The amount of token1 withdrawn
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick
    );

    /// @notice Emitted by the pool for any flashes of token0/token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the tokens from flash
    /// @param amount0 The amount of token0 that was flashed
    /// @param amount1 The amount of token1 that was flashed
    /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
    /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
    event Flash(
        address indexed sender,
        address indexed recipient,
        uint256 amount0,
        uint256 amount1,
        uint256 paid0,
        uint256 paid1
    );

    /// @notice Emitted by the pool for increases to the number of observations that can be stored
    /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
    /// just before a mint/swap/burn.
    /// @param observationCardinalityNextOld The previous value of the next observation cardinality
    /// @param observationCardinalityNextNew The updated value of the next observation cardinality
    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld,
        uint16 observationCardinalityNextNew
    );

    /// @notice Emitted when the protocol fee is changed by the pool
    /// @param feeProtocol0Old The previous value of the token0 protocol fee
    /// @param feeProtocol1Old The previous value of the token1 protocol fee
    /// @param feeProtocol0New The updated value of the token0 protocol fee
    /// @param feeProtocol1New The updated value of the token1 protocol fee
    event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

    /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
    /// @param sender The address that collects the protocol fees
    /// @param recipient The address that receives the collected protocol fees
    /// @param amount0 The amount of token0 protocol fees that is withdrawn
    /// @param amount0 The amount of token1 protocol fees that is withdrawn
    event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}

/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
    IUniswapV3PoolImmutables,
    IUniswapV3PoolState,
    IUniswapV3PoolDerivedState,
    IUniswapV3PoolActions,
    IUniswapV3PoolOwnerActions,
    IUniswapV3PoolEvents
{

}

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Swapper
/// @author zefram.eth
/// @notice Abstract contract for swapping between xPYTs/NYTs and their underlying asset by
/// swapping via an external DEX and minting/burning xPYT/NYT.
/// @dev Swapper supports two-hop swaps where one of the swaps is an 0x swap between two regular tokens,
/// which enables swapping any supported token into any xPYT/NYT. Two-hop swaps are done by chaining
/// two calls together via Multicall and setting the recipient of the first swap to the Swapper.
abstract contract Swapper is
    Multicall,
    SelfPermit,
    ReentrancyGuard,
    BoringOwnable
{
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeTransferLib for ERC20;

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error Error_SameToken();
    error Error_PastDeadline();
    error Error_ZeroExSwapFailed();
    error Error_InsufficientOutput();
    error Error_ProtocolFeeRecipientIsZero();

    /// -----------------------------------------------------------------------
    /// Events
    /// -----------------------------------------------------------------------

    event SetProtocolFee(ProtocolFeeInfo protocolFeeInfo_);

    /// -----------------------------------------------------------------------
    /// Structs
    /// -----------------------------------------------------------------------

    /// @param gate The Gate used by the xPYT/NYT
    /// @param vault The yield-bearing vault used by the xPYT/NYT
    /// @param underlying The underlying asset of the xPYT/NYT
    /// @param nyt The NYT contract linked to the xPYT/NYT being swapped
    /// @param pyt The PYT contract linked to the xPYT/NYT being swapped
    /// @param xPYT The xPYT contract linked to the xPYT/NYT being swapped
    /// @param tokenAmountIn The amount of token input
    /// @param minAmountOut The minimum acceptable token output amount, used for slippage checking.
    /// @param recipient The recipient of the token output
    /// @param useSwapperBalance Set to true to use the Swapper's token balance as token input, in which
    /// case `tokenAmountIn` will be overriden to the balance.
    /// @param usePYT Set to true to use raw PYT as the input/output token instead of xPYT. Ignored
    /// when swapping from the underlying to NYT.
    /// @param deadline The Unix timestamp (in seconds) after which the call will be reverted
    /// @param extraArgs Used for providing extra input parameters for different protocols/use cases
    struct SwapArgs {
        Gate gate;
        address vault;
        ERC20 underlying;
        ERC20 nyt;
        ERC20 pyt;
        IxPYT xPYT;
        uint256 tokenAmountIn;
        uint256 minAmountOut;
        address recipient;
        bool useSwapperBalance;
        bool usePYT;
        uint256 deadline;
        bytes extraArgs;
    }

    /// @param fee The fee value. Each increment represents 0.01%, so max is 2.55% (8 bits)
    /// @param recipient The address that will receive the protocol fees
    struct ProtocolFeeInfo {
        uint8 fee;
        address recipient;
    }

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    /// @notice The 0x proxy contract used for 0x swaps
    address public immutable zeroExProxy;

    /// @notice The Wrapped Ethereum contract
    WETH public immutable weth;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    /// @notice The protocol fee and the fee recipient address.
    ProtocolFeeInfo public protocolFeeInfo;

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(
        address zeroExProxy_,
        WETH weth_,
        ProtocolFeeInfo memory protocolFeeInfo_
    ) {
        zeroExProxy = zeroExProxy_;
        weth = weth_;

        if (
            protocolFeeInfo_.fee != 0 &&
            protocolFeeInfo_.recipient == address(0)
        ) {
            revert Error_ProtocolFeeRecipientIsZero();
        }
        protocolFeeInfo = protocolFeeInfo_;
        emit SetProtocolFee(protocolFeeInfo_);
    }

    /// -----------------------------------------------------------------------
    /// Swaps
    /// -----------------------------------------------------------------------

    /// @notice Swaps the underlying asset of an NYT into the NYT
    /// @param args The input arguments (see SwapArgs definition)
    /// @return tokenAmountOut The amount of token output
    function swapUnderlyingToNyt(SwapArgs calldata args)
        external
        payable
        virtual
        returns (uint256 tokenAmountOut);

    /// @notice Swaps the underlying asset of an xPYT into the xPYT
    /// @param args The input arguments (see SwapArgs definition)
    /// @return tokenAmountOut The amount of token output
    function swapUnderlyingToXpyt(SwapArgs calldata args)
        external
        payable
        virtual
        returns (uint256 tokenAmountOut);

    /// @notice Swaps an NYT to its underlying asset
    /// @param args The input arguments (see SwapArgs definition)
    /// @return tokenAmountOut The amount of token output
    function swapNytToUnderlying(SwapArgs calldata args)
        external
        payable
        virtual
        returns (uint256 tokenAmountOut);

    /// @notice Swaps an xPYT to its underlying asset
    /// @param args The input arguments (see SwapArgs definition)
    /// @return tokenAmountOut The amount of token output
    function swapXpytToUnderlying(SwapArgs calldata args)
        external
        payable
        virtual
        returns (uint256 tokenAmountOut);

    /// -----------------------------------------------------------------------
    /// 0x support
    /// -----------------------------------------------------------------------

    /// @notice Swaps between two regular tokens using 0x.
    /// @dev Used in conjuction with the 0x API https://www.0x.org/docs/api
    /// @param tokenIn The input token
    /// @param tokenAmountIn The amount of token input
    /// @param tokenOut The output token
    /// @param minAmountOut The minimum acceptable token output amount, used for slippage checking.
    /// @param recipient The recipient of the token output
    /// @param useSwapperBalance Set to true to use the Swapper's token balance as token input
    /// @param requireApproval Set to true to approve tokenIn to zeroExProxy
    /// @param deadline The Unix timestamp (in seconds) after which the call will be reverted
    /// @param swapData The call data to zeroExProxy to execute the swap, obtained from
    /// the https://api.0x.org/swap/v1/quote endpoint
    /// @return tokenAmountOut The amount of token output
    function doZeroExSwap(
        ERC20 tokenIn,
        uint256 tokenAmountIn,
        ERC20 tokenOut,
        uint256 minAmountOut,
        address recipient,
        bool useSwapperBalance,
        bool requireApproval,
        uint256 deadline,
        bytes calldata swapData
    ) external payable virtual nonReentrant returns (uint256 tokenAmountOut) {
        // check if input token equals output
        if (tokenIn == tokenOut) {
            revert Error_SameToken();
        }

        // check deadline
        if (block.timestamp > deadline) {
            revert Error_PastDeadline();
        }

        // transfer in input tokens
        if (!useSwapperBalance) {
            tokenIn.safeTransferFrom(msg.sender, address(this), tokenAmountIn);
        }

        // approve zeroExProxy
        if (requireApproval) {
            tokenIn.safeApprove(zeroExProxy, type(uint256).max);
        }

        // do swap via zeroExProxy
        (bool success, ) = zeroExProxy.call(swapData);
        if (!success) {
            revert Error_ZeroExSwapFailed();
        }

        // check slippage
        tokenAmountOut = tokenOut.balanceOf(address(this));
        if (tokenAmountOut < minAmountOut) {
            revert Error_InsufficientOutput();
        }

        // transfer output tokens to recipient
        if (recipient != address(this)) {
            tokenOut.safeTransfer(recipient, tokenAmountOut);
        }
    }

    /// -----------------------------------------------------------------------
    /// WETH support
    /// -----------------------------------------------------------------------

    /// @notice Wraps the user's ETH input into WETH
    /// @dev Should be used as part of a multicall to convert the user's ETH input into WETH
    /// so that it can be swapped into xPYT/NYT.
    function wrapEthInput() external payable {
        weth.deposit{value: msg.value}();
    }

    /// -----------------------------------------------------------------------
    /// Owner functions
    /// -----------------------------------------------------------------------

    /// @notice Updates the protocol fee and/or the protocol fee recipient.
    /// Only callable by the owner.
    /// @param protocolFeeInfo_ The new protocol fee info
    function ownerSetProtocolFee(ProtocolFeeInfo calldata protocolFeeInfo_)
        external
        virtual
        onlyOwner
    {
        if (
            protocolFeeInfo_.fee != 0 &&
            protocolFeeInfo_.recipient == address(0)
        ) {
            revert Error_ProtocolFeeRecipientIsZero();
        }
        protocolFeeInfo = protocolFeeInfo_;

        emit SetProtocolFee(protocolFeeInfo_);
    }
}

/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
    using Bytes32AddressLib for bytes32;

    bytes32 internal constant POOL_INIT_CODE_HASH =
        0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;

    /// @notice The identifying key of the pool
    struct PoolKey {
        address token0;
        address token1;
        uint24 fee;
    }

    /// @notice Returns PoolKey: the ordered tokens with the matched fee levels
    /// @param tokenA The first token of a pool, unsorted
    /// @param tokenB The second token of a pool, unsorted
    /// @param fee The fee level of the pool
    /// @return Poolkey The pool details with ordered token0 and token1 assignments
    function getPoolKey(
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal pure returns (PoolKey memory) {
        if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
        return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
    }

    /// @notice Deterministically computes the pool address given the factory and PoolKey
    /// @param factory The Uniswap V3 factory contract address
    /// @param key The PoolKey
    /// @return pool The contract address of the V3 pool
    function computeAddress(address factory, PoolKey memory key)
        internal
        pure
        returns (address pool)
    {
        require(key.token0 < key.token1);
        pool = keccak256(
            abi.encodePacked(
                hex"ff",
                factory,
                keccak256(abi.encode(key.token0, key.token1, key.fee)),
                POOL_INIT_CODE_HASH
            )
        ).fromLast20Bytes();
    }
}

/// @title UniswapV3Swapper
/// @author zefram.eth
/// @notice Swapper that uses Uniswap V3 to swap between xPYTs/NYTs
contract UniswapV3Swapper is Swapper, IUniswapV3SwapCallback {
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeCast for uint256;
    using SafeTransferLib for ERC20;
    using SafeTransferLib for IxPYT;

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error Error_NotUniswapV3Pool();
    error Error_BothTokenDeltasAreZero();

    /// -----------------------------------------------------------------------
    /// Structs
    /// -----------------------------------------------------------------------

    struct SwapCallbackData {
        ERC20 tokenIn;
        ERC20 tokenOut;
        uint24 fee;
    }

    /// -----------------------------------------------------------------------
    /// Constants
    /// -----------------------------------------------------------------------

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick + 1. Equivalent to getSqrtRatioAtTick(MIN_TICK) + 1
    /// Copied from v3-core/libraries/TickMath.sol
    uint160 internal constant MIN_SQRT_RATIO_PLUS_ONE = 4295128740;

    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick - 1. Equivalent to getSqrtRatioAtTick(MAX_TICK) - 1
    /// Copied from v3-core/libraries/TickMath.sol
    uint160 internal constant MAX_SQRT_RATIO_MINUS_ONE =
        1461446703485210103287273052203988822378723970341;

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    /// @notice The official Uniswap V3 factory address
    address public immutable uniswapV3Factory;

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(
        address zeroExProxy_,
        WETH weth_,
        ProtocolFeeInfo memory protocolFeeInfo_,
        address uniswapV3Factory_
    ) Swapper(zeroExProxy_, weth_, protocolFeeInfo_) {
        uniswapV3Factory = uniswapV3Factory_;
    }

    /// -----------------------------------------------------------------------
    /// Swaps
    /// -----------------------------------------------------------------------

    /// @inheritdoc Swapper
    /// @dev extraArg = (uint24 fee)
    /// fee: The fee tier of the Uniswap V3 pool to use
    function swapUnderlyingToNyt(SwapArgs calldata args)
        external
        payable
        virtual
        override
        nonReentrant
        returns (uint256 tokenAmountOut)
    {
        // check deadline
        if (block.timestamp > args.deadline) {
            revert Error_PastDeadline();
        }

        uint256 xPYTMinted;
        {
            // determine token input amount
            uint256 tokenAmountIn = args.useSwapperBalance
                ? args.underlying.balanceOf(address(this))
                : args.tokenAmountIn;

            // transfer underlying from sender
            if (!args.useSwapperBalance) {
                args.underlying.safeTransferFrom(
                    msg.sender,
                    address(this),
                    tokenAmountIn
                );
            }

            // take protocol fee
            ProtocolFeeInfo memory protocolFeeInfo_ = protocolFeeInfo;
            if (protocolFeeInfo_.fee > 0) {
                uint256 feeAmount = (tokenAmountIn * protocolFeeInfo_.fee) /
                    10000;
                if (feeAmount > 0) {
                    // deduct fee from token input
                    tokenAmountIn -= feeAmount;

                    // transfer fee to recipient
                    args.underlying.safeTransfer(
                        protocolFeeInfo_.recipient,
                        feeAmount
                    );
                }
            }

            // add token output from minting to result
            tokenAmountOut = tokenAmountIn;

            // use underlying to mint xPYT & NYT
            xPYTMinted = args.xPYT.previewDeposit(tokenAmountIn);
            if (
                args.underlying.allowance(address(this), address(args.gate)) <
                tokenAmountIn
            ) {
                args.underlying.safeApprove(
                    address(args.gate),
                    type(uint256).max
                );
            }
            args.gate.enterWithUnderlying(
                args.recipient, // nytRecipient
                address(this), // pytRecipient
                args.vault,
                args.xPYT,
                tokenAmountIn
            );
        }

        // swap xPYT to NYT
        {
            uint24 fee = abi.decode(args.extraArgs, (uint24));
            // swap and add swap output to result
            tokenAmountOut += _swap(
                args.xPYT,
                xPYTMinted,
                args.nyt,
                fee,
                args.recipient
            );
        }

        // check slippage
        if (tokenAmountOut < args.minAmountOut) {
            revert Error_InsufficientOutput();
        }
    }

    /// @inheritdoc Swapper
    /// @dev extraArg = (uint24 fee)
    /// fee: The fee tier of the Uniswap V3 pool to use
    function swapUnderlyingToXpyt(SwapArgs calldata args)
        external
        payable
        virtual
        override
        nonReentrant
        returns (uint256 tokenAmountOut)
    {
        // check deadline
        if (block.timestamp > args.deadline) {
            revert Error_PastDeadline();
        }

        uint256 tokenAmountIn;
        {
            // determine token input and output amounts
            tokenAmountIn = args.useSwapperBalance
                ? args.underlying.balanceOf(address(this))
                : args.tokenAmountIn;

            // transfer underlying from sender
            if (!args.useSwapperBalance) {
                args.underlying.safeTransferFrom(
                    msg.sender,
                    address(this),
                    tokenAmountIn
                );
            }

            // take protocol fee
            ProtocolFeeInfo memory protocolFeeInfo_ = protocolFeeInfo;
            if (protocolFeeInfo_.fee > 0) {
                uint256 feeAmount = (tokenAmountIn * protocolFeeInfo_.fee) /
                    10000;
                if (feeAmount > 0) {
                    // deduct fee from token input
                    tokenAmountIn -= feeAmount;

                    // transfer fee to recipient
                    args.underlying.safeTransfer(
                        protocolFeeInfo_.recipient,
                        feeAmount
                    );
                }
            }

            // add token output from minting to result
            tokenAmountOut = args.usePYT
                ? tokenAmountIn
                : args.xPYT.previewDeposit(tokenAmountIn);

            // use underlying to mint xPYT & NYT
            if (
                args.underlying.allowance(address(this), address(args.gate)) <
                tokenAmountIn
            ) {
                args.underlying.safeApprove(
                    address(args.gate),
                    type(uint256).max
                );
            }
            args.gate.enterWithUnderlying(
                address(this), // nytRecipient
                args.recipient, // pytRecipient
                args.vault,
                args.usePYT ? IxPYT(address(0)) : args.xPYT,
                tokenAmountIn
            );
        }

        // swap NYT to xPYT
        uint256 swapOutput;
        {
            uint24 fee = abi.decode(args.extraArgs, (uint24));
            swapOutput = _swap(
                args.nyt,
                tokenAmountIn,
                args.xPYT,
                fee,
                args.usePYT ? address(this) : args.recipient // set recipient to this when using PYT in order to unwrap xPYT
            );
        }

        // unwrap xPYT if necessary
        if (args.usePYT) {
            tokenAmountOut += args.xPYT.redeem(
                swapOutput,
                args.recipient,
                address(this)
            );
        } else {
            tokenAmountOut += swapOutput;
        }

        // check slippage
        if (tokenAmountOut < args.minAmountOut) {
            revert Error_InsufficientOutput();
        }
    }

    /// @inheritdoc Swapper
    /// @dev extraArg = (uint24 fee, uint256 swapAmountIn)
    /// fee: The fee tier of the Uniswap V3 pool to use
    /// swapAmountIn: The amount of NYT to swap to xPYT
    function swapNytToUnderlying(SwapArgs calldata args)
        external
        payable
        virtual
        override
        nonReentrant
        returns (uint256 tokenAmountOut)
    {
        // check deadline
        if (block.timestamp > args.deadline) {
            revert Error_PastDeadline();
        }

        // transfer token input from sender
        uint256 tokenAmountIn = args.tokenAmountIn;
        if (!args.useSwapperBalance) {
            args.nyt.safeTransferFrom(msg.sender, address(this), tokenAmountIn);
        }

        // take protocol fee
        ProtocolFeeInfo memory protocolFeeInfo_ = protocolFeeInfo;
        if (protocolFeeInfo_.fee > 0) {
            uint256 feeAmount = (tokenAmountIn * protocolFeeInfo_.fee) / 10000;
            if (feeAmount > 0) {
                // deduct fee from token input
                tokenAmountIn -= feeAmount;

                // transfer fee to recipient
                args.nyt.safeTransfer(protocolFeeInfo_.recipient, feeAmount);
            }
        }

        // swap NYT to xPYT
        uint256 swapAmountOut;
        uint256 swapAmountIn;
        {
            uint24 fee;
            (fee, swapAmountIn) = abi.decode(args.extraArgs, (uint24, uint256));
            swapAmountOut = _swap(
                args.nyt,
                swapAmountIn,
                args.xPYT,
                fee,
                address(this)
            );

            // convert swap output xPYT amount into equivalent PYT amount
            swapAmountOut = args.xPYT.convertToAssets(swapAmountOut);
        }

        // determine token output amount
        uint256 remainingAmountIn = args.useSwapperBalance
            ? args.nyt.balanceOf(address(this))
            : tokenAmountIn - swapAmountIn;
        if (remainingAmountIn < swapAmountOut) {
            // NYT to burn < PYT balance
            tokenAmountOut = remainingAmountIn;
        } else {
            // NYT balance >= PYT to burn
            tokenAmountOut = swapAmountOut;
        }

        // check slippage
        if (tokenAmountOut < args.minAmountOut) {
            revert Error_InsufficientOutput();
        }

        // burn xPYT & NYT into underlying
        if (
            args.xPYT.allowance(address(this), address(args.gate)) <
            args.xPYT.previewWithdraw(tokenAmountOut)
        ) {
            args.xPYT.safeApprove(address(args.gate), type(uint256).max);
        }
        args.gate.exitToUnderlying(
            args.recipient,
            args.vault,
            args.xPYT,
            tokenAmountOut
        );

        // handle leftover tokens
        if (remainingAmountIn < swapAmountOut) {
            // NYT to burn < PYT balance
            // give leftover xPYT to recipient
            if (args.usePYT) {
                uint256 maxRedeemAmount = args.xPYT.maxRedeem(address(this));
                if (maxRedeemAmount != 0) {
                    args.xPYT.redeem(
                        args.xPYT.maxRedeem(address(this)),
                        args.recipient,
                        address(this)
                    );
                }
            } else {
                args.xPYT.safeTransfer(
                    args.recipient,
                    args.xPYT.balanceOf(address(this))
                );
            }
        } else {
            // NYT balance >= PYT to burn
            // give leftover NYT to recipient
            args.nyt.safeTransfer(
                args.recipient,
                args.nyt.balanceOf(address(this))
            );
        }
    }

    /// @inheritdoc Swapper
    /// @dev extraArg = (uint24 fee, uint256 swapAmountIn)
    /// fee: The fee tier of the Uniswap V3 pool to use
    /// swapAmountIn: The amount of xPYT to swap to NYT
    function swapXpytToUnderlying(SwapArgs calldata args)
        external
        payable
        virtual
        override
        nonReentrant
        returns (uint256 tokenAmountOut)
    {
        // check deadline
        if (block.timestamp > args.deadline) {
            revert Error_PastDeadline();
        }

        // transfer token input from sender
        uint256 tokenAmountIn = args.tokenAmountIn;
        if (!args.useSwapperBalance) {
            if (args.usePYT) {
                // transfer PYT from sender to this
                args.pyt.safeTransferFrom(
                    msg.sender,
                    address(this),
                    tokenAmountIn
                );

                // convert PYT input into xPYT and update tokenAmountIn
                if (
                    args.pyt.allowance(address(this), address(args.xPYT)) <
                    tokenAmountIn
                ) {
                    args.pyt.approve(address(args.xPYT), type(uint256).max);
                }
                tokenAmountIn = args.xPYT.deposit(tokenAmountIn, address(this));
            } else {
                args.xPYT.safeTransferFrom(
                    msg.sender,
                    address(this),
                    tokenAmountIn
                );
            }
        }

        // take protocol fee
        ProtocolFeeInfo memory protocolFeeInfo_ = protocolFeeInfo;
        if (protocolFeeInfo_.fee > 0) {
            uint256 feeAmount = (tokenAmountIn * protocolFeeInfo_.fee) / 10000;
            if (feeAmount > 0) {
                // deduct fee from token input
                tokenAmountIn -= feeAmount;

                // transfer fee to recipient
                args.xPYT.safeTransfer(protocolFeeInfo_.recipient, feeAmount);
            }
        }

        // swap xPYT to NYT
        uint256 swapAmountOut;
        uint256 swapAmountIn;
        {
            uint24 fee;
            (fee, swapAmountIn) = abi.decode(args.extraArgs, (uint24, uint256));
            swapAmountOut = _swap(
                args.xPYT,
                swapAmountIn,
                args.nyt,
                fee,
                address(this)
            );
        }

        // determine token output amount
        uint256 remainingAmountIn = args.useSwapperBalance
            ? args.xPYT.balanceOf(address(this))
            : tokenAmountIn - swapAmountIn;
        // convert remainingAmountIn from xPYT amount to equivalent PYT amount
        remainingAmountIn = args.xPYT.previewRedeem(remainingAmountIn);
        if (remainingAmountIn < swapAmountOut) {
            // PYT to burn < NYT balance
            tokenAmountOut = remainingAmountIn;
        } else {
            // PYT balance >= NYT to burn
            tokenAmountOut = swapAmountOut;
        }

        // check slippage
        if (tokenAmountOut < args.minAmountOut) {
            revert Error_InsufficientOutput();
        }

        // burn xPYT & NYT into underlying
        if (
            args.xPYT.allowance(address(this), address(args.gate)) <
            args.xPYT.previewWithdraw(tokenAmountOut)
        ) {
            args.xPYT.safeApprove(address(args.gate), type(uint256).max);
        }
        args.gate.exitToUnderlying(
            args.recipient,
            args.vault,
            args.xPYT,
            tokenAmountOut
        );

        // handle leftover tokens
        if (remainingAmountIn < swapAmountOut) {
            // PYT to burn < NYT balance
            // give leftover NYT to recipient
            args.nyt.safeTransfer(
                args.recipient,
                args.nyt.balanceOf(address(this))
            );
        } else {
            // PYT balance >= NYT to burn
            // give leftover xPYT to recipient
            if (args.usePYT) {
                uint256 maxRedeemAmount = args.xPYT.maxRedeem(address(this));
                if (maxRedeemAmount != 0) {
                    args.xPYT.redeem(
                        args.xPYT.maxRedeem(address(this)),
                        args.recipient,
                        address(this)
                    );
                }
            } else {
                args.xPYT.safeTransfer(
                    args.recipient,
                    args.xPYT.balanceOf(address(this))
                );
            }
        }
    }

    /// -----------------------------------------------------------------------
    /// Uniswap V3 support
    /// -----------------------------------------------------------------------

    /// @inheritdoc IUniswapV3SwapCallback
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external {
        // determine amount to pay
        uint256 amountToPay;
        if (amount0Delta > 0) {
            amountToPay = uint256(amount0Delta);
        } else if (amount1Delta > 0) {
            amountToPay = uint256(amount1Delta);
        } else {
            revert Error_BothTokenDeltasAreZero();
        }

        // decode callback data
        SwapCallbackData memory callbackData = abi.decode(
            data,
            (SwapCallbackData)
        );

        // verify sender
        address pool = PoolAddress.computeAddress(
            uniswapV3Factory,
            PoolAddress.getPoolKey(
                address(callbackData.tokenIn),
                address(callbackData.tokenOut),
                callbackData.fee
            )
        );
        if (msg.sender != address(pool)) {
            revert Error_NotUniswapV3Pool();
        }

        // pay tokens to the Uniswap V3 pool
        callbackData.tokenIn.safeTransfer(msg.sender, amountToPay);
    }

    /// -----------------------------------------------------------------------
    /// Internal utilities
    /// -----------------------------------------------------------------------

    /// @dev Use a Uniswap V3 pool to swap between two tokens
    /// @param tokenIn The input token
    /// @param tokenAmountIn The token input amount
    /// @param tokenOut The output token
    /// @param fee The fee tier of the pool to use
    /// @param recipient The address that will receive the token output
    function _swap(
        ERC20 tokenIn,
        uint256 tokenAmountIn,
        ERC20 tokenOut,
        uint24 fee,
        address recipient
    ) internal returns (uint256) {
        // get uniswap v3 pool
        IUniswapV3Pool uniPool = IUniswapV3Pool(
            PoolAddress.computeAddress(
                uniswapV3Factory,
                PoolAddress.getPoolKey(address(tokenIn), address(tokenOut), fee)
            )
        );

        // do swap
        bytes memory swapCallbackData = abi.encode(
            SwapCallbackData({tokenIn: tokenIn, tokenOut: tokenOut, fee: fee})
        );
        bool zeroForOne = address(tokenIn) < address(tokenOut);
        (int256 amount0, int256 amount1) = uniPool.swap(
            recipient,
            zeroForOne,
            tokenAmountIn.toInt256(),
            zeroForOne ? MIN_SQRT_RATIO_PLUS_ONE : MAX_SQRT_RATIO_MINUS_ONE,
            swapCallbackData
        );

        return uint256(-(zeroForOne ? amount1 : amount0));
    }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"zeroExProxy_","type":"address"},{"internalType":"contract WETH","name":"weth_","type":"address"},{"components":[{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct Swapper.ProtocolFeeInfo","name":"protocolFeeInfo_","type":"tuple"},{"internalType":"address","name":"uniswapV3Factory_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Error_BothTokenDeltasAreZero","type":"error"},{"inputs":[],"name":"Error_InsufficientOutput","type":"error"},{"inputs":[],"name":"Error_NotUniswapV3Pool","type":"error"},{"inputs":[],"name":"Error_PastDeadline","type":"error"},{"inputs":[],"name":"Error_ProtocolFeeRecipientIsZero","type":"error"},{"inputs":[],"name":"Error_SameToken","type":"error"},{"inputs":[],"name":"Error_ZeroExSwapFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"recipient","type":"address"}],"indexed":false,"internalType":"struct Swapper.ProtocolFeeInfo","name":"protocolFeeInfo_","type":"tuple"}],"name":"SetProtocolFee","type":"event"},{"inputs":[],"name":"claimOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"contract ERC20","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"useSwapperBalance","type":"bool"},{"internalType":"bool","name":"requireApproval","type":"bool"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"swapData","type":"bytes"}],"name":"doZeroExSwap","outputs":[{"internalType":"uint256","name":"tokenAmountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct Swapper.ProtocolFeeInfo","name":"protocolFeeInfo_","type":"tuple"}],"name":"ownerSetProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeInfo","outputs":[{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"recipient","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract Gate","name":"gate","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"contract ERC20","name":"nyt","type":"address"},{"internalType":"contract ERC20","name":"pyt","type":"address"},{"internalType":"contract IxPYT","name":"xPYT","type":"address"},{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"useSwapperBalance","type":"bool"},{"internalType":"bool","name":"usePYT","type":"bool"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"internalType":"struct Swapper.SwapArgs","name":"args","type":"tuple"}],"name":"swapNytToUnderlying","outputs":[{"internalType":"uint256","name":"tokenAmountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract Gate","name":"gate","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"contract ERC20","name":"nyt","type":"address"},{"internalType":"contract ERC20","name":"pyt","type":"address"},{"internalType":"contract IxPYT","name":"xPYT","type":"address"},{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"useSwapperBalance","type":"bool"},{"internalType":"bool","name":"usePYT","type":"bool"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"internalType":"struct Swapper.SwapArgs","name":"args","type":"tuple"}],"name":"swapUnderlyingToNyt","outputs":[{"internalType":"uint256","name":"tokenAmountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract Gate","name":"gate","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"contract ERC20","name":"nyt","type":"address"},{"internalType":"contract ERC20","name":"pyt","type":"address"},{"internalType":"contract IxPYT","name":"xPYT","type":"address"},{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"useSwapperBalance","type":"bool"},{"internalType":"bool","name":"usePYT","type":"bool"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"internalType":"struct Swapper.SwapArgs","name":"args","type":"tuple"}],"name":"swapUnderlyingToXpyt","outputs":[{"internalType":"uint256","name":"tokenAmountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract Gate","name":"gate","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"contract ERC20","name":"nyt","type":"address"},{"internalType":"contract ERC20","name":"pyt","type":"address"},{"internalType":"contract IxPYT","name":"xPYT","type":"address"},{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"useSwapperBalance","type":"bool"},{"internalType":"bool","name":"usePYT","type":"bool"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"internalType":"struct Swapper.SwapArgs","name":"args","type":"tuple"}],"name":"swapXpytToUnderlying","outputs":[{"internalType":"uint256","name":"tokenAmountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"},{"internalType":"bool","name":"direct","type":"bool"},{"internalType":"bool","name":"renounce","type":"bool"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniswapV3Factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"contract WETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wrapEthInput","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"zeroExProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

60e060405260016000553480156200001657600080fd5b506040516200425e3803806200425e833981016040819052620000399162000186565b600180546001600160a01b03191633908117909155604051859185918591906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a36001600160a01b03808416608052821660a052805160ff1615801590620000b2575060208101516001600160a01b0316155b15620000d15760405163fd15c34360e01b815260040160405180910390fd5b8051600380546020808501805160ff9095166001600160a81b031990931683176101006001600160a01b039687160217909355604080519283529251909316928101929092527fbb1e7f6bcb2654c0e4c0f3cfb23f810d2babfb459af4ad2efcd1de09b452ceab910160405180910390a15050506001600160a01b031660c052506200024f915050565b6001600160a01b03811681146200017157600080fd5b50565b805162000181816200015b565b919050565b60008060008084860360a08112156200019e57600080fd5b8551620001ab816200015b565b6020870151909550620001be816200015b565b93506040603f1982011215620001d357600080fd5b50604080519081016001600160401b03811182821017156200020557634e487b7160e01b600052604160045260246000fd5b604090815286015160ff811681146200021d57600080fd5b815260608601516200022f816200015b565b60208201529150620002446080860162000174565b905092959194509250565b60805160a05160c051613fbc620002a26000396000818161021001528181612f0c01526132030152600081816101a20152612d6f0152600081816103a20152818161223e01526122870152613fbc6000f3fe6080604052600436106101435760003560e01c80638e4856b8116100c0578063e30c397811610074578063e8ab6f2d11610059578063e8ab6f2d146103c4578063f3995c67146103cc578063fa461e33146103df57600080fd5b8063e30c397814610363578063e6d395411461039057600080fd5b8063ac9650d8116100a5578063ac9650d8146102cb578063aec0b543146102eb578063c2e3140a1461035057600080fd5b80638e4856b8146102a5578063aaa6864b146102b857600080fd5b80635b54918211610117578063694333a0116100fc578063694333a01461024557806388321738146102585780638da5cb5b1461027857600080fd5b80635b549182146101fe57806362304cce1461023257600080fd5b8062b25bfb14610148578063078dfbe71461016e5780633fc8cef3146101905780634e71e0c8146101e9575b600080fd5b61015b6101563660046135ed565b6103ff565b6040519081526020015b60405180910390f35b34801561017a57600080fd5b5061018e61018936600461365c565b6109ef565b005b34801561019c57600080fd5b506101c47f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610165565b3480156101f557600080fd5b5061018e610bdc565b34801561020a57600080fd5b506101c47f000000000000000000000000000000000000000000000000000000000000000081565b61015b6102403660046135ed565b610cf3565b61015b6102533660046135ed565b611988565b34801561026457600080fd5b5061018e6102733660046136a7565b611fb2565b34801561028457600080fd5b506001546101c49073ffffffffffffffffffffffffffffffffffffffff1681565b61015b6102b3366004613708565b6120f7565b61015b6102c63660046135ed565b612465565b6102de6102d93660046137ca565b612b49565b60405161016591906138b5565b3480156102f757600080fd5b506003546103229060ff811690610100900473ffffffffffffffffffffffffffffffffffffffff1682565b6040805160ff909316835273ffffffffffffffffffffffffffffffffffffffff909116602083015201610165565b61018e61035e366004613944565b612cbb565b34801561036f57600080fd5b506002546101c49073ffffffffffffffffffffffffffffffffffffffff1681565b34801561039c57600080fd5b506101c47f000000000000000000000000000000000000000000000000000000000000000081565b61018e612d6d565b61018e6103da366004613944565b612df0565b3480156103eb57600080fd5b5061018e6103fa3660046139a0565b612ea2565b60008054600114610471576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b60026000556101608201354211156104b5576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806104ca610140850161012086016139f3565b6104d8578360c00135610578565b6104e86060850160408601613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610554573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105789190613a2d565b905061058c610140850161012086016139f3565b6105c4576105c43330836105a66060890160408a01613a10565b73ffffffffffffffffffffffffffffffffffffffff16929190612fc4565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff1660208301521561066c578051600090612710906106149060ff1685613a75565b61061e9190613ab2565b9050801561066a576106308184613aed565b602083015190935061066a908261064d60608a0160408b01613a10565b73ffffffffffffffffffffffffffffffffffffffff169190613083565b505b909250829061068160c0860160a08701613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ef8b30f7836040518263ffffffff1660e01b81526004016106bb91815260200190565b602060405180830381865afa1580156106d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106fc9190613a2d565b92508161070f6060870160408801613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e3061073860208a018a613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156107a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107cc9190613a2d565b1015610830576108306107e26020870187613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6108136060890160408a01613a10565b73ffffffffffffffffffffffffffffffffffffffff169190613142565b61083d6020860186613a10565b73ffffffffffffffffffffffffffffffffffffffff166363f41f8261086a61012088016101008901613a10565b3061087b60408a0160208b01613a10565b61088b60c08b0160a08c01613a10565b60405160e086901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff94851660048201529284166024840152908316604483015290911660648201526084810185905260a4016020604051808303816000875af1158015610915573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109399190613a2d565b506000915061094e9050610180850185613b04565b81019061095b9190613b81565b905061099961097060c0860160a08701613a10565b836109816080880160608901613a10565b846109946101208a016101008b01613a10565b6131fb565b6109a39084613b9c565b9250508260e001358210156109e4576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001600055919050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a70576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610468565b8115610b955773ffffffffffffffffffffffffffffffffffffffff8316151580610a975750805b610afd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4f776e61626c653a207a65726f206164647265737300000000000000000000006044820152606401610468565b60015460405173ffffffffffffffffffffffffffffffffffffffff8086169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36001805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff000000000000000000000000000000000000000091821617909155600280549091169055505050565b6002805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055505050565b60025473ffffffffffffffffffffffffffffffffffffffff16338114610c5e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c657220213d2070656e64696e67206f776e65726044820152606401610468565b60015460405173ffffffffffffffffffffffffffffffffffffffff8084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36001805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff0000000000000000000000000000000000000000928316179055600280549091169055565b60008054600114610d60576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115610da4576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0820135610dbb610140840161012085016139f3565b61107b57610dd1610160840161014085016139f3565b1561106557610dec3330836105a660a0880160808901613a10565b80610dfd60a0850160808601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e30610e2960c0880160a08901613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610e99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ebd9190613a2d565b1015610fb557610ed360a0840160808501613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b3610efe60c0860160a08701613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1158015610f8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb39190613bb4565b505b610fc560c0840160a08501613a10565b6040517f6e553f650000000000000000000000000000000000000000000000000000000081526004810183905230602482015273ffffffffffffffffffffffffffffffffffffffff9190911690636e553f65906044016020604051808303816000875af115801561103a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061105e9190613a2d565b905061107b565b61107b3330836105a660c0880160a08901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215611106578051600090612710906110cb9060ff1685613a75565b6110d59190613ab2565b90508015611104576110e78184613aed565b6020830151909350611104908261064d60c0890160a08a01613a10565b505b60008080611118610180880188613b04565b8101906111259190613bd1565b9250905061115461113c60c0890160a08a01613a10565b8361114d60808b0160608c01613a10565b84306131fb565b92506000905061116c610140880161012089016139f3565b61117f5761117a8286613aed565b61121f565b61118f60c0880160a08901613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa1580156111fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061121f9190613a2d565b905061123160c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16634cdad506826040518263ffffffff1660e01b815260040161126b91815260200190565b602060405180830381865afa158015611288573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ac9190613a2d565b9050828110156112be578095506112c2565b8295505b8660e00135861015611300576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61131060c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16630a28a477876040518263ffffffff1660e01b815260040161134a91815260200190565b602060405180830381865afa158015611367573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138b9190613a2d565b61139b60c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e306113c460208c018c613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015611434573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114589190613a2d565b101561149f5761149f61146e6020890189613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61081360c08b0160a08c01613a10565b6114ac6020880188613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ca64c6114d96101208a016101008b01613a10565b6114e960408b0160208c01613a10565b6114f960c08c0160a08d01613a10565b60405160e085901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff938416600482015291831660248301529091166044820152606481018990526084016020604051808303816000875af115801561157b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159f9190613a2d565b50828110156116725761166d6115bd61012089016101008a01613a10565b6115cd60808a0160608b01613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611639573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061165d9190613a2d565b61064d60808b0160608c01613a10565b611978565b611684610160880161014089016139f3565b156118b357600061169b60c0890160a08a01613a10565b6040517fd905777e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff919091169063d905777e90602401602060405180830381865afa158015611707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061172b9190613a2d565b905080156118ad5761174360c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ba08765261176e60c08b0160a08c01613a10565b6040517fd905777e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff919091169063d905777e90602401602060405180830381865afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613a2d565b6118106101208c016101008d01613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b168152600481019290925273ffffffffffffffffffffffffffffffffffffffff1660248201523060448201526064016020604051808303816000875af1158015611887573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ab9190613a2d565b505b50611978565b6119786118c861012089016101008a01613a10565b6118d860c08a0160a08b01613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119689190613a2d565b61064d60c08b0160a08c01613a10565b5050600160005550919392505050565b600080546001146119f5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115611a39576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0820135611a50610140840161012085016139f3565b611a6a57611a6a3330836105a66080880160608901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215611af557805160009061271090611aba9060ff1685613a75565b611ac49190613ab2565b90508015611af357611ad68184613aed565b6020830151909350611af3908261064d6080890160608a01613a10565b505b60008080611b07610180880188613b04565b810190611b149190613bd1565b92509050611b3c611b2b6080890160608a01613a10565b8361114d60c08b0160a08c01613a10565b9250611b4e60c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff166307a2d13a846040518263ffffffff1660e01b8152600401611b8891815260200190565b602060405180830381865afa158015611ba5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bc99190613a2d565b925060009050611be1610140880161012089016139f3565b611bf457611bef8286613aed565b611c94565b611c046080880160608901613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c949190613a2d565b905082811015611ca657809550611caa565b8295505b8660e00135861015611ce8576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611cf860c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16630a28a477876040518263ffffffff1660e01b8152600401611d3291815260200190565b602060405180830381865afa158015611d4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d739190613a2d565b611d8360c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e30611dac60208c018c613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015611e1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e409190613a2d565b1015611e5657611e5661146e6020890189613a10565b611e636020880188613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ca64c611e906101208a016101008b01613a10565b611ea060408b0160208c01613a10565b611eb060c08c0160a08d01613a10565b60405160e085901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff938416600482015291831660248301529091166044820152606481018990526084016020604051808303816000875af1158015611f32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f569190613a2d565b5082811015611f9d57611f71610160880161014089016139f3565b15611f8857600061169b60c0890160a08a01613a10565b61166d6118c861012089016101008a01613a10565b6119786115bd61012089016101008a01613a10565b60015473ffffffffffffffffffffffffffffffffffffffff163314612033576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610468565b6120406020820182613bfb565b60ff16158015906120765750600061205e6040830160208401613a10565b73ffffffffffffffffffffffffffffffffffffffff16145b156120ad576040517ffd15c34300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060036120ba8282613c18565b9050507fbb1e7f6bcb2654c0e4c0f3cfb23f810d2babfb459af4ad2efcd1de09b452ceab816040516120ec9190613caa565b60405180910390a150565b60008054600114612164576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b600260005573ffffffffffffffffffffffffffffffffffffffff808a16908c16036121bb576040517fdc85d61400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b834211156121f5576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8561221c5761221c73ffffffffffffffffffffffffffffffffffffffff8c1633308d612fc4565b84156122835761228373ffffffffffffffffffffffffffffffffffffffff8c167f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613142565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1684846040516122cc929190613cef565b6000604051808303816000865af19150503d8060008114612309576040519150601f19603f3d011682016040523d82523d6000602084013e61230e565b606091505b5050905080612349576040517f965d94b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8b16906370a0823190602401602060405180830381865afa1580156123b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d79190613a2d565b915088821015612413576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff881630146124515761245173ffffffffffffffffffffffffffffffffffffffff8b168984613083565b5060016000559a9950505050505050505050565b600080546001146124d2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115612516576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061252a610140840161012085016139f3565b612538578260c001356125d8565b6125486060840160408501613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa1580156125b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d89190613a2d565b90506125ec610140840161012085016139f3565b612606576126063330836105a66060880160408901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215612691578051600090612710906126569060ff1685613a75565b6126609190613ab2565b9050801561268f576126728184613aed565b602083015190935061268f908261064d6060890160408a01613a10565b505b6126a3610160850161014086016139f3565b612737576126b760c0850160a08601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ef8b30f7836040518263ffffffff1660e01b81526004016126f191815260200190565b602060405180830381865afa15801561270e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127329190613a2d565b612739565b815b92508161274c6060860160408701613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e306127756020890189613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156127e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128099190613a2d565b10156128505761285061281f6020860186613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6108136060880160408901613a10565b61285d6020850185613a10565b73ffffffffffffffffffffffffffffffffffffffff166363f41f823061288b61012088016101008901613a10565b61289b6040890160208a01613a10565b6128ad6101608a016101408b016139f3565b6128c6576128c160c08a0160a08b01613a10565b6128c9565b60005b60405160e086901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff94851660048201529284166024840152908316604483015290911660648201526084810185905260a4016020604051808303816000875af1158015612953573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129779190613a2d565b50600090508061298b610180860186613b04565b8101906129989190613b81565b90506129ed6129ad6080870160608801613a10565b846129be60c0890160a08a01613a10565b846129d16101608b016101408c016139f3565b6129e7576109946101208b016101008c01613a10565b306131fb565b9150612a039050610160850161014086016139f3565b15612af257612a1860c0850160a08601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ba08765282612a4661012088016101008901613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b168152600481019290925273ffffffffffffffffffffffffffffffffffffffff1660248201523060448201526064016020604051808303816000875af1158015612abd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ae19190613a2d565b612aeb9084613b9c565b9250612aff565b612afc8184613b9c565b92505b8360e00135831015612b3d576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50506001600055919050565b60608167ffffffffffffffff811115612b6457612b64613cff565b604051908082528060200260200182016040528015612b9757816020015b6060815260200190600190039081612b825790505b50905060005b82811015612cb45760008030868685818110612bbb57612bbb613d2e565b9050602002810190612bcd9190613b04565b604051612bdb929190613cef565b600060405180830381855af49150503d8060008114612c16576040519150601f19603f3d011682016040523d82523d6000602084013e612c1b565b606091505b509150915081612c8157604481511015612c3457600080fd5b60048101905080806020019051810190612c4e9190613d5d565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104689190613e28565b80848481518110612c9457612c94613d2e565b602002602001018190525050508080612cac90613e3b565b915050612b9d565b5092915050565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152859073ffffffffffffffffffffffffffffffffffffffff88169063dd62ed3e90604401602060405180830381865afa158015612d2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d519190613a2d565b1015612d6557612d65868686868686612df0565b505050505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015612dd557600080fd5b505af1158015612de9573d6000803e3d6000fd5b5050505050565b6040517fd505accf000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018690526064810185905260ff8416608482015260a4810183905260c4810182905273ffffffffffffffffffffffffffffffffffffffff87169063d505accf9060e401600060405180830381600087803b158015612e8257600080fd5b505af1158015612e96573d6000803e3d6000fd5b50505050505050505050565b600080851315612eb3575083612ef5565b6000841315612ec3575082612ef5565b6040517f3fbd122600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612f0383850185613e73565b90506000612f477f0000000000000000000000000000000000000000000000000000000000000000612f428460000151856020015186604001516133e9565b61347b565b90503373ffffffffffffffffffffffffffffffffffffffff821614612f98576040517f1a13dcc000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151612fbb9073ffffffffffffffffffffffffffffffffffffffff163385613083565b50505050505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080612de9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610468565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061313c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610468565b50505050565b60006040517f095ea7b3000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061313c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f415050524f56455f4641494c45440000000000000000000000000000000000006044820152606401610468565b60008061322d7f0000000000000000000000000000000000000000000000000000000000000000612f428988886133e9565b9050600060405180606001604052808973ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018662ffffff168152506040516020016132c99190815173ffffffffffffffffffffffffffffffffffffffff90811682526020808401519091169082015260409182015162ffffff169181019190915260600190565b604051602081830303815290604052905060008673ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161090506000808473ffffffffffffffffffffffffffffffffffffffff1663128acb0888856133378e6135bb565b876133565773fffd8963efd1fc6a506488495d951d5263988d2561335d565b6401000276a45b896040518663ffffffff1660e01b815260040161337e959493929190613ee3565b60408051808303816000875af115801561339c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133c09190613f2a565b91509150826133cf57816133d1565b805b6133da90613f4e565b9b9a5050505050505050505050565b60408051606081018252600080825260208201819052918101919091528273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16111561343e579192915b506040805160608101825273ffffffffffffffffffffffffffffffffffffffff948516815292909316602083015262ffffff169181019190915290565b6000816020015173ffffffffffffffffffffffffffffffffffffffff16826000015173ffffffffffffffffffffffffffffffffffffffff16106134bd57600080fd5b8151602080840151604080860151815173ffffffffffffffffffffffffffffffffffffffff95861681860152949092168482015262ffffff90911660608085019190915281518085038201815260808501909252815191909201207fff0000000000000000000000000000000000000000000000000000000000000060a08401529085901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660a183015260b58201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d58201526135b49060f5016040516020818303038152906040528051906020012090565b9392505050565b60007f800000000000000000000000000000000000000000000000000000000000000082106135e957600080fd5b5090565b6000602082840312156135ff57600080fd5b813567ffffffffffffffff81111561361657600080fd5b82016101a081850312156135b457600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461364b57600080fd5b50565b801515811461364b57600080fd5b60008060006060848603121561367157600080fd5b833561367c81613629565b9250602084013561368c8161364e565b9150604084013561369c8161364e565b809150509250925092565b6000604082840312156136b957600080fd5b50919050565b60008083601f8401126136d157600080fd5b50813567ffffffffffffffff8111156136e957600080fd5b60208301915083602082850101111561370157600080fd5b9250929050565b6000806000806000806000806000806101208b8d03121561372857600080fd5b8a3561373381613629565b995060208b0135985060408b013561374a81613629565b975060608b0135965060808b013561376181613629565b955060a08b01356137718161364e565b945060c08b01356137818161364e565b935060e08b013592506101008b013567ffffffffffffffff8111156137a557600080fd5b6137b18d828e016136bf565b915080935050809150509295989b9194979a5092959850565b600080602083850312156137dd57600080fd5b823567ffffffffffffffff808211156137f557600080fd5b818501915085601f83011261380957600080fd5b81358181111561381857600080fd5b8660208260051b850101111561382d57600080fd5b60209290920196919550909350505050565b60005b8381101561385a578181015183820152602001613842565b8381111561313c5750506000910152565b6000815180845261388381602086016020860161383f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613928577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261391685835161386b565b945092850192908501906001016138dc565b5092979650505050505050565b60ff8116811461364b57600080fd5b60008060008060008060c0878903121561395d57600080fd5b863561396881613629565b95506020870135945060408701359350606087013561398681613935565b9598949750929560808101359460a0909101359350915050565b600080600080606085870312156139b657600080fd5b8435935060208501359250604085013567ffffffffffffffff8111156139db57600080fd5b6139e7878288016136bf565b95989497509550505050565b600060208284031215613a0557600080fd5b81356135b48161364e565b600060208284031215613a2257600080fd5b81356135b481613629565b600060208284031215613a3f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615613aad57613aad613a46565b500290565b600082613ae8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600082821015613aff57613aff613a46565b500390565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613b3957600080fd5b83018035915067ffffffffffffffff821115613b5457600080fd5b60200191503681900382131561370157600080fd5b803562ffffff81168114613b7c57600080fd5b919050565b600060208284031215613b9357600080fd5b6135b482613b69565b60008219821115613baf57613baf613a46565b500190565b600060208284031215613bc657600080fd5b81516135b48161364e565b60008060408385031215613be457600080fd5b613bed83613b69565b946020939093013593505050565b600060208284031215613c0d57600080fd5b81356135b481613935565b8135613c2381613935565b60ff811690508154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161783556020840135613c6081613629565b74ffffffffffffffffffffffffffffffffffffffff008160081b16837fffffffffffffffffffffff0000000000000000000000000000000000000000008416171784555050505050565b604081018235613cb981613935565b60ff1682526020830135613ccc81613629565b73ffffffffffffffffffffffffffffffffffffffff811660208401525092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215613d6f57600080fd5b815167ffffffffffffffff80821115613d8757600080fd5b818401915084601f830112613d9b57600080fd5b815181811115613dad57613dad613cff565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715613df357613df3613cff565b81604052828152876020848701011115613e0c57600080fd5b613e1d83602083016020880161383f565b979650505050505050565b6020815260006135b4602083018461386b565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e6c57613e6c613a46565b5060010190565b600060608284031215613e8557600080fd5b6040516060810181811067ffffffffffffffff82111715613ea857613ea8613cff565b6040528235613eb681613629565b81526020830135613ec681613629565b6020820152613ed760408401613b69565b60408201529392505050565b600073ffffffffffffffffffffffffffffffffffffffff8088168352861515602084015285604084015280851660608401525060a06080830152613e1d60a083018461386b565b60008060408385031215613f3d57600080fd5b505080516020909101519092909150565b60007f80000000000000000000000000000000000000000000000000000000000000008203613f7f57613f7f613a46565b506000039056fea26469706673582212208d94d1204ab19dc7a3879bb7654d8a5b1075db86cff7084f168b58405235ad8d64736f6c634300080d0033000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000009a8fee232dcf73060af348a1b62cdb0a19852d130000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984

Deployed Bytecode

0x6080604052600436106101435760003560e01c80638e4856b8116100c0578063e30c397811610074578063e8ab6f2d11610059578063e8ab6f2d146103c4578063f3995c67146103cc578063fa461e33146103df57600080fd5b8063e30c397814610363578063e6d395411461039057600080fd5b8063ac9650d8116100a5578063ac9650d8146102cb578063aec0b543146102eb578063c2e3140a1461035057600080fd5b80638e4856b8146102a5578063aaa6864b146102b857600080fd5b80635b54918211610117578063694333a0116100fc578063694333a01461024557806388321738146102585780638da5cb5b1461027857600080fd5b80635b549182146101fe57806362304cce1461023257600080fd5b8062b25bfb14610148578063078dfbe71461016e5780633fc8cef3146101905780634e71e0c8146101e9575b600080fd5b61015b6101563660046135ed565b6103ff565b6040519081526020015b60405180910390f35b34801561017a57600080fd5b5061018e61018936600461365c565b6109ef565b005b34801561019c57600080fd5b506101c47f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610165565b3480156101f557600080fd5b5061018e610bdc565b34801561020a57600080fd5b506101c47f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b61015b6102403660046135ed565b610cf3565b61015b6102533660046135ed565b611988565b34801561026457600080fd5b5061018e6102733660046136a7565b611fb2565b34801561028457600080fd5b506001546101c49073ffffffffffffffffffffffffffffffffffffffff1681565b61015b6102b3366004613708565b6120f7565b61015b6102c63660046135ed565b612465565b6102de6102d93660046137ca565b612b49565b60405161016591906138b5565b3480156102f757600080fd5b506003546103229060ff811690610100900473ffffffffffffffffffffffffffffffffffffffff1682565b6040805160ff909316835273ffffffffffffffffffffffffffffffffffffffff909116602083015201610165565b61018e61035e366004613944565b612cbb565b34801561036f57600080fd5b506002546101c49073ffffffffffffffffffffffffffffffffffffffff1681565b34801561039c57600080fd5b506101c47f000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff81565b61018e612d6d565b61018e6103da366004613944565b612df0565b3480156103eb57600080fd5b5061018e6103fa3660046139a0565b612ea2565b60008054600114610471576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b60026000556101608201354211156104b5576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806104ca610140850161012086016139f3565b6104d8578360c00135610578565b6104e86060850160408601613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610554573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105789190613a2d565b905061058c610140850161012086016139f3565b6105c4576105c43330836105a66060890160408a01613a10565b73ffffffffffffffffffffffffffffffffffffffff16929190612fc4565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff1660208301521561066c578051600090612710906106149060ff1685613a75565b61061e9190613ab2565b9050801561066a576106308184613aed565b602083015190935061066a908261064d60608a0160408b01613a10565b73ffffffffffffffffffffffffffffffffffffffff169190613083565b505b909250829061068160c0860160a08701613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ef8b30f7836040518263ffffffff1660e01b81526004016106bb91815260200190565b602060405180830381865afa1580156106d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106fc9190613a2d565b92508161070f6060870160408801613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e3061073860208a018a613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156107a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107cc9190613a2d565b1015610830576108306107e26020870187613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6108136060890160408a01613a10565b73ffffffffffffffffffffffffffffffffffffffff169190613142565b61083d6020860186613a10565b73ffffffffffffffffffffffffffffffffffffffff166363f41f8261086a61012088016101008901613a10565b3061087b60408a0160208b01613a10565b61088b60c08b0160a08c01613a10565b60405160e086901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff94851660048201529284166024840152908316604483015290911660648201526084810185905260a4016020604051808303816000875af1158015610915573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109399190613a2d565b506000915061094e9050610180850185613b04565b81019061095b9190613b81565b905061099961097060c0860160a08701613a10565b836109816080880160608901613a10565b846109946101208a016101008b01613a10565b6131fb565b6109a39084613b9c565b9250508260e001358210156109e4576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001600055919050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a70576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610468565b8115610b955773ffffffffffffffffffffffffffffffffffffffff8316151580610a975750805b610afd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4f776e61626c653a207a65726f206164647265737300000000000000000000006044820152606401610468565b60015460405173ffffffffffffffffffffffffffffffffffffffff8086169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36001805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff000000000000000000000000000000000000000091821617909155600280549091169055505050565b6002805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055505050565b60025473ffffffffffffffffffffffffffffffffffffffff16338114610c5e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c657220213d2070656e64696e67206f776e65726044820152606401610468565b60015460405173ffffffffffffffffffffffffffffffffffffffff8084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36001805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff0000000000000000000000000000000000000000928316179055600280549091169055565b60008054600114610d60576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115610da4576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0820135610dbb610140840161012085016139f3565b61107b57610dd1610160840161014085016139f3565b1561106557610dec3330836105a660a0880160808901613a10565b80610dfd60a0850160808601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e30610e2960c0880160a08901613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610e99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ebd9190613a2d565b1015610fb557610ed360a0840160808501613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b3610efe60c0860160a08701613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1158015610f8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb39190613bb4565b505b610fc560c0840160a08501613a10565b6040517f6e553f650000000000000000000000000000000000000000000000000000000081526004810183905230602482015273ffffffffffffffffffffffffffffffffffffffff9190911690636e553f65906044016020604051808303816000875af115801561103a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061105e9190613a2d565b905061107b565b61107b3330836105a660c0880160a08901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215611106578051600090612710906110cb9060ff1685613a75565b6110d59190613ab2565b90508015611104576110e78184613aed565b6020830151909350611104908261064d60c0890160a08a01613a10565b505b60008080611118610180880188613b04565b8101906111259190613bd1565b9250905061115461113c60c0890160a08a01613a10565b8361114d60808b0160608c01613a10565b84306131fb565b92506000905061116c610140880161012089016139f3565b61117f5761117a8286613aed565b61121f565b61118f60c0880160a08901613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa1580156111fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061121f9190613a2d565b905061123160c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16634cdad506826040518263ffffffff1660e01b815260040161126b91815260200190565b602060405180830381865afa158015611288573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ac9190613a2d565b9050828110156112be578095506112c2565b8295505b8660e00135861015611300576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61131060c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16630a28a477876040518263ffffffff1660e01b815260040161134a91815260200190565b602060405180830381865afa158015611367573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138b9190613a2d565b61139b60c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e306113c460208c018c613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015611434573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114589190613a2d565b101561149f5761149f61146e6020890189613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61081360c08b0160a08c01613a10565b6114ac6020880188613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ca64c6114d96101208a016101008b01613a10565b6114e960408b0160208c01613a10565b6114f960c08c0160a08d01613a10565b60405160e085901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff938416600482015291831660248301529091166044820152606481018990526084016020604051808303816000875af115801561157b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061159f9190613a2d565b50828110156116725761166d6115bd61012089016101008a01613a10565b6115cd60808a0160608b01613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611639573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061165d9190613a2d565b61064d60808b0160608c01613a10565b611978565b611684610160880161014089016139f3565b156118b357600061169b60c0890160a08a01613a10565b6040517fd905777e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff919091169063d905777e90602401602060405180830381865afa158015611707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061172b9190613a2d565b905080156118ad5761174360c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ba08765261176e60c08b0160a08c01613a10565b6040517fd905777e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff919091169063d905777e90602401602060405180830381865afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613a2d565b6118106101208c016101008d01613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b168152600481019290925273ffffffffffffffffffffffffffffffffffffffff1660248201523060448201526064016020604051808303816000875af1158015611887573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ab9190613a2d565b505b50611978565b6119786118c861012089016101008a01613a10565b6118d860c08a0160a08b01613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119689190613a2d565b61064d60c08b0160a08c01613a10565b5050600160005550919392505050565b600080546001146119f5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115611a39576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0820135611a50610140840161012085016139f3565b611a6a57611a6a3330836105a66080880160608901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215611af557805160009061271090611aba9060ff1685613a75565b611ac49190613ab2565b90508015611af357611ad68184613aed565b6020830151909350611af3908261064d6080890160608a01613a10565b505b60008080611b07610180880188613b04565b810190611b149190613bd1565b92509050611b3c611b2b6080890160608a01613a10565b8361114d60c08b0160a08c01613a10565b9250611b4e60c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff166307a2d13a846040518263ffffffff1660e01b8152600401611b8891815260200190565b602060405180830381865afa158015611ba5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bc99190613a2d565b925060009050611be1610140880161012089016139f3565b611bf457611bef8286613aed565b611c94565b611c046080880160608901613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015611c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c949190613a2d565b905082811015611ca657809550611caa565b8295505b8660e00135861015611ce8576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611cf860c0880160a08901613a10565b73ffffffffffffffffffffffffffffffffffffffff16630a28a477876040518263ffffffff1660e01b8152600401611d3291815260200190565b602060405180830381865afa158015611d4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d739190613a2d565b611d8360c0890160a08a01613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e30611dac60208c018c613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015611e1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e409190613a2d565b1015611e5657611e5661146e6020890189613a10565b611e636020880188613a10565b73ffffffffffffffffffffffffffffffffffffffff1663095ca64c611e906101208a016101008b01613a10565b611ea060408b0160208c01613a10565b611eb060c08c0160a08d01613a10565b60405160e085901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff938416600482015291831660248301529091166044820152606481018990526084016020604051808303816000875af1158015611f32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f569190613a2d565b5082811015611f9d57611f71610160880161014089016139f3565b15611f8857600061169b60c0890160a08a01613a10565b61166d6118c861012089016101008a01613a10565b6119786115bd61012089016101008a01613a10565b60015473ffffffffffffffffffffffffffffffffffffffff163314612033576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610468565b6120406020820182613bfb565b60ff16158015906120765750600061205e6040830160208401613a10565b73ffffffffffffffffffffffffffffffffffffffff16145b156120ad576040517ffd15c34300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060036120ba8282613c18565b9050507fbb1e7f6bcb2654c0e4c0f3cfb23f810d2babfb459af4ad2efcd1de09b452ceab816040516120ec9190613caa565b60405180910390a150565b60008054600114612164576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b600260005573ffffffffffffffffffffffffffffffffffffffff808a16908c16036121bb576040517fdc85d61400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b834211156121f5576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8561221c5761221c73ffffffffffffffffffffffffffffffffffffffff8c1633308d612fc4565b84156122835761228373ffffffffffffffffffffffffffffffffffffffff8c167f000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613142565b60007f000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff73ffffffffffffffffffffffffffffffffffffffff1684846040516122cc929190613cef565b6000604051808303816000865af19150503d8060008114612309576040519150601f19603f3d011682016040523d82523d6000602084013e61230e565b606091505b5050905080612349576040517f965d94b600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8b16906370a0823190602401602060405180830381865afa1580156123b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d79190613a2d565b915088821015612413576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff881630146124515761245173ffffffffffffffffffffffffffffffffffffffff8b168984613083565b5060016000559a9950505050505050505050565b600080546001146124d2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610468565b6002600055610160820135421115612516576040517faa6b6a7800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061252a610140840161012085016139f3565b612538578260c001356125d8565b6125486060840160408501613a10565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa1580156125b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d89190613a2d565b90506125ec610140840161012085016139f3565b612606576126063330836105a66060880160408901613a10565b6040805180820190915260035460ff811680835261010090910473ffffffffffffffffffffffffffffffffffffffff16602083015215612691578051600090612710906126569060ff1685613a75565b6126609190613ab2565b9050801561268f576126728184613aed565b602083015190935061268f908261064d6060890160408a01613a10565b505b6126a3610160850161014086016139f3565b612737576126b760c0850160a08601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ef8b30f7836040518263ffffffff1660e01b81526004016126f191815260200190565b602060405180830381865afa15801561270e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127329190613a2d565b612739565b815b92508161274c6060860160408701613a10565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e306127756020890189613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156127e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128099190613a2d565b10156128505761285061281f6020860186613a10565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6108136060880160408901613a10565b61285d6020850185613a10565b73ffffffffffffffffffffffffffffffffffffffff166363f41f823061288b61012088016101008901613a10565b61289b6040890160208a01613a10565b6128ad6101608a016101408b016139f3565b6128c6576128c160c08a0160a08b01613a10565b6128c9565b60005b60405160e086901b7fffffffff0000000000000000000000000000000000000000000000000000000016815273ffffffffffffffffffffffffffffffffffffffff94851660048201529284166024840152908316604483015290911660648201526084810185905260a4016020604051808303816000875af1158015612953573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129779190613a2d565b50600090508061298b610180860186613b04565b8101906129989190613b81565b90506129ed6129ad6080870160608801613a10565b846129be60c0890160a08a01613a10565b846129d16101608b016101408c016139f3565b6129e7576109946101208b016101008c01613a10565b306131fb565b9150612a039050610160850161014086016139f3565b15612af257612a1860c0850160a08601613a10565b73ffffffffffffffffffffffffffffffffffffffff1663ba08765282612a4661012088016101008901613a10565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b168152600481019290925273ffffffffffffffffffffffffffffffffffffffff1660248201523060448201526064016020604051808303816000875af1158015612abd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ae19190613a2d565b612aeb9084613b9c565b9250612aff565b612afc8184613b9c565b92505b8360e00135831015612b3d576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50506001600055919050565b60608167ffffffffffffffff811115612b6457612b64613cff565b604051908082528060200260200182016040528015612b9757816020015b6060815260200190600190039081612b825790505b50905060005b82811015612cb45760008030868685818110612bbb57612bbb613d2e565b9050602002810190612bcd9190613b04565b604051612bdb929190613cef565b600060405180830381855af49150503d8060008114612c16576040519150601f19603f3d011682016040523d82523d6000602084013e612c1b565b606091505b509150915081612c8157604481511015612c3457600080fd5b60048101905080806020019051810190612c4e9190613d5d565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104689190613e28565b80848481518110612c9457612c94613d2e565b602002602001018190525050508080612cac90613e3b565b915050612b9d565b5092915050565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152859073ffffffffffffffffffffffffffffffffffffffff88169063dd62ed3e90604401602060405180830381865afa158015612d2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d519190613a2d565b1015612d6557612d65868686868686612df0565b505050505050565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015612dd557600080fd5b505af1158015612de9573d6000803e3d6000fd5b5050505050565b6040517fd505accf000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018690526064810185905260ff8416608482015260a4810183905260c4810182905273ffffffffffffffffffffffffffffffffffffffff87169063d505accf9060e401600060405180830381600087803b158015612e8257600080fd5b505af1158015612e96573d6000803e3d6000fd5b50505050505050505050565b600080851315612eb3575083612ef5565b6000841315612ec3575082612ef5565b6040517f3fbd122600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612f0383850185613e73565b90506000612f477f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984612f428460000151856020015186604001516133e9565b61347b565b90503373ffffffffffffffffffffffffffffffffffffffff821614612f98576040517f1a13dcc000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151612fbb9073ffffffffffffffffffffffffffffffffffffffff163385613083565b50505050505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080612de9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610468565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061313c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610468565b50505050565b60006040517f095ea7b3000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061313c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f415050524f56455f4641494c45440000000000000000000000000000000000006044820152606401610468565b60008061322d7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984612f428988886133e9565b9050600060405180606001604052808973ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018662ffffff168152506040516020016132c99190815173ffffffffffffffffffffffffffffffffffffffff90811682526020808401519091169082015260409182015162ffffff169181019190915260600190565b604051602081830303815290604052905060008673ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161090506000808473ffffffffffffffffffffffffffffffffffffffff1663128acb0888856133378e6135bb565b876133565773fffd8963efd1fc6a506488495d951d5263988d2561335d565b6401000276a45b896040518663ffffffff1660e01b815260040161337e959493929190613ee3565b60408051808303816000875af115801561339c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133c09190613f2a565b91509150826133cf57816133d1565b805b6133da90613f4e565b9b9a5050505050505050505050565b60408051606081018252600080825260208201819052918101919091528273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16111561343e579192915b506040805160608101825273ffffffffffffffffffffffffffffffffffffffff948516815292909316602083015262ffffff169181019190915290565b6000816020015173ffffffffffffffffffffffffffffffffffffffff16826000015173ffffffffffffffffffffffffffffffffffffffff16106134bd57600080fd5b8151602080840151604080860151815173ffffffffffffffffffffffffffffffffffffffff95861681860152949092168482015262ffffff90911660608085019190915281518085038201815260808501909252815191909201207fff0000000000000000000000000000000000000000000000000000000000000060a08401529085901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660a183015260b58201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d58201526135b49060f5016040516020818303038152906040528051906020012090565b9392505050565b60007f800000000000000000000000000000000000000000000000000000000000000082106135e957600080fd5b5090565b6000602082840312156135ff57600080fd5b813567ffffffffffffffff81111561361657600080fd5b82016101a081850312156135b457600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461364b57600080fd5b50565b801515811461364b57600080fd5b60008060006060848603121561367157600080fd5b833561367c81613629565b9250602084013561368c8161364e565b9150604084013561369c8161364e565b809150509250925092565b6000604082840312156136b957600080fd5b50919050565b60008083601f8401126136d157600080fd5b50813567ffffffffffffffff8111156136e957600080fd5b60208301915083602082850101111561370157600080fd5b9250929050565b6000806000806000806000806000806101208b8d03121561372857600080fd5b8a3561373381613629565b995060208b0135985060408b013561374a81613629565b975060608b0135965060808b013561376181613629565b955060a08b01356137718161364e565b945060c08b01356137818161364e565b935060e08b013592506101008b013567ffffffffffffffff8111156137a557600080fd5b6137b18d828e016136bf565b915080935050809150509295989b9194979a5092959850565b600080602083850312156137dd57600080fd5b823567ffffffffffffffff808211156137f557600080fd5b818501915085601f83011261380957600080fd5b81358181111561381857600080fd5b8660208260051b850101111561382d57600080fd5b60209290920196919550909350505050565b60005b8381101561385a578181015183820152602001613842565b8381111561313c5750506000910152565b6000815180845261388381602086016020860161383f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613928577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261391685835161386b565b945092850192908501906001016138dc565b5092979650505050505050565b60ff8116811461364b57600080fd5b60008060008060008060c0878903121561395d57600080fd5b863561396881613629565b95506020870135945060408701359350606087013561398681613935565b9598949750929560808101359460a0909101359350915050565b600080600080606085870312156139b657600080fd5b8435935060208501359250604085013567ffffffffffffffff8111156139db57600080fd5b6139e7878288016136bf565b95989497509550505050565b600060208284031215613a0557600080fd5b81356135b48161364e565b600060208284031215613a2257600080fd5b81356135b481613629565b600060208284031215613a3f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615613aad57613aad613a46565b500290565b600082613ae8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600082821015613aff57613aff613a46565b500390565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613b3957600080fd5b83018035915067ffffffffffffffff821115613b5457600080fd5b60200191503681900382131561370157600080fd5b803562ffffff81168114613b7c57600080fd5b919050565b600060208284031215613b9357600080fd5b6135b482613b69565b60008219821115613baf57613baf613a46565b500190565b600060208284031215613bc657600080fd5b81516135b48161364e565b60008060408385031215613be457600080fd5b613bed83613b69565b946020939093013593505050565b600060208284031215613c0d57600080fd5b81356135b481613935565b8135613c2381613935565b60ff811690508154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161783556020840135613c6081613629565b74ffffffffffffffffffffffffffffffffffffffff008160081b16837fffffffffffffffffffffff0000000000000000000000000000000000000000008416171784555050505050565b604081018235613cb981613935565b60ff1682526020830135613ccc81613629565b73ffffffffffffffffffffffffffffffffffffffff811660208401525092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215613d6f57600080fd5b815167ffffffffffffffff80821115613d8757600080fd5b818401915084601f830112613d9b57600080fd5b815181811115613dad57613dad613cff565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715613df357613df3613cff565b81604052828152876020848701011115613e0c57600080fd5b613e1d83602083016020880161383f565b979650505050505050565b6020815260006135b4602083018461386b565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e6c57613e6c613a46565b5060010190565b600060608284031215613e8557600080fd5b6040516060810181811067ffffffffffffffff82111715613ea857613ea8613cff565b6040528235613eb681613629565b81526020830135613ec681613629565b6020820152613ed760408401613b69565b60408201529392505050565b600073ffffffffffffffffffffffffffffffffffffffff8088168352861515602084015285604084015280851660608401525060a06080830152613e1d60a083018461386b565b60008060408385031215613f3d57600080fd5b505080516020909101519092909150565b60007f80000000000000000000000000000000000000000000000000000000000000008203613f7f57613f7f613a46565b506000039056fea26469706673582212208d94d1204ab19dc7a3879bb7654d8a5b1075db86cff7084f168b58405235ad8d64736f6c634300080d0033

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

000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000009a8fee232dcf73060af348a1b62cdb0a19852d130000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984

-----Decoded View---------------
Arg [0] : zeroExProxy_ (address): 0xDef1C0ded9bec7F1a1670819833240f027b25EfF
Arg [1] : weth_ (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Arg [2] : protocolFeeInfo_ (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]
Arg [3] : uniswapV3Factory_ (address): 0x1F98431c8aD98523631AE4a59f267346ea31F984

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff
Arg [1] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [2] : 000000000000000000000000000000000000000000000000000000000000000a
Arg [3] : 0000000000000000000000009a8fee232dcf73060af348a1b62cdb0a19852d13
Arg [4] : 0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984


Deployed Bytecode Sourcemap

146202:20324:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;148915:2789;;;;;;:::i;:::-;;:::i;:::-;;;553:25:1;;;541:2;526:18;148915:2789:0;;;;;;;;29238:506;;;;;;;;;;-1:-1:-1;29238:506:0;;;;;:::i;:::-;;:::i;:::-;;137901:26;;;;;;;;;;;;;;;;;;1589:42:1;1577:55;;;1559:74;;1547:2;1532:18;137901:26:0;1393:246:1;29826:340:0;;;;;;;;;;;;;:::i;148110:41::-;;;;;;;;;;;;;;;159140:4455;;;;;;:::i;:::-;;:::i;155268:3661::-;;;;;;:::i;:::-;;:::i;143880:424::-;;;;;;;;;;-1:-1:-1;143880:424:0;;;;;:::i;:::-;;:::i;28391:20::-;;;;;;;;;;-1:-1:-1;28391:20:0;;;;;;;;141548:1478;;;;;;:::i;:::-;;:::i;151836:3221::-;;;;;;:::i;:::-;;:::i;49225:726::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;138192:38::-;;;;;;;;;;-1:-1:-1;138192:38:0;;;;;;;;;;;;;;;;;;;6049:4:1;6037:17;;;6019:36;;6103:42;6091:55;;;6086:2;6071:18;;6064:83;5992:18;138192:38:0;5849:304:1;50558:317:0;;;;;;:::i;:::-;;:::i;28418:27::-;;;;;;;;;;-1:-1:-1;28418:27:0;;;;;;;;137809:36;;;;;;;;;;;;;;;143418:92;;;:::i;50293:257::-;;;;;;:::i;:::-;;:::i;163839:1147::-;;;;;;;;;;-1:-1:-1;163839:1147:0;;;;;:::i;:::-;;:::i;148915:2789::-;149078:22;30811:6;;30821:1;30811:11;30803:34;;;;;;;7704:2:1;30803:34:0;;;7686:21:1;7743:2;7723:18;;;7716:30;7782:12;7762:18;;;7755:40;7812:18;;30803:34:0;;;;;;;;;30859:1;30850:6;:10;149167:13:::1;::::0;::::1;;149149:15;:31;149145:91;;;149204:20;;;;;;;;;;;;;;149145:91;149248:18;::::0;149361:22:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:120;;149463:4;:18;;;149361:120;;;149403:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;:40;::::0;;;;149437:4:::1;149403:40;::::0;::::1;1559:74:1::0;149403:25:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;149403:40:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;149337:144:::0;-1:-1:-1;149551:22:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;149546:220;;149594:156;149649:10;149690:4;149718:13:::0;149594:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:32;;::::0;:156;;:32:::1;:156::i;:::-;149816:57;::::0;;;;::::1;::::0;;;149858:15:::1;149816:57:::0;::::1;::::0;::::1;::::0;;;::::1;::::0;;::::1;;;;::::0;::::1;::::0;149892:24;149888:525:::1;;149974:20:::0;;149937:17:::1;::::0;150019:5:::1;::::0;149958:36:::1;::::0;::::1;;:13:::0;:36:::1;:::i;:::-;149957:67;;;;:::i;:::-;149937:87:::0;-1:-1:-1;150047:13:0;;150043:355:::1;;150137:26;150154:9:::0;150137:26;::::1;:::i;:::-;150293;::::0;::::1;::::0;150137;;-1:-1:-1;150238:140:0::1;::::0;150346:9;150238:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:28;;::::0;:140;:28:::1;:140::i;:::-;149918:495;149888:525;150502:13:::0;;-1:-1:-1;150502:13:0;;150595:9:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:24;;;150620:13;150595:39;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;150595:39:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;150582:52:::0;-1:-1:-1;150751:13:0;150671:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:25;;;150705:4;150720:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;150671:60;::::0;;::::1;::::0;;;;;;10087:42:1;10156:15;;;150671:60:0::1;::::0;::::1;10138:34:1::0;10208:15;;10188:18;;;10181:43;10050:18;;150671:60:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:93;150649:293;;;150799:127;150857:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;150890:17;150799:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;:27;;::::0;:127;:27:::1;:127::i;:::-;150956:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;:29;;;151004:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;151061:4;151101:10;::::0;;;::::1;::::0;::::1;;:::i;:::-;151130:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;150956:230;::::0;::::1;::::0;;;;;;;10770:42:1;10839:15;;;150956:230:0::1;::::0;::::1;10821:34:1::0;10891:15;;;10871:18;;;10864:43;10943:15;;;10923:18;;;10916:43;10995:15;;;10975:18;;;10968:43;11027:19;;;11020:35;;;10732:19;;150956:230:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1::0;151254:10:0::1;::::0;-1:-1:-1;151278:14:0::1;::::0;-1:-1:-1;151278:14:0::1;::::0;::::1;:4:::0;:14:::1;:::i;:::-;151267:36;;;;;;;:::i;:::-;151254:49:::0;-1:-1:-1;151387:159:0::1;151411:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;151439:10:::0;151468:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;151495:3:::0;151517:14:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;151387:5;:159::i;:::-;151369:177;::::0;;::::1;:::i;:::-;;;151239:319;151618:4;:17;;;151601:14;:34;151597:100;;;151659:26;;;;;;;;;;;;;;151597:100;-1:-1:-1::0;30896:1:0;30887:6;:10;148915:2789;;-1:-1:-1;148915:2789:0:o;29238:506::-;30294:5;;;;30280:10;:19;30272:64;;;;;;;12341:2:1;30272:64:0;;;12323:21:1;;;12360:18;;;12353:30;12419:34;12399:18;;;12392:62;12471:18;;30272:64:0;12139:356:1;30272:64:0;29377:6:::1;29373:364;;;29431:22;::::0;::::1;::::0;::::1;::::0;:34:::1;;;29457:8;29431:34;29423:68;;;::::0;::::1;::::0;;12702:2:1;29423:68:0::1;::::0;::::1;12684:21:1::0;12741:2;12721:18;;;12714:30;12780:23;12760:18;;;12753:51;12821:18;;29423:68:0::1;12500:345:1::0;29423:68:0::1;29558:5;::::0;29537:37:::1;::::0;::::1;::::0;;::::1;::::0;29558:5:::1;::::0;29537:37:::1;::::0;29558:5:::1;::::0;29537:37:::1;29589:5;:16:::0;;::::1;::::0;::::1;::::0;;;::::1;;::::0;;;29620:12:::1;:25:::0;;;;::::1;::::0;;29238:506;;;:::o;29373:364::-:1;29702:12;:23:::0;;::::1;::::0;::::1;::::0;;;::::1;;::::0;;29238:506;;;:::o;29826:340::-;29894:12;;;;29946:10;:27;;29938:72;;;;;;;13052:2:1;29938:72:0;;;13034:21:1;;;13071:18;;;13064:30;13130:34;13110:18;;;13103:62;13182:18;;29938:72:0;12850:356:1;29938:72:0;30069:5;;30048:42;;;;;;;30069:5;;30048:42;;30069:5;;30048:42;30101:5;:21;;;;;;;;;;;;;30133:12;:25;;;;;;;29826:340::o;159140:4455::-;159304:22;30811:6;;30821:1;30811:11;30803:34;;;;;;;7704:2:1;30803:34:0;;;7686:21:1;7743:2;7723:18;;;7716:30;7782:12;7762:18;;;7755:40;7812:18;;30803:34:0;7502:334:1;30803:34:0;30859:1;30850:6;:10;159393:13:::1;::::0;::::1;;159375:15;:31;159371:91;;;159430:20;;;;;;;;;;;;;;159371:91;159543:18;::::0;::::1;;159577:22;::::0;;;::::1;::::0;::::1;;:::i;:::-;159572:909;;159620:11;::::0;;;::::1;::::0;::::1;;:::i;:::-;159616:854;;;159705:149;159753:10;159794:4;159822:13:::0;159705:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:149::-;160051:13:::0;159974:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:18;;;160001:4;160016:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;159974:53;::::0;;::::1;::::0;;;;;;10087:42:1;10156:15;;;159974:53:0::1;::::0;::::1;10138:34:1::0;10208:15;;10188:18;;;10181:43;10050:18;;159974:53:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:90;159948:234;;;160107:8;::::0;;;::::1;::::0;::::1;;:::i;:::-;:16;;;160132:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;160107:55;::::0;;::::1;::::0;;;;;;13415:42:1;13403:55;;;160107::0::1;::::0;::::1;13385:74:1::0;160144:17:0::1;13475:18:1::0;;;13468:34;13358:18;;160107:55:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;159948:234;160216:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:47;::::0;;;;::::1;::::0;::::1;13937:25:1::0;;;160257:4:0::1;13978:18:1::0;;;13971:83;160216:17:0::1;::::0;;;::::1;::::0;::::1;::::0;13910:18:1;;160216:47:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;160200:63;;159616:854;;;160304:150;160353:10;160394:4;160422:13:::0;160304:9:::1;::::0;;;::::1;::::0;::::1;;:::i;:150::-;160523:57;::::0;;;;::::1;::::0;;;160565:15:::1;160523:57:::0;::::1;::::0;::::1;::::0;;;::::1;::::0;;::::1;;;;::::0;::::1;::::0;160595:24;160591:393:::1;;160673:20:::0;;160636:17:::1;::::0;160697:5:::1;::::0;160657:36:::1;::::0;::::1;;:13:::0;:36:::1;:::i;:::-;160656:46;;;;:::i;:::-;160636:66:::0;-1:-1:-1;160721:13:0;;160717:256:::1;;160803:26;160820:9:::0;160803:26;::::1;:::i;:::-;160919;::::0;::::1;::::0;160803;;-1:-1:-1;160896:61:0::1;::::0;160947:9;160896::::1;::::0;;;::::1;::::0;::::1;;:::i;:61::-;160621:363;160591:393;161025:21;::::0;;161161:14:::1;;::::0;::::1;:4:::0;:14:::1;:::i;:::-;161150:45;;;;;;;:::i;:::-;161128:67:::0;-1:-1:-1;161128:67:0;-1:-1:-1;161226:160:0::1;161250:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;161278:12:::0;161309:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;161336:3;161366:4;161226:5;:160::i;:::-;161210:176:::0;-1:-1:-1;161452:25:0::1;::::0;-1:-1:-1;161480:22:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:116;;161568:28;161584:12:::0;161568:13;:28:::1;:::i;:::-;161480:116;;;161518:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:34;::::0;;;;161546:4:::1;161518:34;::::0;::::1;1559:74:1::0;161518:19:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;161518:34:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;161452:144:::0;-1:-1:-1;161707:9:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:23;;;161731:17;161707:42;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;161707:42:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;161687:62;;161784:13;161764:17;:33;161760:248;;;161873:17;161856:34;;161760:248;;;161983:13;161966:30;;161760:248;162068:4;:17;;;162051:14;:34;162047:100;;;162109:26;;;;;;;;;;;;;;162047:100;162291:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:25;;;162317:14;162291:41;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;162291:41:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;162221:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:19;;;162249:4;162264:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;162221:54;::::0;;::::1;::::0;;;;;;10087:42:1;10156:15;;;162221:54:0::1;::::0;::::1;10138:34:1::0;10208:15;;10188:18;;;10181:43;10050:18;;162221:54:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:111;162203:228;;;162359:60;162389:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;162401:17;162359:9;::::0;;;::::1;::::0;::::1;;:::i;:60::-;162441:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;:26;;;162482:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;162511:10;::::0;;;::::1;::::0;::::1;;:::i;:::-;162536:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;162441:144;::::0;::::1;::::0;;;;;;;14577:42:1;14646:15;;;162441:144:0::1;::::0;::::1;14628:34:1::0;14698:15;;;14678:18;;;14671:43;14750:15;;;14730:18;;;14723:43;14782:18;;;14775:34;;;14539:19;;162441:144:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;162657:13;162637:17;:33;162633:955;;;162776:121;162816:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;162849:8;::::0;;;::::1;::::0;::::1;;:::i;:::-;:33;::::0;;;;162876:4:::1;162849:33;::::0;::::1;1559:74:1::0;162849:18:0::1;::::0;;;::::1;::::0;::::1;::::0;1532::1;;162849:33:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;162776:8;::::0;;;::::1;::::0;::::1;;:::i;:121::-;162633:955;;;163025:11;::::0;;;::::1;::::0;::::1;;:::i;:::-;163021:556;;;163057:23;163083:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:34;::::0;;;;163111:4:::1;163083:34;::::0;::::1;1559:74:1::0;163083:19:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;163083:34:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;163057:60:::0;-1:-1:-1;163140:20:0;;163136:250:::1;;163185:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:16;;;163228:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:34;::::0;;;;163256:4:::1;163228:34;::::0;::::1;1559:74:1::0;163228:19:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;163228:34:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;163289:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;163185:181;::::0;;::::1;::::0;;;;;;::::1;::::0;::::1;15022:25:1::0;;;;15066:42;15144:15;15124:18;;;15117:43;163338:4:0::1;15176:18:1::0;;;15169:43;14995:18;;163185:181:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;163136:250;163038:363;163021:556;;;163426:135;163471:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;163508:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:34;::::0;;;;163536:4:::1;163508:34;::::0;::::1;1559:74:1::0;163508:19:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;163508:34:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;163426:9;::::0;;;::::1;::::0;::::1;;:::i;:135::-;-1:-1:-1::0;;30896:1:0;30887:6;:10;-1:-1:-1;159140:4455:0;;;-1:-1:-1;;;159140:4455:0:o;155268:3661::-;155431:22;30811:6;;30821:1;30811:11;30803:34;;;;;;;7704:2:1;30803:34:0;;;7686:21:1;7743:2;7723:18;;;7716:30;7782:12;7762:18;;;7755:40;7812:18;;30803:34:0;7502:334:1;30803:34:0;30859:1;30850:6;:10;155520:13:::1;::::0;::::1;;155502:15;:31;155498:91;;;155557:20;;;;;;;;;;;;;;155498:91;155670:18;::::0;::::1;;155704:22;::::0;;;::::1;::::0;::::1;;:::i;:::-;155699:123;;155743:67;155769:10;155789:4;155796:13:::0;155743:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:67::-;155864:57;::::0;;;;::::1;::::0;;;155906:15:::1;155864:57:::0;::::1;::::0;::::1;::::0;;;::::1;::::0;;::::1;;;;::::0;::::1;::::0;155936:24;155932:392:::1;;156014:20:::0;;155977:17:::1;::::0;156038:5:::1;::::0;155998:36:::1;::::0;::::1;;:13:::0;:36:::1;:::i;:::-;155997:46;;;;:::i;:::-;155977:66:::0;-1:-1:-1;156062:13:0;;156058:255:::1;;156144:26;156161:9:::0;156144:26;::::1;:::i;:::-;156259;::::0;::::1;::::0;156144;;-1:-1:-1;156237:60:0::1;::::0;156287:9;156237:8:::1;::::0;;;::::1;::::0;::::1;;:::i;:60::-;155962:362;155932:392;156365:21;::::0;;156501:14:::1;;::::0;::::1;:4:::0;:14:::1;:::i;:::-;156490:45;;;;;;;:::i;:::-;156468:67:::0;-1:-1:-1;156468:67:0;-1:-1:-1;156566:160:0::1;156590:8;::::0;;;::::1;::::0;::::1;;:::i;:::-;156617:12:::0;156648:9:::1;::::0;;;::::1;::::0;::::1;;:::i;156566:160::-;156550:176:::0;-1:-1:-1;156834:9:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:25;;;156860:13;156834:40;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;156834:40:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;156818:56:::0;-1:-1:-1;156940:25:0::1;::::0;-1:-1:-1;156968:22:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:115;;157055:28;157071:12:::0;157055:13;:28:::1;:::i;:::-;156968:115;;;157006:8;::::0;;;::::1;::::0;::::1;;:::i;:::-;:33;::::0;;;;157033:4:::1;157006:33;::::0;::::1;1559:74:1::0;157006:18:0::1;::::0;;;::::1;::::0;::::1;::::0;1532::1;;157006:33:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;156940:143;;157118:13;157098:17;:33;157094:248;;;157207:17;157190:34;;157094:248;;;157317:13;157300:30;;157094:248;157402:4;:17;;;157385:14;:34;157381:100;;;157443:26;;;;;;;;;;;;;;157381:100;157625:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:25;;;157651:14;157625:41;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;157625:41:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;157555:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:19;;;157583:4;157598:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;157555:54;::::0;;::::1;::::0;;;;;;10087:42:1;10156:15;;;157555:54:0::1;::::0;::::1;10138:34:1::0;10208:15;;10188:18;;;10181:43;10050:18;;157555:54:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:111;157537:228;;;157693:60;157723:9;;::::0;::::1;:4:::0;:9:::1;:::i;157693:60::-;157775:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;:26;;;157816:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;157845:10;::::0;;;::::1;::::0;::::1;;:::i;:::-;157870:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;157775:144;::::0;::::1;::::0;;;;;;;14577:42:1;14646:15;;;157775:144:0::1;::::0;::::1;14628:34:1::0;14698:15;;;14678:18;;;14671:43;14750:15;;;14730:18;;;14723:43;14782:18;;;14775:34;;;14539:19;;157775:144:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;157991:13;157971:17;:33;157967:955;;;158115:11;::::0;;;::::1;::::0;::::1;;:::i;:::-;158111:556;;;158147:23;158173:9;::::0;;;::::1;::::0;::::1;;:::i;158111:556::-;158516:135;158561:14;::::0;;;::::1;::::0;::::1;;:::i;157967:955::-;158789:121;158829:14;::::0;;;::::1;::::0;::::1;;:::i;143880:424::-:0;30294:5;;;;30280:10;:19;30272:64;;;;;;;12341:2:1;30272:64:0;;;12323:21:1;;;12360:18;;;12353:30;12419:34;12399:18;;;12392:62;12471:18;;30272:64:0;12139:356:1;30272:64:0;144040:20:::1;;::::0;::::1;:16:::0;:20:::1;:::i;:::-;:25;;::::0;;::::1;::::0;:82:::1;;-1:-1:-1::0;144120:1:0::1;144082:26;::::0;;;::::1;::::0;::::1;;:::i;:::-;:40;;;144040:82;144022:180;;;144156:34;;;;;;;;;;;;;;144022:180;144230:16:::0;144212:15:::1;:34;144230:16:::0;144212:15;:34:::1;:::i;:::-;;;;144264:32;144279:16;144264:32;;;;;;:::i;:::-;;;;;;;;143880:424:::0;:::o;141548:1478::-;141889:22;30811:6;;30821:1;30811:11;30803:34;;;;;;;7704:2:1;30803:34:0;;;7686:21:1;7743:2;7723:18;;;7716:30;7782:12;7762:18;;;7755:40;7812:18;;30803:34:0;7502:334:1;30803:34:0;30859:1;30850:6;:10;141975:19:::1;::::0;;::::1;::::0;;::::1;::::0;141971:76:::1;;142018:17;;;;;;;;;;;;;;141971:76;142108:8;142090:15;:26;142086:86;;;142140:20;;;;;;;;;;;;;;142086:86;142226:17;142221:117;;142260:66;:24;::::0;::::1;142285:10;142305:4;142312:13:::0;142260:24:::1;:66::i;:::-;142386:15;142382:99;;;142418:51;:19;::::0;::::1;142438:11;142451:17;142418:19;:51::i;:::-;142530:12;142548:11;:16;;142565:8;;142548:26;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;142529:45;;;142590:7;142585:72;;142621:24;;;;;;;;;;;;;;142585:72;142713:33;::::0;;;;142740:4:::1;142713:33;::::0;::::1;1559:74:1::0;142713:18:0::1;::::0;::::1;::::0;::::1;::::0;1532::1;;142713:33:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;142696:50;;142778:12;142761:14;:29;142757:95;;;142814:26;;;;;;;;;;;;;;142757:95;142916:26;::::0;::::1;142937:4;142916:26;142912:107;;142959:48;:21;::::0;::::1;142981:9:::0;142992:14;142959:21:::1;:48::i;:::-;-1:-1:-1::0;30896:1:0;30887:6;:10;141548:1478;;-1:-1:-1;;;;;;;;;;141548:1478:0:o;151836:3221::-;152000:22;30811:6;;30821:1;30811:11;30803:34;;;;;;;7704:2:1;30803:34:0;;;7686:21:1;7743:2;7723:18;;;7716:30;7782:12;7762:18;;;7755:40;7812:18;;30803:34:0;7502:334:1;30803:34:0;30859:1;30850:6;:10;152089:13:::1;::::0;::::1;;152071:15;:31;152067:91;;;152126:20;;;;;;;;;;;;;;152067:91;152170:21;152290:22;::::0;;;::::1;::::0;::::1;;:::i;:::-;:120;;152392:4;:18;;;152290:120;;;152332:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;:40;::::0;;;;152366:4:::1;152332:40;::::0;::::1;1559:74:1::0;152332:25:0::1;::::0;;;::::1;::::0;::::1;::::0;1532:18:1;;152332:40:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;152274:136:::0;-1:-1:-1;152480:22:0::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;152475:220;;152523:156;152578:10;152619:4;152647:13:::0;152523:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:156::-;152745:57;::::0;;;;::::1;::::0;;;152787:15:::1;152745:57:::0;::::1;::::0;::::1;::::0;;;::::1;::::0;;::::1;;;;::::0;::::1;::::0;152821:24;152817:525:::1;;152903:20:::0;;152866:17:::1;::::0;152948:5:::1;::::0;152887:36:::1;::::0;::::1;;:13:::0;:36:::1;:::i;:::-;152886:67;;;;:::i;:::-;152866:87:::0;-1:-1:-1;152976:13:0;;152972:355:::1;;153066:26;153083:9:::0;153066:26;::::1;:::i;:::-;153222;::::0;::::1;::::0;153066;;-1:-1:-1;153167:140:0::1;::::0;153275:9;153167:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:140::-;152847:495;152817:525;153431:11;::::0;;;::::1;::::0;::::1;;:::i;:::-;:103;;153495:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:24;;;153520:13;153495:39;;;;;;;;;;;;;553:25:1::0;;541:2;526:18;;407:177;153495:39:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;153431:103;;;153462:13;153431:103;153414:120:::0;-1:-1:-1;153703:13:0;153623:15:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:25;;;153657:4;153672:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;153623:60;::::0;;::::1;::::0;;;;;;10087:42:1;10156:15;;;153623:60:0::1;::::0;::::1;10138:34:1::0;10208:15;;10188:18;;;10181:43;10050:18;;153623:60:0::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:93;153601:293;;;153751:127;153809:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;153842:17;153751:15;::::0;;;::::1;::::0;::::1;;:::i;:127::-;153908:9;;::::0;::::1;:4:::0;:9:::1;:::i;:::-;:29;;;153964:4;154004:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;154053:10;::::0;;;::::1;::::0;::::1;;:::i;:::-;154082:11;::::0;;;::::1;::::0;::::1;;:::i;:::-;:43;;154116:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;154082:43;;;154110:1;154082:43;153908:264;::::0;::::1;::::0;;;;;;;10770:42:1;10839:15;;;153908:264:0::1;::::0;::::1;10821:34:1::0;10891:15;;;10871:18;;;10864:43;10943:15;;;10923:18;;;10916:43;10995:15;;;10975:18;;;10968:43;11027:19;;;11020:35;;;10732:19;;153908:264:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1::0;154225:18:0::1;::::0;-1:-1:-1;154225:18:0;154293:14:::1;;::::0;::::1;:4:::0;:14:::1;:::i;:::-;154282:36;;;;;;;:::i;:::-;154269:49:::0;-1:-1:-1;154346:256:0::1;154370:8;::::0;;;::::1;::::0;::::1;;:::i;:::-;154397:13:::0;154429:9:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;154457:3:::0;154479:11:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;:44;;154509:14;::::0;;;::::1;::::0;::::1;;:::i;154479:44::-;154501:4;154346:5;:256::i;:::-;154333:269:::0;-1:-1:-1;154667:11:0::1;::::0;-1:-1:-1;154667:11:0;;;::::1;::::0;::::1;;:::i;:::-;154663:248;;;154713:9;::::0;;;::::1;::::0;::::1;;:::i;:::-;:16;;;154748:10:::0;154777:14:::1;::::0;;;::::1;::::0;::::1;;:::i;:::-;154713:125;::::0;;::::1;::::0;;;;;;::::1;::::0;::::1;15022:25:1::0;;;;15066:42;15144:15;15124:18;;;15117:43;154818:4:0::1;15176:18:1::0;;;15169:43;14995:18;;154713:125:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;154695:143;::::0;;::::1;:::i;:::-;;;154663:248;;;154871:28;154889:10:::0;154871:28;::::1;:::i;:::-;;;154663:248;154971:4;:17;;;154954:14;:34;154950:100;;;155012:26;;;;;;;;;;;;;;154950:100;-1:-1:-1::0;;30896:1:0;30887:6;:10;151836:3221;;-1:-1:-1;151836:3221:0:o;49225:726::-;49320:22;49382:4;49370:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49360:34;;49410:9;49405:539;49425:15;;;49405:539;;;49463:12;;49508:4;49545;;49550:1;49545:7;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;49500:67;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49462:105;;;;49589:7;49584:313;;49718:2;49702:6;:13;:18;49698:32;;;49722:8;;;49698:32;49803:4;49795:6;49791:17;49781:27;;49863:6;49852:28;;;;;;;;;;;;:::i;:::-;49845:36;;;;;;;;;;;:::i;49584:313::-;49926:6;49913:7;49921:1;49913:10;;;;;;;;:::i;:::-;;;;;;:19;;;;49447:497;;49442:3;;;;;:::i;:::-;;;;49405:539;;;;49225:726;;;;:::o;50558:317::-;50759:42;;;;;50775:10;50759:42;;;10138:34:1;50795:4:0;10188:18:1;;;10181:43;50804:5:0;;50759:15;;;;;;10050:18:1;;50759:42:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:50;50755:112;;;50824:43;50835:5;50842;50849:8;50859:1;50862;50865;50824:10;:43::i;:::-;50558:317;;;;;;:::o;143418:92::-;143470:4;:12;;;143490:9;143470:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;143418:92::o;50293:257::-;50477:65;;;;;50490:10;50477:65;;;19084:34:1;50510:4:0;19134:18:1;;;19127:43;19186:18;;;19179:34;;;19229:18;;;19222:34;;;19305:4;19293:17;;19272:19;;;19265:46;19327:19;;;19320:35;;;19371:19;;;19364:35;;;50477:12:0;;;;;;18995:19:1;;50477:65:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;50293:257;;;;;;:::o;163839:1147::-;164023:19;164072:1;164057:12;:16;164053:244;;;-1:-1:-1;164112:12:0;164053:244;;;164162:1;164147:12;:16;164143:154;;;-1:-1:-1;164202:12:0;164143:154;;;164255:30;;;;;;;;;;;;;;164143:154;164342:36;164381:73;;;;164406:4;164381:73;:::i;:::-;164342:112;;164493:12;164508:252;164549:16;164580:169;164629:12;:20;;;164677:12;:21;;;164718:12;:16;;;164580:22;:169::i;:::-;164508:26;:252::i;:::-;164493:267;-1:-1:-1;164775:10:0;:27;;;;164771:91;;164826:24;;;;;;;;;;;;;;164771:91;164920:20;;:58;;:33;;164954:10;164966:11;164920:33;:58::i;:::-;163976:1010;;;163839:1147;;;;:::o;8262:1604::-;8406:12;8537:4;8531:11;8682:66;8663:17;8656:93;8797:4;8793:1;8774:17;8770:25;8763:39;8882:2;8877;8858:17;8854:26;8847:38;8963:6;8958:2;8939:17;8935:26;8928:42;9777:2;9774:1;9769:3;9750:17;9747:1;9740:5;9733;9728:52;9291:16;9284:24;9278:2;9260:16;9257:24;9253:1;9249;9243:8;9240:15;9236:46;9233:76;9030:765;9019:776;;;9826:7;9818:40;;;;;;;20341:2:1;9818:40:0;;;20323:21:1;20380:2;20360:18;;;20353:30;20419:22;20399:18;;;20392:50;20459:18;;9818:40:0;20139:344:1;9874:1485:0;9991:12;10122:4;10116:11;10267:66;10248:17;10241:93;10382:2;10378:1;10359:17;10355:25;10348:37;10463:6;10458:2;10439:17;10435:26;10428:42;11275:2;11272:1;11268:2;11249:17;11246:1;11239:5;11232;11227:51;10791:16;10784:24;10778:2;10760:16;10757:24;10753:1;10749;10743:8;10740:15;10736:46;10733:76;10530:763;10519:774;;;11324:7;11316:35;;;;;;;20690:2:1;11316:35:0;;;20672:21:1;20729:2;20709:18;;;20702:30;20768:17;20748:18;;;20741:45;20803:18;;11316:35:0;20488:339:1;11316:35:0;9980:1379;9874:1485;;;:::o;11367:1483::-;11483:12;11614:4;11608:11;11759:66;11740:17;11733:93;11874:2;11870:1;11851:17;11847:25;11840:37;11955:6;11950:2;11931:17;11927:26;11920:42;12767:2;12764:1;12760:2;12741:17;12738:1;12731:5;12724;12719:51;12283:16;12276:24;12270:2;12252:16;12249:24;12245:1;12241;12235:8;12232:15;12228:46;12225:76;12022:763;12011:774;;;12816:7;12808:34;;;;;;;21034:2:1;12808:34:0;;;21016:21:1;21073:2;21053:18;;;21046:30;21112:16;21092:18;;;21085:44;21146:18;;12808:34:0;20832:338:1;165509:1014:0;165679:7;165731:22;165785:159;165830:16;165865:64;165896:7;165914:8;165925:3;165865:22;:64::i;165785:159::-;165731:224;;165988:29;166045:66;;;;;;;;166072:7;166045:66;;;;;;166091:8;166045:66;;;;;;166106:3;166045:66;;;;;166020:102;;;;;;;21472:13:1;;21399:42;21468:22;;;21450:41;;21551:4;21539:17;;;21533:24;21529:33;;;21507:20;;;21500:63;21623:4;21611:17;;;21605:24;21631:8;21601:39;21579:20;;;21572:69;;;;21377:2;21362:18;;21175:472;166020:102:0;;;;;;;;;;;;;165988:134;;166133:15;166178:8;166151:36;;166159:7;166151:36;;;166133:54;;166199:14;166215;166233:7;:12;;;166260:9;166284:10;166309:24;:13;:22;:24::i;:::-;166348:10;:63;;147801:49;166348:63;;;147538:10;166348:63;166426:16;166233:220;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;166198:255;;;;166483:10;:30;;166506:7;166483:30;;;166496:7;166483:30;166481:33;;;:::i;:::-;166466:49;165509:1014;-1:-1:-1;;;;;;;;;;;165509:1014:0:o;145093:281::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;145254:6:0;145245:15;;:6;:15;;;145241:56;;;145282:6;;145290;145241:56;-1:-1:-1;145315:51:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;145093:281::o;145627:446::-;145738:12;145789:3;:10;;;145776:23;;:3;:10;;;:23;;;145768:32;;;;;;145950:10;;145962;;;;;145974:7;;;;;145939:43;;22902:42:1;22971:15;;;145939:43:0;;;22953:34:1;23023:15;;;;23003:18;;;22996:43;23087:8;23075:21;;;23055:18;;;;23048:49;;;;145939:43:0;;;;;;;;;22865:18:1;;;145939:43:0;;;145929:54;;;;;;;23406:66:1;145842:194:0;;;23394:79:1;23506:15;;;;23523:66;23502:88;23489:11;;;23482:109;23607:12;;;23600:28;144536:66:0;23644:12:1;;;23637:28;145818:247:0;;23681:12:1;;145842:194:0;;;;;;;;;;;;145818:229;;;;;;31243:10;31124:140;145818:247;145811:254;145627:446;-1:-1:-1;;;145627:446:0:o;107919:124::-;107971:8;108004:6;108000:1;:10;107992:19;;;;;;-1:-1:-1;108033:1:0;107919:124::o;14:388:1:-;101:6;154:2;142:9;133:7;129:23;125:32;122:52;;;170:1;167;160:12;122:52;210:9;197:23;243:18;235:6;232:30;229:50;;;275:1;272;265:12;229:50;298:22;;354:3;336:16;;;332:26;329:46;;;371:1;368;361:12;589:154;675:42;668:5;664:54;657:5;654:65;644:93;;733:1;730;723:12;644:93;589:154;:::o;748:118::-;834:5;827:13;820:21;813:5;810:32;800:60;;856:1;853;846:12;871:517;942:6;950;958;1011:2;999:9;990:7;986:23;982:32;979:52;;;1027:1;1024;1017:12;979:52;1066:9;1053:23;1085:31;1110:5;1085:31;:::i;:::-;1135:5;-1:-1:-1;1192:2:1;1177:18;;1164:32;1205:30;1164:32;1205:30;:::i;:::-;1254:7;-1:-1:-1;1313:2:1;1298:18;;1285:32;1326:30;1285:32;1326:30;:::i;:::-;1375:7;1365:17;;;871:517;;;;;:::o;1875:201::-;1969:6;2022:2;2010:9;2001:7;1997:23;1993:32;1990:52;;;2038:1;2035;2028:12;1990:52;-1:-1:-1;2061:9:1;1875:201;-1:-1:-1;1875:201:1:o;2081:347::-;2132:8;2142:6;2196:3;2189:4;2181:6;2177:17;2173:27;2163:55;;2214:1;2211;2204:12;2163:55;-1:-1:-1;2237:20:1;;2280:18;2269:30;;2266:50;;;2312:1;2309;2302:12;2266:50;2349:4;2341:6;2337:17;2325:29;;2401:3;2394:4;2385:6;2377;2373:19;2369:30;2366:39;2363:59;;;2418:1;2415;2408:12;2363:59;2081:347;;;;;:::o;2433:1332::-;2595:6;2603;2611;2619;2627;2635;2643;2651;2659;2667;2720:3;2708:9;2699:7;2695:23;2691:33;2688:53;;;2737:1;2734;2727:12;2688:53;2776:9;2763:23;2795:31;2820:5;2795:31;:::i;:::-;2845:5;-1:-1:-1;2897:2:1;2882:18;;2869:32;;-1:-1:-1;2953:2:1;2938:18;;2925:32;2966:33;2925:32;2966:33;:::i;:::-;3018:7;-1:-1:-1;3072:2:1;3057:18;;3044:32;;-1:-1:-1;3128:3:1;3113:19;;3100:33;3142;3100;3142;:::i;:::-;3194:7;-1:-1:-1;3253:3:1;3238:19;;3225:33;3267:30;3225:33;3267:30;:::i;:::-;3316:7;-1:-1:-1;3375:3:1;3360:19;;3347:33;3389:30;3347:33;3389:30;:::i;:::-;3438:7;-1:-1:-1;3492:3:1;3477:19;;3464:33;;-1:-1:-1;3548:3:1;3533:19;;3520:33;3576:18;3565:30;;3562:50;;;3608:1;3605;3598:12;3562:50;3647:58;3697:7;3688:6;3677:9;3673:22;3647:58;:::i;:::-;3621:84;;3724:8;3714:18;;;3751:8;3741:18;;;2433:1332;;;;;;;;;;;;;:::o;3770:626::-;3867:6;3875;3928:2;3916:9;3907:7;3903:23;3899:32;3896:52;;;3944:1;3941;3934:12;3896:52;3984:9;3971:23;4013:18;4054:2;4046:6;4043:14;4040:34;;;4070:1;4067;4060:12;4040:34;4108:6;4097:9;4093:22;4083:32;;4153:7;4146:4;4142:2;4138:13;4134:27;4124:55;;4175:1;4172;4165:12;4124:55;4215:2;4202:16;4241:2;4233:6;4230:14;4227:34;;;4257:1;4254;4247:12;4227:34;4310:7;4305:2;4295:6;4292:1;4288:14;4284:2;4280:23;4276:32;4273:45;4270:65;;;4331:1;4328;4321:12;4270:65;4362:2;4354:11;;;;;4384:6;;-1:-1:-1;3770:626:1;;-1:-1:-1;;;;3770:626:1:o;4401:258::-;4473:1;4483:113;4497:6;4494:1;4491:13;4483:113;;;4573:11;;;4567:18;4554:11;;;4547:39;4519:2;4512:10;4483:113;;;4614:6;4611:1;4608:13;4605:48;;;-1:-1:-1;;4649:1:1;4631:16;;4624:27;4401:258::o;4664:316::-;4705:3;4743:5;4737:12;4770:6;4765:3;4758:19;4786:63;4842:6;4835:4;4830:3;4826:14;4819:4;4812:5;4808:16;4786:63;:::i;:::-;4894:2;4882:15;4899:66;4878:88;4869:98;;;;4969:4;4865:109;;4664:316;-1:-1:-1;;4664:316:1:o;4985:859::-;5145:4;5174:2;5214;5203:9;5199:18;5244:2;5233:9;5226:21;5267:6;5302;5296:13;5333:6;5325;5318:22;5371:2;5360:9;5356:18;5349:25;;5433:2;5423:6;5420:1;5416:14;5405:9;5401:30;5397:39;5383:53;;5471:2;5463:6;5459:15;5492:1;5502:313;5516:6;5513:1;5510:13;5502:313;;;5605:66;5593:9;5585:6;5581:22;5577:95;5572:3;5565:108;5696:39;5728:6;5719;5713:13;5696:39;:::i;:::-;5686:49;-1:-1:-1;5793:12:1;;;;5758:15;;;;5538:1;5531:9;5502:313;;;-1:-1:-1;5832:6:1;;4985:859;-1:-1:-1;;;;;;;4985:859:1:o;6158:114::-;6242:4;6235:5;6231:16;6224:5;6221:27;6211:55;;6262:1;6259;6252:12;6277:672;6392:6;6400;6408;6416;6424;6432;6485:3;6473:9;6464:7;6460:23;6456:33;6453:53;;;6502:1;6499;6492:12;6453:53;6541:9;6528:23;6560:31;6585:5;6560:31;:::i;:::-;6610:5;-1:-1:-1;6662:2:1;6647:18;;6634:32;;-1:-1:-1;6713:2:1;6698:18;;6685:32;;-1:-1:-1;6769:2:1;6754:18;;6741:32;6782:31;6741:32;6782:31;:::i;:::-;6277:672;;;;-1:-1:-1;6277:672:1;;6886:3;6871:19;;6858:33;;6938:3;6923:19;;;6910:33;;-1:-1:-1;6277:672:1;-1:-1:-1;;6277:672:1:o;6954:543::-;7040:6;7048;7056;7064;7117:2;7105:9;7096:7;7092:23;7088:32;7085:52;;;7133:1;7130;7123:12;7085:52;7169:9;7156:23;7146:33;;7226:2;7215:9;7211:18;7198:32;7188:42;;7281:2;7270:9;7266:18;7253:32;7308:18;7300:6;7297:30;7294:50;;;7340:1;7337;7330:12;7294:50;7379:58;7429:7;7420:6;7409:9;7405:22;7379:58;:::i;:::-;6954:543;;;;-1:-1:-1;7456:8:1;-1:-1:-1;;;;6954:543:1:o;7841:241::-;7897:6;7950:2;7938:9;7929:7;7925:23;7921:32;7918:52;;;7966:1;7963;7956:12;7918:52;8005:9;7992:23;8024:28;8046:5;8024:28;:::i;8087:260::-;8159:6;8212:2;8200:9;8191:7;8187:23;8183:32;8180:52;;;8228:1;8225;8218:12;8180:52;8267:9;8254:23;8286:31;8311:5;8286:31;:::i;8352:184::-;8422:6;8475:2;8463:9;8454:7;8450:23;8446:32;8443:52;;;8491:1;8488;8481:12;8443:52;-1:-1:-1;8514:16:1;;8352:184;-1:-1:-1;8352:184:1:o;8541:::-;8593:77;8590:1;8583:88;8690:4;8687:1;8680:15;8714:4;8711:1;8704:15;8730:228;8770:7;8896:1;8828:66;8824:74;8821:1;8818:81;8813:1;8806:9;8799:17;8795:105;8792:131;;;8903:18;;:::i;:::-;-1:-1:-1;8943:9:1;;8730:228::o;8963:274::-;9003:1;9029;9019:189;;9064:77;9061:1;9054:88;9165:4;9162:1;9155:15;9193:4;9190:1;9183:15;9019:189;-1:-1:-1;9222:9:1;;8963:274::o;9242:125::-;9282:4;9310:1;9307;9304:8;9301:34;;;9315:18;;:::i;:::-;-1:-1:-1;9352:9:1;;9242:125::o;11066:580::-;11143:4;11149:6;11209:11;11196:25;11299:66;11288:8;11272:14;11268:29;11264:102;11244:18;11240:127;11230:155;;11381:1;11378;11371:12;11230:155;11408:33;;11460:20;;;-1:-1:-1;11503:18:1;11492:30;;11489:50;;;11535:1;11532;11525:12;11489:50;11568:4;11556:17;;-1:-1:-1;11599:14:1;11595:27;;;11585:38;;11582:58;;;11636:1;11633;11626:12;11651:161;11718:20;;11778:8;11767:20;;11757:31;;11747:59;;11802:1;11799;11792:12;11747:59;11651:161;;;:::o;11817:184::-;11875:6;11928:2;11916:9;11907:7;11903:23;11899:32;11896:52;;;11944:1;11941;11934:12;11896:52;11967:28;11985:9;11967:28;:::i;12006:128::-;12046:3;12077:1;12073:6;12070:1;12067:13;12064:39;;;12083:18;;:::i;:::-;-1:-1:-1;12119:9:1;;12006:128::o;13513:245::-;13580:6;13633:2;13621:9;13612:7;13608:23;13604:32;13601:52;;;13649:1;13646;13639:12;13601:52;13681:9;13675:16;13700:28;13722:5;13700:28;:::i;14065:252::-;14132:6;14140;14193:2;14181:9;14172:7;14168:23;14164:32;14161:52;;;14209:1;14206;14199:12;14161:52;14232:28;14250:9;14232:28;:::i;:::-;14222:38;14307:2;14292:18;;;;14279:32;;-1:-1:-1;;;14065:252:1:o;15223:243::-;15280:6;15333:2;15321:9;15312:7;15308:23;15304:32;15301:52;;;15349:1;15346;15339:12;15301:52;15388:9;15375:23;15407:29;15430:5;15407:29;:::i;15471:678::-;15652:5;15639:19;15667:31;15690:7;15667:31;:::i;:::-;15730:4;15721:7;15717:18;15707:28;;15760:4;15754:11;15867:2;15798:66;15794:2;15790:75;15787:83;15781:4;15774:97;15919:2;15912:5;15908:14;15895:28;15932:33;15957:7;15932:33;:::i;:::-;16096:44;16086:7;16083:1;16079:15;16075:66;16070:2;16001:66;15997:2;15993:75;15990:83;15987:155;15981:4;15974:169;;;;15471:678;;:::o;16154:527::-;16356:2;16341:18;;16381:20;;16410:29;16381:20;16410:29;:::i;:::-;16477:4;16466:16;16448:35;;16532:4;16520:17;;16507:31;16547:33;16507:31;16547:33;:::i;:::-;16631:42;16622:7;16618:56;16611:4;16600:9;16596:20;16589:86;;16154:527;;;;:::o;16686:271::-;16869:6;16861;16856:3;16843:33;16825:3;16895:16;;16920:13;;;16895:16;16686:271;-1:-1:-1;16686:271:1:o;16962:184::-;17014:77;17011:1;17004:88;17111:4;17108:1;17101:15;17135:4;17132:1;17125:15;17151:184;17203:77;17200:1;17193:88;17300:4;17297:1;17290:15;17324:4;17321:1;17314:15;17340:943;17420:6;17473:2;17461:9;17452:7;17448:23;17444:32;17441:52;;;17489:1;17486;17479:12;17441:52;17522:9;17516:16;17551:18;17592:2;17584:6;17581:14;17578:34;;;17608:1;17605;17598:12;17578:34;17646:6;17635:9;17631:22;17621:32;;17691:7;17684:4;17680:2;17676:13;17672:27;17662:55;;17713:1;17710;17703:12;17662:55;17742:2;17736:9;17764:2;17760;17757:10;17754:36;;;17770:18;;:::i;:::-;17904:2;17898:9;17966:4;17958:13;;17809:66;17954:22;;;17978:2;17950:31;17946:40;17934:53;;;18002:18;;;18022:22;;;17999:46;17996:72;;;18048:18;;:::i;:::-;18088:10;18084:2;18077:22;18123:2;18115:6;18108:18;18163:7;18158:2;18153;18149;18145:11;18141:20;18138:33;18135:53;;;18184:1;18181;18174:12;18135:53;18197:55;18249:2;18244;18236:6;18232:15;18227:2;18223;18219:11;18197:55;:::i;:::-;18271:6;17340:943;-1:-1:-1;;;;;;;17340:943:1:o;18288:219::-;18437:2;18426:9;18419:21;18400:4;18457:44;18497:2;18486:9;18482:18;18474:6;18457:44;:::i;18512:195::-;18551:3;18582:66;18575:5;18572:77;18569:103;;18652:18;;:::i;:::-;-1:-1:-1;18699:1:1;18688:13;;18512:195::o;19410:724::-;19503:6;19556:2;19544:9;19535:7;19531:23;19527:32;19524:52;;;19572:1;19569;19562:12;19524:52;19605:2;19599:9;19647:2;19639:6;19635:15;19716:6;19704:10;19701:22;19680:18;19668:10;19665:34;19662:62;19659:88;;;19727:18;;:::i;:::-;19763:2;19756:22;19800:23;;19832:31;19800:23;19832:31;:::i;:::-;19872:21;;19945:2;19930:18;;19917:32;19958:33;19917:32;19958:33;:::i;:::-;20019:2;20007:15;;20000:32;20065:37;20098:2;20083:18;;20065:37;:::i;:::-;20060:2;20048:15;;20041:62;20052:6;19410:724;-1:-1:-1;;;19410:724:1:o;21652:591::-;21866:4;21895:42;21976:2;21968:6;21964:15;21953:9;21946:34;22030:6;22023:14;22016:22;22011:2;22000:9;21996:18;21989:50;22075:6;22070:2;22059:9;22055:18;22048:34;22130:2;22122:6;22118:15;22113:2;22102:9;22098:18;22091:43;;22171:3;22165;22154:9;22150:19;22143:32;22192:45;22232:3;22221:9;22217:19;22209:6;22192:45;:::i;22248:243::-;22325:6;22333;22386:2;22374:9;22365:7;22361:23;22357:32;22354:52;;;22402:1;22399;22392:12;22354:52;-1:-1:-1;;22425:16:1;;22481:2;22466:18;;;22460:25;22425:16;;22460:25;;-1:-1:-1;22248:243:1:o;22496:191::-;22531:3;22562:66;22555:5;22552:77;22549:103;;22632:18;;:::i;:::-;-1:-1:-1;22672:1:1;22668:13;;22496:191::o

Swarm Source

ipfs://8d94d1204ab19dc7a3879bb7654d8a5b1075db86cff7084f168b58405235ad8d

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]
[ Download: CSV Export  ]

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.