ETH Price: $3,268.77 (+0.69%)
Gas: 1 Gwei

Token

ERC20 ***
 

Overview

Max Total Supply

2,843.429443 ERC20 ***

Holders

56

Market

Onchain Market Cap

$0.00

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 6 Decimals)

Balance
0.000002 ERC20 ***

Value
$0.00
0xa52093dc50f2596abadb56dc643630e61944aba9
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
# Exchange Pair Price  24H Volume % Volume
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
UniswapV3xPYT

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-08-22
*/

// 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");
    }
}

/// @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;
}

pragma abicoder v2;

/// @title Quoter Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
    /// @notice Returns the amount out received for a given exact input swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee
    /// @param amountIn The amount of the first token to swap
    /// @return amountOut The amount of the last token that would be received
    function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);

    /// @notice Returns the amount out received for a given exact input but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountIn The desired input amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountOut The amount of `tokenOut` that would be received
    function quoteExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountOut);

    /// @notice Returns the amount in required for a given exact output swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
    /// @param amountOut The amount of the last token to receive
    /// @return amountIn The amount of first token required to be paid
    function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);

    /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountOut The desired output amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
    function quoteExactOutputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountOut,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountIn);
}

/// @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 {}
}

/// @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;
    }
}

// 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 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 XPYT
/// @author zefram.eth
/// @notice Permissionless auto-compounding vault for Timeless perpetual yield tokens
abstract contract XPYT is ERC4626, ReentrancyGuard, Multicall, SelfPermit {
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeTransferLib for ERC20;

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

    error Error_InsufficientOutput();
    error Error_InvalidMultiplierValue();
    error Error_ConsultTwapOracleFailed();

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

    event Pound(
        address indexed sender,
        address indexed pounderRewardRecipient,
        uint256 yieldAmount,
        uint256 pytCompounded,
        uint256 pounderReward
    );

    /// -----------------------------------------------------------------------
    /// Enums
    /// -----------------------------------------------------------------------

    enum PreviewPoundErrorCode {
        OK,
        TWAP_FAIL,
        INSUFFICIENT_OUTPUT
    }

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

    /// @notice The base unit for fixed point decimals.
    uint256 internal constant ONE = 10**18;

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

    /// @notice The Gate associated with the PYT.
    Gate public immutable gate;

    /// @notice The vault associated with the PYT.
    address public immutable vault;

    /// @notice The NYT associated with the PYT.
    NegativeYieldToken public immutable nyt;

    /// @notice The minimum acceptable ratio between the NYT output in pound() and the expected NYT output
    /// based on the TWAP. Scaled by ONE.
    uint256 public immutable minOutputMultiplier;

    /// @notice The proportion of the yield claimed in pound() to give to the caller as reward. Scaled by ONE.
    uint256 public immutable pounderRewardMultiplier;

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

    /// @notice The recorded balance of the deposited asset.
    /// @dev This is used instead of asset.balanceOf(address(this)) to prevent attackers from
    /// atomically increasing the vault share value and thus exploiting integrated lending protocols.
    uint256 public assetBalance;

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

    constructor(
        ERC20 asset_,
        string memory name_,
        string memory symbol_,
        uint256 pounderRewardMultiplier_,
        uint256 minOutputMultiplier_
    ) ERC4626(asset_, name_, symbol_) {
        if (minOutputMultiplier_ > ONE) {
            revert Error_InvalidMultiplierValue();
        }
        minOutputMultiplier = minOutputMultiplier_;
        pounderRewardMultiplier = pounderRewardMultiplier_;
        if (pounderRewardMultiplier_ > ONE) {
            revert Error_InvalidMultiplierValue();
        }
        Gate gate_ = PerpetualYieldToken(address(asset_)).gate();
        gate = gate_;
        address vault_ = PerpetualYieldToken(address(asset_)).vault();
        vault = vault_;
        nyt = gate_.getNegativeYieldTokenForVault(vault_);
    }

    /// -----------------------------------------------------------------------
    /// Compounding
    /// -----------------------------------------------------------------------

    /// @notice Claims the yield earned by the PYT held and sells the claimed NYT into more PYT.
    /// @dev Part of the claimed yield is given to the caller as reward, which incentivizes MEV bots
    /// to perform the auto-compounding for us.
    /// @param pounderRewardRecipient The address that will receive the caller reward
    /// @return yieldAmount The amount of PYT & NYT claimed as yield
    /// @return pytCompounded The amount of PYT distributed to XPYT holders
    /// @return pounderReward The amount of caller reward given, in PYT
    function pound(address pounderRewardRecipient)
        external
        virtual
        nonReentrant
        returns (
            uint256 yieldAmount,
            uint256 pytCompounded,
            uint256 pounderReward
        )
    {
        // claim yield from gate
        yieldAmount = gate.claimYieldAndEnter(
            address(this),
            address(this),
            vault,
            IxPYT(address(0))
        );

        // compute minXpytAmountOut based on the TWAP & minOutputMultiplier
        (bool success, uint256 twapQuoteAmountOut) = _getTwapQuote(yieldAmount);
        if (!success) {
            revert Error_ConsultTwapOracleFailed();
        }
        uint256 minXpytAmountOut = FullMath.mulDiv(
            twapQuoteAmountOut,
            minOutputMultiplier,
            ONE
        );

        // swap NYT into xPYT
        uint256 xPytAmountOut = _swap(yieldAmount);
        if (xPytAmountOut < minXpytAmountOut) {
            revert Error_InsufficientOutput();
        }

        // burn the xPYT
        uint256 pytAmountRedeemed = convertToAssets(xPytAmountOut);
        _burn(address(this), xPytAmountOut);

        // record PYT balance increase
        unchecked {
            // token balance cannot exceed 256 bits since totalSupply is an uint256
            pytCompounded = yieldAmount + pytAmountRedeemed;
            pounderReward = FullMath.mulDiv(
                pytCompounded,
                pounderRewardMultiplier,
                ONE
            );
            pytCompounded -= pounderReward;
            // don't add pytAmountRedeemed to assetBalance since it's already in the vault,
            // we just burnt the corresponding xPYT
            assetBalance = assetBalance + yieldAmount - pounderReward;
        }

        // transfer pounder reward
        asset.safeTransfer(pounderRewardRecipient, pounderReward);

        emit Pound(
            msg.sender,
            pounderRewardRecipient,
            yieldAmount,
            pytCompounded,
            pounderReward
        );
    }

    /// @notice Previews the result of calling pound()
    /// @return errorCode The end state of pound()
    /// @return yieldAmount The amount of PYT & NYT claimed as yield
    /// @return pytCompounded The amount of PYT distributed to xPYT holders
    /// @return pounderReward The amount of caller reward given, in PYT
    function previewPound()
        external
        returns (
            PreviewPoundErrorCode errorCode,
            uint256 yieldAmount,
            uint256 pytCompounded,
            uint256 pounderReward
        )
    {
        // get claimable yield amount from gate
        yieldAmount = gate.getClaimableYieldAmount(vault, address(this));

        // compute minXpytAmountOut based on the TWAP & minOutputMultiplier
        (bool twapSuccess, uint256 twapQuoteAmountOut) = _getTwapQuote(
            yieldAmount
        );
        if (!twapSuccess) {
            return (PreviewPoundErrorCode.TWAP_FAIL, 0, 0, 0);
        }
        uint256 minXpytAmountOut = FullMath.mulDiv(
            twapQuoteAmountOut,
            minOutputMultiplier,
            ONE
        );

        // simulate swapping NYT into PYT
        uint256 xPytAmountOut = _quote(yieldAmount);
        if (xPytAmountOut < minXpytAmountOut) {
            return (PreviewPoundErrorCode.INSUFFICIENT_OUTPUT, 0, 0, 0);
        }

        // burn the xPYT
        uint256 pytAmountRedeemed = convertToAssets(xPytAmountOut);

        // compute compounded PYT amount and pounder reward amount
        unchecked {
            // token balance cannot exceed 256 bits since totalSupply is an uint256
            pytCompounded = yieldAmount + pytAmountRedeemed;
            pounderReward = FullMath.mulDiv(
                pytCompounded,
                pounderRewardMultiplier,
                ONE
            );
            // don't add pytAmountRedeemed to assetBalance since it's already in the vault,
            // we just burnt the corresponding xPYT
            pytCompounded -= pounderReward;
        }

        // if execution has reached this point, the simulation was successful
        errorCode = PreviewPoundErrorCode.OK;
    }

    /// -----------------------------------------------------------------------
    /// Sweeping
    /// -----------------------------------------------------------------------

    /// @notice Uses the extra asset balance of the xPYT contract to mint shares
    /// @param receiver The recipient of the minted shares
    /// @return shares The amount of shares minted
    function sweep(address receiver) external virtual returns (uint256 shares) {
        uint256 assets = asset.balanceOf(address(this)) - assetBalance;

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

        _mint(receiver, shares);

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

        afterDeposit(assets, shares);
    }

    /// -----------------------------------------------------------------------
    /// ERC4626 overrides
    /// -----------------------------------------------------------------------

    function totalAssets() public view virtual override returns (uint256) {
        return assetBalance;
    }

    function beforeWithdraw(
        uint256 assets,
        uint256 /*shares*/
    ) internal virtual override {
        unchecked {
            assetBalance -= assets;
        }
    }

    function afterDeposit(
        uint256 assets,
        uint256 /*shares*/
    ) internal virtual override {
        assetBalance += assets;
    }

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

    /// @dev Consults the TWAP oracle to get a quote for how much xPYT will be received from swapping
    /// `nytAmountIn` NYT.
    /// @param nytAmountIn The amount of NYT to swap
    /// @return success True if the call to the TWAP oracle was successful, false otherwise
    /// @return xPytAmountOut The amount of xPYT that will be received from the swap
    function _getTwapQuote(uint256 nytAmountIn)
        internal
        view
        virtual
        returns (bool success, uint256 xPytAmountOut);

    /// @dev Swaps `nytAmountIn` NYT into xPYT using the underlying DEX
    /// @param nytAmountIn The amount of NYT to swap
    /// @return xPytAmountOut The amount of xPYT received from the swap
    function _swap(uint256 nytAmountIn)
        internal
        virtual
        returns (uint256 xPytAmountOut);

    /// @dev Gets a quote from the underlying DEX for swapping `nytAmountIn` NYT into xPYT
    /// @param nytAmountIn The amount of NYT to swap
    /// @return xPytAmountOut The amount of xPYT that will be received from the swap
    function _quote(uint256 nytAmountIn)
        internal
        virtual
        returns (uint256 xPytAmountOut);
}

/// @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 Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    error T();
    error R();

    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
            if (absTick > uint256(int256(MAX_TICK))) revert T();

            uint256 ratio = absTick & 0x1 != 0
                ? 0xfffcb933bd6fad37aa2d162d1a594001
                : 0x100000000000000000000000000000000;
            if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

            if (tick > 0) ratio = type(uint256).max / ratio;

            // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
            // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
            // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
            sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
        }
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // second inequality must be < because the price can never reach the price at the max tick
            if (!(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO)) revert R();
            uint256 ratio = uint256(sqrtPriceX96) << 32;

            uint256 r = ratio;
            uint256 msb = 0;

            assembly {
                let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(5, gt(r, 0xFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(4, gt(r, 0xFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(3, gt(r, 0xFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(2, gt(r, 0xF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(1, gt(r, 0x3))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := gt(r, 0x1)
                msb := or(msb, f)
            }

            if (msb >= 128) r = ratio >> (msb - 127);
            else r = ratio << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}

/// @title Oracle library
/// @notice Provides functions to integrate with V3 pool oracle
library OracleLibrary {
    /// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool
    /// @param pool Address of the pool that we want to observe
    /// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
    /// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp
    /// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp
    function consult(address pool, uint32 secondsAgo)
        internal
        view
        returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity)
    {
        require(secondsAgo != 0, "BP");

        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = secondsAgo;
        secondsAgos[1] = 0;

        (
            int56[] memory tickCumulatives,
            uint160[] memory secondsPerLiquidityCumulativeX128s
        ) = IUniswapV3Pool(pool).observe(secondsAgos);

        int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
        uint160 secondsPerLiquidityCumulativesDelta = secondsPerLiquidityCumulativeX128s[
                1
            ] - secondsPerLiquidityCumulativeX128s[0];

        arithmeticMeanTick = int24(
            tickCumulativesDelta / int56(uint56(secondsAgo))
        );
        // Always round to negative infinity
        if (
            tickCumulativesDelta < 0 &&
            (tickCumulativesDelta % int56(uint56(secondsAgo)) != 0)
        ) arithmeticMeanTick--;

        // We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128
        uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max;
        harmonicMeanLiquidity = uint128(
            secondsAgoX160 /
                (uint192(secondsPerLiquidityCumulativesDelta) << 32)
        );
    }

    /// @notice Given a tick and a token amount, calculates the amount of token received in exchange
    /// @param tick Tick value used to calculate the quote
    /// @param baseAmount Amount of token to be converted
    /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
    /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
    /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
    function getQuoteAtTick(
        int24 tick,
        uint128 baseAmount,
        address baseToken,
        address quoteToken
    ) internal pure returns (uint256 quoteAmount) {
        uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);

        // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
        if (sqrtRatioX96 <= type(uint128).max) {
            uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
            quoteAmount = baseToken < quoteToken
                ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
                : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
        } else {
            uint256 ratioX128 = FullMath.mulDiv(
                sqrtRatioX96,
                sqrtRatioX96,
                1 << 64
            );
            quoteAmount = baseToken < quoteToken
                ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
                : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
        }
    }

    /// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation
    /// @param pool Address of Uniswap V3 pool that we want to observe
    /// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool
    function getOldestObservationSecondsAgo(address pool)
        internal
        view
        returns (uint32 secondsAgo)
    {
        (
            ,
            ,
            uint16 observationIndex,
            uint16 observationCardinality,
            ,
            ,

        ) = IUniswapV3Pool(pool).slot0();
        require(observationCardinality > 0, "NI");

        (uint32 observationTimestamp, , , bool initialized) = IUniswapV3Pool(
            pool
        ).observations((observationIndex + 1) % observationCardinality);

        // The next index might not be initialized if the cardinality is in the process of increasing
        // In this case the oldest observation is always in index 0
        if (!initialized) {
            (observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0);
        }

        secondsAgo = uint32(block.timestamp) - observationTimestamp;
    }

    /// @notice Given a pool, it returns the tick value as of the start of the current block
    /// @param pool Address of Uniswap V3 pool
    /// @return The tick that the pool was in at the start of the current block
    function getBlockStartingTickAndLiquidity(address pool)
        internal
        view
        returns (int24, uint128)
    {
        (
            ,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            ,
            ,

        ) = IUniswapV3Pool(pool).slot0();

        // 2 observations are needed to reliably calculate the block starting tick
        require(observationCardinality > 1, "NEO");

        // If the latest observation occurred in the past, then no tick-changing trades have happened in this block
        // therefore the tick in `slot0` is the same as at the beginning of the current block.
        // We don't need to check if this observation is initialized - it is guaranteed to be.
        (
            uint32 observationTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,

        ) = IUniswapV3Pool(pool).observations(observationIndex);
        if (observationTimestamp != uint32(block.timestamp)) {
            return (tick, IUniswapV3Pool(pool).liquidity());
        }

        uint256 prevIndex = (uint256(observationIndex) +
            observationCardinality -
            1) % observationCardinality;
        (
            uint32 prevObservationTimestamp,
            int56 prevTickCumulative,
            uint160 prevSecondsPerLiquidityCumulativeX128,
            bool prevInitialized
        ) = IUniswapV3Pool(pool).observations(prevIndex);

        require(prevInitialized, "ONI");

        uint32 delta = observationTimestamp - prevObservationTimestamp;
        tick = int24(
            (tickCumulative - prevTickCumulative) / int56(uint56(delta))
        );
        uint128 liquidity = uint128(
            (uint192(delta) * type(uint160).max) /
                (uint192(
                    secondsPerLiquidityCumulativeX128 -
                        prevSecondsPerLiquidityCumulativeX128
                ) << 32)
        );
        return (tick, liquidity);
    }

    /// @notice Information for calculating a weighted arithmetic mean tick
    struct WeightedTickData {
        int24 tick;
        uint128 weight;
    }

    /// @notice Given an array of ticks and weights, calculates the weighted arithmetic mean tick
    /// @param weightedTickData An array of ticks and weights
    /// @return weightedArithmeticMeanTick The weighted arithmetic mean tick
    /// @dev Each entry of `weightedTickData` should represents ticks from pools with the same underlying pool tokens. If they do not,
    /// extreme care must be taken to ensure that ticks are comparable (including decimal differences).
    /// @dev Note that the weighted arithmetic mean tick corresponds to the weighted geometric mean price.
    function getWeightedArithmeticMeanTick(
        WeightedTickData[] memory weightedTickData
    ) internal pure returns (int24 weightedArithmeticMeanTick) {
        // Accumulates the sum of products between each tick and its weight
        int256 numerator;

        // Accumulates the sum of the weights
        uint256 denominator;

        // Products fit in 152 bits, so it would take an array of length ~2**104 to overflow this logic
        for (uint256 i; i < weightedTickData.length; i++) {
            numerator +=
                weightedTickData[i].tick *
                int256(int128(weightedTickData[i].weight));
            denominator += weightedTickData[i].weight;
        }

        weightedArithmeticMeanTick = int24(numerator / int256(denominator));
        // Always round to negative infinity
        if (numerator < 0 && (numerator % int256(denominator) != 0))
            weightedArithmeticMeanTick--;
    }

    /// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last
    /// @dev Useful for calculating relative prices along routes.
    /// @dev There must be one tick for each pairwise set of tokens.
    /// @param tokens The token contract addresses
    /// @param ticks The ticks, representing the price of each token pair in `tokens`
    /// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens`
    function getChainedPrice(address[] memory tokens, int24[] memory ticks)
        internal
        pure
        returns (int256 syntheticTick)
    {
        require(tokens.length - 1 == ticks.length, "DL");
        for (uint256 i = 1; i <= ticks.length; i++) {
            // check the tokens for address sort order, then accumulate the
            // ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out"
            tokens[i - 1] < tokens[i]
                ? syntheticTick += ticks[i - 1]
                : syntheticTick -= ticks[i - 1];
        }
    }
}

/// @title UniswapV3xPYT
/// @author zefram.eth
/// @notice xPYT implementation using Uniswap V3 to swap NYT into PYT
contract UniswapV3xPYT is XPYT, IUniswapV3SwapCallback {
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeTransferLib for ERC20;

    /// -----------------------------------------------------------------------
    /// 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;

    /// @notice The Uniswap V3 Quoter deployment
    IQuoter public immutable uniswapV3Quoter;

    /// @notice The fee used by the Uniswap V3 pool used for swapping
    uint24 public immutable uniswapV3PoolFee;

    /// @notice The number of seconds in the past from which to take the TWAP of the Uniswap V3 pool
    uint32 public immutable uniswapV3TwapSecondsAgo;

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

    constructor(
        ERC20 asset_,
        string memory name_,
        string memory symbol_,
        uint256 pounderRewardMultiplier_,
        uint256 minOutputMultiplier_,
        address uniswapV3Factory_,
        IQuoter uniswapV3Quoter_,
        uint24 uniswapV3PoolFee_,
        uint32 uniswapV3TwapSecondsAgo_
    )
        XPYT(
            asset_,
            name_,
            symbol_,
            pounderRewardMultiplier_,
            minOutputMultiplier_
        )
    {
        uniswapV3Factory = uniswapV3Factory_;
        uniswapV3Quoter = uniswapV3Quoter_;
        uniswapV3PoolFee = uniswapV3PoolFee_;
        uniswapV3TwapSecondsAgo = uniswapV3TwapSecondsAgo_;
    }

    /// -----------------------------------------------------------------------
    /// 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
    /// -----------------------------------------------------------------------

    /// @inheritdoc XPYT
    function _getTwapQuote(uint256 nytAmountIn)
        internal
        view
        virtual
        override
        returns (bool success, uint256 xPytAmountOut)
    {
        // get uniswap v3 pool
        address uniPool = PoolAddress.computeAddress(
            uniswapV3Factory,
            PoolAddress.getPoolKey(
                address(nyt),
                address(this),
                uniswapV3PoolFee
            )
        );

        // ensure oldest observation is at or before (block.timestamp - uniswapV3TwapSecondsAgo)
        uint32 oldestObservationSecondsAgo = OracleLibrary
            .getOldestObservationSecondsAgo(uniPool);
        if (oldestObservationSecondsAgo < uniswapV3TwapSecondsAgo) {
            return (false, 0);
        }

        // get mean tick from TWAP oracle
        (int24 arithmeticMeanTick, ) = OracleLibrary.consult(
            uniPool,
            uniswapV3TwapSecondsAgo
        );

        // convert mean tick to quote
        success = true;
        xPytAmountOut = OracleLibrary.getQuoteAtTick(
            arithmeticMeanTick,
            uint128(nytAmountIn),
            address(nyt),
            address(this)
        );
    }

    /// @inheritdoc XPYT
    function _swap(uint256 nytAmountIn)
        internal
        virtual
        override
        returns (uint256 xPytAmountOut)
    {
        // get uniswap v3 pool
        IUniswapV3Pool uniPool = IUniswapV3Pool(
            PoolAddress.computeAddress(
                uniswapV3Factory,
                PoolAddress.getPoolKey(
                    address(nyt),
                    address(this),
                    uniswapV3PoolFee
                )
            )
        );

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

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

    /// @inheritdoc XPYT
    function _quote(uint256 nytAmountIn)
        internal
        virtual
        override
        returns (uint256 xPytAmountOut)
    {
        bool zeroForOne = address(nyt) < address(this);
        return
            uniswapV3Quoter.quoteExactInputSingle(
                address(nyt),
                address(this),
                uniswapV3PoolFee,
                nytAmountIn,
                zeroForOne ? MIN_SQRT_RATIO_PLUS_ONE : MAX_SQRT_RATIO_MINUS_ONE
            );
    }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract ERC20","name":"asset_","type":"address"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint256","name":"pounderRewardMultiplier_","type":"uint256"},{"internalType":"uint256","name":"minOutputMultiplier_","type":"uint256"},{"internalType":"address","name":"uniswapV3Factory_","type":"address"},{"internalType":"contract IQuoter","name":"uniswapV3Quoter_","type":"address"},{"internalType":"uint24","name":"uniswapV3PoolFee_","type":"uint24"},{"internalType":"uint32","name":"uniswapV3TwapSecondsAgo_","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Error_BothTokenDeltasAreZero","type":"error"},{"inputs":[],"name":"Error_ConsultTwapOracleFailed","type":"error"},{"inputs":[],"name":"Error_InsufficientOutput","type":"error"},{"inputs":[],"name":"Error_InvalidMultiplierValue","type":"error"},{"inputs":[],"name":"Error_NotUniswapV3Pool","type":"error"},{"inputs":[],"name":"T","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"pounderRewardRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"yieldAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pytCompounded","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pounderReward","type":"uint256"}],"name":"Pound","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gate","outputs":[{"internalType":"contract Gate","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minOutputMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nyt","outputs":[{"internalType":"contract NegativeYieldToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","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":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pounderRewardRecipient","type":"address"}],"name":"pound","outputs":[{"internalType":"uint256","name":"yieldAmount","type":"uint256"},{"internalType":"uint256","name":"pytCompounded","type":"uint256"},{"internalType":"uint256","name":"pounderReward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pounderRewardMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"previewPound","outputs":[{"internalType":"enum XPYT.PreviewPoundErrorCode","name":"errorCode","type":"uint8"},{"internalType":"uint256","name":"yieldAmount","type":"uint256"},{"internalType":"uint256","name":"pytCompounded","type":"uint256"},{"internalType":"uint256","name":"pounderReward","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","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":[{"internalType":"address","name":"receiver","type":"address"}],"name":"sweep","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniswapV3Factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniswapV3PoolFee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniswapV3Quoter","outputs":[{"internalType":"contract IQuoter","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":"uniswapV3TwapSecondsAgo","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

61022060405260016006553480156200001757600080fd5b5060405162004f0138038062004f018339810160408190526200003a916200056d565b88888888888484848181846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000083573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000a991906200064d565b8251620000be906000906020860190620003a6565b508151620000d4906001906020850190620003a6565b5060ff81166080524660a052620000ea6200030a565b60c0525050506001600160a01b0390921660e0525050670de0b6b3a76400008111156200012a57604051633e8a238b60e01b815260040160405180910390fd5b610160819052610180829052670de0b6b3a76400008211156200016057604051633e8a238b60e01b815260040160405180910390fd5b6000856001600160a01b0316637a0ebc886040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001c7919062000679565b9050806001600160a01b0316610100816001600160a01b0316815250506000866001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000225573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200024b919062000679565b6001600160a01b0381811661012081905260405163696bf4b760e01b8152600481019190915291925083169063696bf4b790602401602060405180830381865afa1580156200029e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002c4919062000679565b6001600160a01b03908116610140529a8b166101a05250505050949095166101c0525062ffffff9091166101e05263ffffffff1661020052506200077895505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516200033e9190620006d5565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b828054620003b49062000699565b90600052602060002090601f016020900481019282620003d8576000855562000423565b82601f10620003f357805160ff191683800117855562000423565b8280016001018555821562000423579182015b828111156200042357825182559160200191906001019062000406565b506200043192915062000435565b5090565b5b8082111562000431576000815560010162000436565b6001600160a01b03811681146200046257600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200048d57600080fd5b81516001600160401b0380821115620004aa57620004aa62000465565b604051601f8301601f19908116603f01168101908282118183101715620004d557620004d562000465565b81604052838152602092508683858801011115620004f257600080fd5b600091505b83821015620005165785820183015181830184015290820190620004f7565b83821115620005285760008385830101525b9695505050505050565b80516200053f816200044c565b919050565b805162ffffff811681146200053f57600080fd5b805163ffffffff811681146200053f57600080fd5b60008060008060008060008060006101208a8c0312156200058d57600080fd5b89516200059a816200044c565b60208b01519099506001600160401b0380821115620005b857600080fd5b620005c68d838e016200047b565b995060408c0151915080821115620005dd57600080fd5b50620005ec8c828d016200047b565b97505060608a0151955060808a0151945060a08a01516200060d816200044c565b93506200061d60c08b0162000532565b92506200062d60e08b0162000544565b91506200063e6101008b0162000558565b90509295985092959850929598565b6000602082840312156200066057600080fd5b815160ff811681146200067257600080fd5b9392505050565b6000602082840312156200068c57600080fd5b815162000672816200044c565b600181811c90821680620006ae57607f821691505b602082108103620006cf57634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c915080831680620006f257607f831692505b602080841082036200071257634e487b7160e01b86526022600452602486fd5b8180156200072957600181146200073b576200076a565b60ff198616895284890196506200076a565b60008a81526020902060005b86811015620007625781548b82015290850190830162000747565b505084890196505b509498975050505050505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051614613620008ee6000396000818161055501528181612305015261234c01526000818161045f015281816122d0015281816124cb0152818161255801526129c60152600081816105be015261296801526000818161062d0152818161204d0152818161228a01526124850152600081816107dd0152818161110f01526114a501526000818161097701528181611077015261143101526000818161070f015281816122ae0152818161237e015281816124a901528181612500015281816125f70152818161292e01526129a40152600081816109fe01528181610f8a015261135a0152600081816106ae01528181610fb901526113910152600081816104bc01528181610a6b015281816111680152818161128e01528181611510015281816119080152611ade01526000610e9d01526000610e680152600061041901526146136000f3fe6080604052600436106102f25760003560e01c80638b2919a41161018f578063c63d75b6116100e1578063dd62ed3e1161008a578063f3995c6711610064578063f3995c67146109b9578063fa461e33146109cc578063fbfa77cf146109ec57600080fd5b8063dd62ed3e1461092d578063ed693a5914610965578063ef8b30f71461099957600080fd5b8063ce96cb77116100bb578063ce96cb77146108aa578063d505accf146108ca578063d905777e146108ea57600080fd5b8063c63d75b614610503578063c66f245514610874578063c6e6f5921461088a57600080fd5b8063ac9650d811610143578063b460af941161011d578063b460af941461081f578063ba0876521461083f578063c2e3140a1461085f57600080fd5b8063ac9650d8146107ab578063ad7d5013146107cb578063b3d7f6b9146107ff57600080fd5b806394bf804d1161017457806394bf804d1461075657806395d89b4114610776578063a9059cbb1461078b57600080fd5b80638b2919a4146106fd5780638f8ceed71461073157600080fd5b806338d52e0f11610248578063509e1f50116101fc57806370a08231116101d657806370a082311461066f5780637a0ebc881461069c5780637ecebe00146106d057600080fd5b8063509e1f50146105e05780635b5491821461061b5780636e553f651461064f57600080fd5b80634397c40f1161022d5780634397c40f146105435780634cdad5061461058c5780634d20d0f8146105ac57600080fd5b806338d52e0f146104aa578063402d267d1461050357600080fd5b80630a28a477116102aa578063313ce56711610284578063313ce5671461040757806335edc1041461044d5780633644e5151461049557600080fd5b80630a28a477146103b157806318160ddd146103d157806323b872dd146103e757600080fd5b806306fdde03116102db57806306fdde031461033f57806307a2d13a14610361578063095ea7b31461038157600080fd5b806301681a62146102f757806301e1d1141461032a575b600080fd5b34801561030357600080fd5b50610317610312366004613702565b610a20565b6040519081526020015b60405180910390f35b34801561033657600080fd5b50600754610317565b34801561034b57600080fd5b50610354610bc9565b6040516103219190613795565b34801561036d57600080fd5b5061031761037c3660046137a8565b610c57565b34801561038d57600080fd5b506103a161039c3660046137c1565b610c85565b6040519015158152602001610321565b3480156103bd57600080fd5b506103176103cc3660046137a8565b610cff565b3480156103dd57600080fd5b5061031760025481565b3480156103f357600080fd5b506103a16104023660046137ed565b610d20565b34801561041357600080fd5b5061043b7f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff9091168152602001610321565b34801561045957600080fd5b506104817f000000000000000000000000000000000000000000000000000000000000000081565b60405162ffffff9091168152602001610321565b3480156104a157600080fd5b50610317610e64565b3480156104b657600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610321565b34801561050f57600080fd5b5061031761051e366004613702565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90565b34801561054f57600080fd5b506105777f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610321565b34801561059857600080fd5b506103176105a73660046137a8565b610ebf565b3480156105b857600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b3480156105ec57600080fd5b506106006105fb366004613702565b610eca565b60408051938452602084019290925290820152606001610321565b34801561062757600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b34801561065b57600080fd5b5061031761066a36600461382e565b6111fd565b34801561067b57600080fd5b5061031761068a366004613702565b60036020526000908152604090205481565b3480156106a857600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b3480156106dc57600080fd5b506103176106eb366004613702565b60056020526000908152604090205481565b34801561070957600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b34801561073d57600080fd5b5061074661131d565b604051610321949392919061385e565b34801561076257600080fd5b5061031761077136600461382e565b6114e9565b34801561078257600080fd5b5061035461159f565b34801561079757600080fd5b506103a16107a63660046137c1565b6115ac565b6107be6107b93660046138b5565b611631565b604051610321919061392a565b3480156107d757600080fd5b506103177f000000000000000000000000000000000000000000000000000000000000000081565b34801561080b57600080fd5b5061031761081a3660046137a8565b6117a3565b34801561082b57600080fd5b5061031761083a3660046139aa565b6117c3565b34801561084b57600080fd5b5061031761085a3660046139aa565b61192f565b61087261086d3660046139fb565b611b05565b005b34801561088057600080fd5b5061031760075481565b34801561089657600080fd5b506103176108a53660046137a8565b611bb7565b3480156108b657600080fd5b506103176108c5366004613702565b611bd8565b3480156108d657600080fd5b506108726108e5366004613a57565b611c07565b3480156108f657600080fd5b50610317610905366004613702565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b34801561093957600080fd5b50610317610948366004613ac8565b600460209081526000928352604080842090915290825290205481565b34801561097157600080fd5b506103177f000000000000000000000000000000000000000000000000000000000000000081565b3480156109a557600080fd5b506103176109b43660046137a8565b611f26565b6108726109c73660046139fb565b611f31565b3480156109d857600080fd5b506108726109e7366004613af6565b611fe3565b3480156109f857600080fd5b506104de7f000000000000000000000000000000000000000000000000000000000000000081565b6007546040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600091829173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015610ab2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ad69190613b76565b610ae09190613bbe565b9050610aeb81611f26565b915081600003610b5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f53484152455300000000000000000000000000000000000000000060448201526064015b60405180910390fd5b610b668383612105565b604080518281526020810184905273ffffffffffffffffffffffffffffffffffffffff85169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610bc3818361217e565b50919050565b60008054610bd690613bd5565b80601f0160208091040260200160405190810160405280929190818152602001828054610c0290613bd5565b8015610c4f5780601f10610c2457610100808354040283529160200191610c4f565b820191906000526020600020905b815481529060010190602001808311610c3257829003601f168201915b505050505081565b6002546000908015610c7c57610c77610c6f60075490565b849083612199565b610c7e565b825b9392505050565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590610ced9086815260200190565b60405180910390a35060015b92915050565b6002546000908015610c7c57610c7781610d1860075490565b8591906121b8565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610db457610d828382613bbe565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff851660009081526003602052604081208054859290610de9908490613bbe565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610e519087815260200190565b60405180910390a3506001949350505050565b60007f00000000000000000000000000000000000000000000000000000000000000004614610e9a57610e956121e6565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b6000610cf982610c57565b6000806000600654600114610f3b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610b53565b60026006556040517f875efaae0000000000000000000000000000000000000000000000000000000081523060048201819052602482015273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081166044830152600060648301527f0000000000000000000000000000000000000000000000000000000000000000169063875efaae906084016020604051808303816000875af1158015611002573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110269190613b76565b925060008061103485612280565b915091508161106f576040517fd29a518f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110a4827f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400006123ad565b905060006110b18761247d565b9050818110156110ed576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110f882610c57565b905061110430836126ff565b808801965061113c877f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400006123ad565b600780548a0182900390559687900396955061118f73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168a8861278d565b604080518981526020810189905290810187905273ffffffffffffffffffffffffffffffffffffffff8a169033907f9c887064dc205d9d0f44ce02f33d4cb26f380d7a310268e968116a4465da36529060600160405180910390a35050600160065550939592945090925050565b600061120883611f26565b905080600003611274576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152606401610b53565b6112b673ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001633308661284c565b6112c08282612105565b604080518481526020810183905273ffffffffffffffffffffffffffffffffffffffff84169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610cf9838261217e565b6040517f748b259c00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811660048301523060248301526000918291829182917f0000000000000000000000000000000000000000000000000000000000000000169063748b259c90604401602060405180830381865afa1580156113d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113fc9190613b76565b925060008061140a85612280565b91509150816114295760016000806000955095509550955050506114e3565b600061145e827f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400006123ad565b9050600061146b87612912565b90508181101561148d57600260008060009750975097509750505050506114e3565b600061149882610c57565b905080880196506114d2877f0000000000000000000000000000000000000000000000000000000000000000670de0b6b3a76400006123ad565b955085870396506000985050505050505b90919293565b60006114f4836117a3565b905061153873ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001633308461284c565b6115428284612105565b604080518281526020810185905273ffffffffffffffffffffffffffffffffffffffff84169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610cf9818461217e565b60018054610bd690613bd5565b336000908152600360205260408120805483919083906115cd908490613bbe565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610ced9086815260200190565b60608167ffffffffffffffff81111561164c5761164c613c22565b60405190808252806020026020018201604052801561167f57816020015b606081526020019060019003908161166a5790505b50905060005b8281101561179c57600080308686858181106116a3576116a3613c51565b90506020028101906116b59190613c80565b6040516116c3929190613cec565b600060405180830381855af49150503d80600081146116fe576040519150601f19603f3d011682016040523d82523d6000602084013e611703565b606091505b5091509150816117695760448151101561171c57600080fd5b600481019050808060200190518101906117369190613d4b565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b539190613795565b8084848151811061177c5761177c613c51565b60200260200101819052505050808061179490613df4565b915050611685565b5092915050565b6002546000908015610c7c57610c776117bb60075490565b8490836121b8565b60006117ce84610cff565b90503373ffffffffffffffffffffffffffffffffffffffff8316146118835773ffffffffffffffffffffffffffffffffffffffff821660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146118815761184f8282613bbe565b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020555b505b60078054859003905561189682826126ff565b604080518581526020810183905273ffffffffffffffffffffffffffffffffffffffff808516929086169133917ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db910160405180910390a4610c7e73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016848661278d565b60003373ffffffffffffffffffffffffffffffffffffffff8316146119e45773ffffffffffffffffffffffffffffffffffffffff821660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146119e2576119b08582613bbe565b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020555b505b6119ed84610ebf565b905080600003611a59576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152606401610b53565b600780548290039055611a6c82856126ff565b604080518281526020810186905273ffffffffffffffffffffffffffffffffffffffff808516929086169133917ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db910160405180910390a4610c7e73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016848361278d565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152859073ffffffffffffffffffffffffffffffffffffffff88169063dd62ed3e90604401602060405180830381865afa158015611b77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b9b9190613b76565b1015611baf57611baf868686868686611f31565b505050505050565b6002546000908015610c7c57610c7781611bd060075490565b859190612199565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812054610cf990610c57565b42841015611c71576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610b53565b60006001611c7d610e64565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015611dcf573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611e4a57508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611eb0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610b53565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b6000610cf982611bb7565b6040517fd505accf000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018690526064810185905260ff8416608482015260a4810183905260c4810182905273ffffffffffffffffffffffffffffffffffffffff87169063d505accf9060e401600060405180830381600087803b158015611fc357600080fd5b505af1158015611fd7573d6000803e3d6000fd5b50505050505050505050565b600080851315611ff4575083612036565b6000841315612004575082612036565b6040517f3fbd122600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061204483850185613e2c565b905060006120887f0000000000000000000000000000000000000000000000000000000000000000612083846000015185602001518660400151612abd565b612b4f565b90503373ffffffffffffffffffffffffffffffffffffffff8216146120d9576040517f1a13dcc000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81516120fc9073ffffffffffffffffffffffffffffffffffffffff16338561278d565b50505050505050565b80600260008282546121179190613ea6565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b81600760008282546121909190613ea6565b90915550505050565b8282028115158415858304851417166121b157600080fd5b0492915050565b8282028115158415858304851417166121d057600080fd5b6001826001830304018115150290509392505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516122189190613ebe565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b60008060006122f47f00000000000000000000000000000000000000000000000000000000000000006120837f0000000000000000000000000000000000000000000000000000000000000000307f0000000000000000000000000000000000000000000000000000000000000000612abd565b9050600061230182612c88565b90507f000000000000000000000000000000000000000000000000000000000000000063ffffffff168163ffffffff161015612344575060009485945092505050565b6000612370837f0000000000000000000000000000000000000000000000000000000000000000612ed8565b509050600194506123a381877f00000000000000000000000000000000000000000000000000000000000000003061319c565b9350505050915091565b600080807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8587098587029250828110838203039150508060000361240457600084116123f957600080fd5b508290049050610c7e565b80841161241057600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b6000806124ef7f00000000000000000000000000000000000000000000000000000000000000006120837f0000000000000000000000000000000000000000000000000000000000000000307f0000000000000000000000000000000000000000000000000000000000000000612abd565b9050600060405180606001604052807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000062ffffff168152506040516020016125cb9190815173ffffffffffffffffffffffffffffffffffffffff90811682526020808401519091169082015260409182015162ffffff169181019190915260600190565b604051602081830303815290604052905060003073ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff161090506000808473ffffffffffffffffffffffffffffffffffffffff1663128acb0830858a876126705773fffd8963efd1fc6a506488495d951d5263988d25612677565b6401000276a45b896040518663ffffffff1660e01b8152600401612698959493929190613f90565b60408051808303816000875af11580156126b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126da9190613fd7565b91509150826126e957816126eb565b805b6126f490613ffb565b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290612734908490613bbe565b909155505060028054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001612172565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080612846576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610b53565b50505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d116001600051141617169150508061290b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610b53565b5050505050565b6000803073ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff161090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f7729d437f0000000000000000000000000000000000000000000000000000000000000000307f00000000000000000000000000000000000000000000000000000000000000008786612a055773fffd8963efd1fc6a506488495d951d5263988d25612a0c565b6401000276a45b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff9586166004820152938516602485015262ffffff9092166044840152606483015291909116608482015260a4016020604051808303816000875af1158015612a99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c7e9190613b76565b60408051606081018252600080825260208201819052918101919091528273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161115612b12579192915b506040805160608101825273ffffffffffffffffffffffffffffffffffffffff948516815292909316602083015262ffffff169181019190915290565b6000816020015173ffffffffffffffffffffffffffffffffffffffff16826000015173ffffffffffffffffffffffffffffffffffffffff1610612b9157600080fd5b8151602080840151604080860151815173ffffffffffffffffffffffffffffffffffffffff95861681860152949092168482015262ffffff90911660608085019190915281518085038201815260808501909252815191909201207fff0000000000000000000000000000000000000000000000000000000000000060a08401529085901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660a183015260b58201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d5820152610c7e9060f5016040516020818303038152906040528051906020012090565b60008060008373ffffffffffffffffffffffffffffffffffffffff16633850c7bd6040518163ffffffff1660e01b815260040160e060405180830381865afa158015612cd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cfc919061405a565b50505093509350505060008161ffff1611612d73576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4e490000000000000000000000000000000000000000000000000000000000006044820152606401610b53565b60008073ffffffffffffffffffffffffffffffffffffffff861663252c09d784612d9e8760016140ee565b612da89190614143565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815261ffff9091166004820152602401608060405180830381865afa158015612dff573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e239190614176565b93505050915080612ec4576040517f252c09d70000000000000000000000000000000000000000000000000000000081526000600482015273ffffffffffffffffffffffffffffffffffffffff87169063252c09d790602401608060405180830381865afa158015612e99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ebd9190614176565b5091935050505b612ece82426141d7565b9695505050505050565b6000808263ffffffff16600003612f4b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f42500000000000000000000000000000000000000000000000000000000000006044820152606401610b53565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612f8057612f80613c51565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612faf57612faf613c51565b602002602001019063ffffffff16908163ffffffff16815250506000808673ffffffffffffffffffffffffffffffffffffffff1663883bdbfd846040518263ffffffff1660e01b815260040161300591906141fc565b600060405180830381865afa158015613022573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261306891908101906142de565b9150915060008260008151811061308157613081613c51565b60200260200101518360018151811061309c5761309c613c51565b60200260200101516130ae91906143a1565b90506000826000815181106130c5576130c5613c51565b6020026020010151836001815181106130e0576130e0613c51565b60200260200101516130f29190614409565b905061310463ffffffff891683614436565b965060008260060b12801561312a575061312463ffffffff8916836144aa565b60060b15155b1561313d5786613139816144cc565b9750505b600061316373ffffffffffffffffffffffffffffffffffffffff63ffffffff8b16614529565b905061318d77ffffffffffffffffffffffffffffffffffffffff00000000602084901b1682614569565b96505050505050509250929050565b6000806131a886613376565b90506fffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff8216116132a75760006131f973ffffffffffffffffffffffffffffffffffffffff8316806145a0565b90508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1610613269576132647801000000000000000000000000000000000000000000000000876fffffffffffffffffffffffffffffffff16836123ad565b61329f565b61329f81876fffffffffffffffffffffffffffffffff1678010000000000000000000000000000000000000000000000006123ad565b92505061336d565b60006132d373ffffffffffffffffffffffffffffffffffffffff831680680100000000000000006123ad565b90508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161061333b57613336700100000000000000000000000000000000876fffffffffffffffffffffffffffffffff16836123ad565b613369565b61336981876fffffffffffffffffffffffffffffffff167001000000000000000000000000000000006123ad565b9250505b50949350505050565b60008060008360020b1261338d578260020b613395565b8260020b6000035b9050620d89e88111156133d4576040517f2bc80f3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000816001166000036133f85770010000000000000000000000000000000061340a565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561343e576ffff97272373d413259a46990580e213a0260801c5b600482161561345d576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b600882161561347c576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b601082161561349b576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156134ba576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156134d9576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156134f8576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615613518576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615613538576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615613558576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615613578576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615613598576fd097f3bdfd2022b8845ad8f792aa58250260801c5b6120008216156135b8576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156135d8576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156135f8576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615613619576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615613639576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615613658576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615613675576b048a170391f7dc42444e8fa20260801c5b60008460020b13156136b457807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff816136b0576136b0614114565b0490505b6401000000008106156136c85760016136cb565b60005b60ff16602082901c0192505050919050565b73ffffffffffffffffffffffffffffffffffffffff811681146136ff57600080fd5b50565b60006020828403121561371457600080fd5b8135610c7e816136dd565b60005b8381101561373a578181015183820152602001613722565b838111156128465750506000910152565b6000815180845261376381602086016020860161371f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c7e602083018461374b565b6000602082840312156137ba57600080fd5b5035919050565b600080604083850312156137d457600080fd5b82356137df816136dd565b946020939093013593505050565b60008060006060848603121561380257600080fd5b833561380d816136dd565b9250602084013561381d816136dd565b929592945050506040919091013590565b6000806040838503121561384157600080fd5b823591506020830135613853816136dd565b809150509250929050565b6080810160038610613899577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9481526020810193909352604083019190915260609091015290565b600080602083850312156138c857600080fd5b823567ffffffffffffffff808211156138e057600080fd5b818501915085601f8301126138f457600080fd5b81358181111561390357600080fd5b8660208260051b850101111561391857600080fd5b60209290920196919550909350505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561399d577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261398b85835161374b565b94509285019290850190600101613951565b5092979650505050505050565b6000806000606084860312156139bf57600080fd5b8335925060208401356139d1816136dd565b915060408401356139e1816136dd565b809150509250925092565b60ff811681146136ff57600080fd5b60008060008060008060c08789031215613a1457600080fd5b8635613a1f816136dd565b955060208701359450604087013593506060870135613a3d816139ec565b9598949750929560808101359460a0909101359350915050565b600080600080600080600060e0888a031215613a7257600080fd5b8735613a7d816136dd565b96506020880135613a8d816136dd565b955060408801359450606088013593506080880135613aab816139ec565b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215613adb57600080fd5b8235613ae6816136dd565b91506020830135613853816136dd565b60008060008060608587031215613b0c57600080fd5b8435935060208501359250604085013567ffffffffffffffff80821115613b3257600080fd5b818701915087601f830112613b4657600080fd5b813581811115613b5557600080fd5b886020828501011115613b6757600080fd5b95989497505060200194505050565b600060208284031215613b8857600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015613bd057613bd0613b8f565b500390565b600181811c90821680613be957607f821691505b602082108103610bc3577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613cb557600080fd5b83018035915067ffffffffffffffff821115613cd057600080fd5b602001915036819003821315613ce557600080fd5b9250929050565b8183823760009101908152919050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613d4357613d43613c22565b604052919050565b600060208284031215613d5d57600080fd5b815167ffffffffffffffff80821115613d7557600080fd5b818401915084601f830112613d8957600080fd5b815181811115613d9b57613d9b613c22565b613dcc60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613cfc565b9150808252856020828501011115613de357600080fd5b61336d81602084016020860161371f565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e2557613e25613b8f565b5060010190565b600060608284031215613e3e57600080fd5b6040516060810181811067ffffffffffffffff82111715613e6157613e61613c22565b6040528235613e6f816136dd565b81526020830135613e7f816136dd565b6020820152604083013562ffffff81168114613e9a57600080fd5b60408201529392505050565b60008219821115613eb957613eb9613b8f565b500190565b600080835481600182811c915080831680613eda57607f831692505b60208084108203613f12577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b818015613f265760018114613f5557613f82565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528489019650613f82565b60008a81526020902060005b86811015613f7a5781548b820152908501908301613f61565b505084890196505b509498975050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8088168352861515602084015285604084015280851660608401525060a060808301526126f460a083018461374b565b60008060408385031215613fea57600080fd5b505080516020909101519092909150565b60007f8000000000000000000000000000000000000000000000000000000000000000820361402c5761402c613b8f565b5060000390565b805161ffff8116811461404557600080fd5b919050565b8051801515811461404557600080fd5b600080600080600080600060e0888a03121561407557600080fd5b8751614080816136dd565b8097505060208801518060020b811461409857600080fd5b95506140a660408901614033565b94506140b460608901614033565b93506140c260808901614033565b925060a08801516140d2816139ec565b91506140e060c0890161404a565b905092959891949750929550565b600061ffff80831681851680830382111561410b5761410b613b8f565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061ffff8084168061415857614158614114565b92169190910692915050565b8051600681900b811461404557600080fd5b6000806000806080858703121561418c57600080fd5b845163ffffffff811681146141a057600080fd5b93506141ae60208601614164565b925060408501516141be816136dd565b91506141cc6060860161404a565b905092959194509250565b600063ffffffff838116908316818110156141f4576141f4613b8f565b039392505050565b6020808252825182820181905260009190848201906040850190845b8181101561423a57835163ffffffff1683529284019291840191600101614218565b50909695505050505050565b600067ffffffffffffffff82111561426057614260613c22565b5060051b60200190565b600082601f83011261427b57600080fd5b8151602061429061428b83614246565b613cfc565b82815260059290921b840181019181810190868411156142af57600080fd5b8286015b848110156142d35780516142c6816136dd565b83529183019183016142b3565b509695505050505050565b600080604083850312156142f157600080fd5b825167ffffffffffffffff8082111561430957600080fd5b818501915085601f83011261431d57600080fd5b8151602061432d61428b83614246565b82815260059290921b8401810191818101908984111561434c57600080fd5b948201945b838610156143715761436286614164565b82529482019490820190614351565b9188015191965090935050508082111561438a57600080fd5b506143978582860161426a565b9150509250929050565b60008160060b8360060b60008112817fffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000018312811516156143e4576143e4613b8f565b81667fffffffffffff0183138116156143ff576143ff613b8f565b5090039392505050565b600073ffffffffffffffffffffffffffffffffffffffff838116908316818110156141f4576141f4613b8f565b60008160060b8360060b8061444d5761444d614114565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81147fffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000831416156144a1576144a1613b8f565b90059392505050565b60008260060b806144bd576144bd614114565b808360060b0791505092915050565b60008160020b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800000810361450157614501613b8f565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0192915050565b600077ffffffffffffffffffffffffffffffffffffffffffffffff8083168185168183048111821515161561456057614560613b8f565b02949350505050565b600077ffffffffffffffffffffffffffffffffffffffffffffffff8084168061459457614594614114565b92169190910492915050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156145d8576145d8613b8f565b50029056fea2646970667358221220832f17e38b50631d77ff02822d449a12a858945c42ad88e3ce74476ca3f6feda64736f6c634300080d003300000000000000000000000077498edf4dd1cfb551976347e1ead990dde631490000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000d2f13f7789f00000000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab60000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000708000000000000000000000000000000000000000000000000000000000000001954696d656c657373205553444320795661756c74207850595400000000000000000000000000000000000000000000000000000000000000000000000000000fe2889e2d7976555344432d785059540000000000000000000000000000000000

Deployed Bytecode

0x6080604052600436106102f25760003560e01c80638b2919a41161018f578063c63d75b6116100e1578063dd62ed3e1161008a578063f3995c6711610064578063f3995c67146109b9578063fa461e33146109cc578063fbfa77cf146109ec57600080fd5b8063dd62ed3e1461092d578063ed693a5914610965578063ef8b30f71461099957600080fd5b8063ce96cb77116100bb578063ce96cb77146108aa578063d505accf146108ca578063d905777e146108ea57600080fd5b8063c63d75b614610503578063c66f245514610874578063c6e6f5921461088a57600080fd5b8063ac9650d811610143578063b460af941161011d578063b460af941461081f578063ba0876521461083f578063c2e3140a1461085f57600080fd5b8063ac9650d8146107ab578063ad7d5013146107cb578063b3d7f6b9146107ff57600080fd5b806394bf804d1161017457806394bf804d1461075657806395d89b4114610776578063a9059cbb1461078b57600080fd5b80638b2919a4146106fd5780638f8ceed71461073157600080fd5b806338d52e0f11610248578063509e1f50116101fc57806370a08231116101d657806370a082311461066f5780637a0ebc881461069c5780637ecebe00146106d057600080fd5b8063509e1f50146105e05780635b5491821461061b5780636e553f651461064f57600080fd5b80634397c40f1161022d5780634397c40f146105435780634cdad5061461058c5780634d20d0f8146105ac57600080fd5b806338d52e0f146104aa578063402d267d1461050357600080fd5b80630a28a477116102aa578063313ce56711610284578063313ce5671461040757806335edc1041461044d5780633644e5151461049557600080fd5b80630a28a477146103b157806318160ddd146103d157806323b872dd146103e757600080fd5b806306fdde03116102db57806306fdde031461033f57806307a2d13a14610361578063095ea7b31461038157600080fd5b806301681a62146102f757806301e1d1141461032a575b600080fd5b34801561030357600080fd5b50610317610312366004613702565b610a20565b6040519081526020015b60405180910390f35b34801561033657600080fd5b50600754610317565b34801561034b57600080fd5b50610354610bc9565b6040516103219190613795565b34801561036d57600080fd5b5061031761037c3660046137a8565b610c57565b34801561038d57600080fd5b506103a161039c3660046137c1565b610c85565b6040519015158152602001610321565b3480156103bd57600080fd5b506103176103cc3660046137a8565b610cff565b3480156103dd57600080fd5b5061031760025481565b3480156103f357600080fd5b506103a16104023660046137ed565b610d20565b34801561041357600080fd5b5061043b7f000000000000000000000000000000000000000000000000000000000000000681565b60405160ff9091168152602001610321565b34801561045957600080fd5b506104817f0000000000000000000000000000000000000000000000000000000000000bb881565b60405162ffffff9091168152602001610321565b3480156104a157600080fd5b50610317610e64565b3480156104b657600080fd5b506104de7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde6314981565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610321565b34801561050f57600080fd5b5061031761051e366004613702565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90565b34801561054f57600080fd5b506105777f000000000000000000000000000000000000000000000000000000000000070881565b60405163ffffffff9091168152602001610321565b34801561059857600080fd5b506103176105a73660046137a8565b610ebf565b3480156105b857600080fd5b506104de7f000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab681565b3480156105ec57600080fd5b506106006105fb366004613702565b610eca565b60408051938452602084019290925290820152606001610321565b34801561062757600080fd5b506104de7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b34801561065b57600080fd5b5061031761066a36600461382e565b6111fd565b34801561067b57600080fd5b5061031761068a366004613702565b60036020526000908152604090205481565b3480156106a857600080fd5b506104de7f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e281565b3480156106dc57600080fd5b506103176106eb366004613702565b60056020526000908152604090205481565b34801561070957600080fd5b506104de7f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b4630481565b34801561073d57600080fd5b5061074661131d565b604051610321949392919061385e565b34801561076257600080fd5b5061031761077136600461382e565b6114e9565b34801561078257600080fd5b5061035461159f565b34801561079757600080fd5b506103a16107a63660046137c1565b6115ac565b6107be6107b93660046138b5565b611631565b604051610321919061392a565b3480156107d757600080fd5b506103177f00000000000000000000000000000000000000000000000000b1a2bc2ec5000081565b34801561080b57600080fd5b5061031761081a3660046137a8565b6117a3565b34801561082b57600080fd5b5061031761083a3660046139aa565b6117c3565b34801561084b57600080fd5b5061031761085a3660046139aa565b61192f565b61087261086d3660046139fb565b611b05565b005b34801561088057600080fd5b5061031760075481565b34801561089657600080fd5b506103176108a53660046137a8565b611bb7565b3480156108b657600080fd5b506103176108c5366004613702565b611bd8565b3480156108d657600080fd5b506108726108e5366004613a57565b611c07565b3480156108f657600080fd5b50610317610905366004613702565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b34801561093957600080fd5b50610317610948366004613ac8565b600460209081526000928352604080842090915290825290205481565b34801561097157600080fd5b506103177f0000000000000000000000000000000000000000000000000d2f13f7789f000081565b3480156109a557600080fd5b506103176109b43660046137a8565b611f26565b6108726109c73660046139fb565b611f31565b3480156109d857600080fd5b506108726109e7366004613af6565b611fe3565b3480156109f857600080fd5b506104de7f000000000000000000000000a354f35829ae975e850e23e9615b11da1b3dc4de81565b6007546040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152600091829173ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde6314916906370a0823190602401602060405180830381865afa158015610ab2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ad69190613b76565b610ae09190613bbe565b9050610aeb81611f26565b915081600003610b5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f53484152455300000000000000000000000000000000000000000060448201526064015b60405180910390fd5b610b668383612105565b604080518281526020810184905273ffffffffffffffffffffffffffffffffffffffff85169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610bc3818361217e565b50919050565b60008054610bd690613bd5565b80601f0160208091040260200160405190810160405280929190818152602001828054610c0290613bd5565b8015610c4f5780601f10610c2457610100808354040283529160200191610c4f565b820191906000526020600020905b815481529060010190602001808311610c3257829003601f168201915b505050505081565b6002546000908015610c7c57610c77610c6f60075490565b849083612199565b610c7e565b825b9392505050565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590610ced9086815260200190565b60405180910390a35060015b92915050565b6002546000908015610c7c57610c7781610d1860075490565b8591906121b8565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610db457610d828382613bbe565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff851660009081526003602052604081208054859290610de9908490613bbe565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610e519087815260200190565b60405180910390a3506001949350505050565b60007f00000000000000000000000000000000000000000000000000000000000000014614610e9a57610e956121e6565b905090565b507ff6c5b47217dd80ba11dec3da37984dd3d22022c5edab1844cabf0a9a95eba7a190565b6000610cf982610c57565b6000806000600654600114610f3b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610b53565b60026006556040517f875efaae0000000000000000000000000000000000000000000000000000000081523060048201819052602482015273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a354f35829ae975e850e23e9615b11da1b3dc4de81166044830152600060648301527f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e2169063875efaae906084016020604051808303816000875af1158015611002573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110269190613b76565b925060008061103485612280565b915091508161106f576040517fd29a518f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110a4827f0000000000000000000000000000000000000000000000000d2f13f7789f0000670de0b6b3a76400006123ad565b905060006110b18761247d565b9050818110156110ed576040517ff580341600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110f882610c57565b905061110430836126ff565b808801965061113c877f00000000000000000000000000000000000000000000000000b1a2bc2ec50000670de0b6b3a76400006123ad565b600780548a0182900390559687900396955061118f73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde63149168a8861278d565b604080518981526020810189905290810187905273ffffffffffffffffffffffffffffffffffffffff8a169033907f9c887064dc205d9d0f44ce02f33d4cb26f380d7a310268e968116a4465da36529060600160405180910390a35050600160065550939592945090925050565b600061120883611f26565b905080600003611274576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152606401610b53565b6112b673ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde631491633308661284c565b6112c08282612105565b604080518481526020810183905273ffffffffffffffffffffffffffffffffffffffff84169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610cf9838261217e565b6040517f748b259c00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a354f35829ae975e850e23e9615b11da1b3dc4de811660048301523060248301526000918291829182917f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e2169063748b259c90604401602060405180830381865afa1580156113d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113fc9190613b76565b925060008061140a85612280565b91509150816114295760016000806000955095509550955050506114e3565b600061145e827f0000000000000000000000000000000000000000000000000d2f13f7789f0000670de0b6b3a76400006123ad565b9050600061146b87612912565b90508181101561148d57600260008060009750975097509750505050506114e3565b600061149882610c57565b905080880196506114d2877f00000000000000000000000000000000000000000000000000b1a2bc2ec50000670de0b6b3a76400006123ad565b955085870396506000985050505050505b90919293565b60006114f4836117a3565b905061153873ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde631491633308461284c565b6115428284612105565b604080518281526020810185905273ffffffffffffffffffffffffffffffffffffffff84169133917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3610cf9818461217e565b60018054610bd690613bd5565b336000908152600360205260408120805483919083906115cd908490613bbe565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610ced9086815260200190565b60608167ffffffffffffffff81111561164c5761164c613c22565b60405190808252806020026020018201604052801561167f57816020015b606081526020019060019003908161166a5790505b50905060005b8281101561179c57600080308686858181106116a3576116a3613c51565b90506020028101906116b59190613c80565b6040516116c3929190613cec565b600060405180830381855af49150503d80600081146116fe576040519150601f19603f3d011682016040523d82523d6000602084013e611703565b606091505b5091509150816117695760448151101561171c57600080fd5b600481019050808060200190518101906117369190613d4b565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b539190613795565b8084848151811061177c5761177c613c51565b60200260200101819052505050808061179490613df4565b915050611685565b5092915050565b6002546000908015610c7c57610c776117bb60075490565b8490836121b8565b60006117ce84610cff565b90503373ffffffffffffffffffffffffffffffffffffffff8316146118835773ffffffffffffffffffffffffffffffffffffffff821660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146118815761184f8282613bbe565b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020555b505b60078054859003905561189682826126ff565b604080518581526020810183905273ffffffffffffffffffffffffffffffffffffffff808516929086169133917ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db910160405180910390a4610c7e73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde6314916848661278d565b60003373ffffffffffffffffffffffffffffffffffffffff8316146119e45773ffffffffffffffffffffffffffffffffffffffff821660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146119e2576119b08582613bbe565b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020555b505b6119ed84610ebf565b905080600003611a59576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152606401610b53565b600780548290039055611a6c82856126ff565b604080518281526020810186905273ffffffffffffffffffffffffffffffffffffffff808516929086169133917ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db910160405180910390a4610c7e73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000077498edf4dd1cfb551976347e1ead990dde6314916848361278d565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152859073ffffffffffffffffffffffffffffffffffffffff88169063dd62ed3e90604401602060405180830381865afa158015611b77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b9b9190613b76565b1015611baf57611baf868686868686611f31565b505050505050565b6002546000908015610c7c57610c7781611bd060075490565b859190612199565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812054610cf990610c57565b42841015611c71576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610b53565b60006001611c7d610e64565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015611dcf573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611e4a57508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611eb0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610b53565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b6000610cf982611bb7565b6040517fd505accf000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018690526064810185905260ff8416608482015260a4810183905260c4810182905273ffffffffffffffffffffffffffffffffffffffff87169063d505accf9060e401600060405180830381600087803b158015611fc357600080fd5b505af1158015611fd7573d6000803e3d6000fd5b50505050505050505050565b600080851315611ff4575083612036565b6000841315612004575082612036565b6040517f3fbd122600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061204483850185613e2c565b905060006120887f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984612083846000015185602001518660400151612abd565b612b4f565b90503373ffffffffffffffffffffffffffffffffffffffff8216146120d9576040517f1a13dcc000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81516120fc9073ffffffffffffffffffffffffffffffffffffffff16338561278d565b50505050505050565b80600260008282546121179190613ea6565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b81600760008282546121909190613ea6565b90915550505050565b8282028115158415858304851417166121b157600080fd5b0492915050565b8282028115158415858304851417166121d057600080fd5b6001826001830304018115150290509392505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516122189190613ebe565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b60008060006122f47f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9846120837f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b46304307f0000000000000000000000000000000000000000000000000000000000000bb8612abd565b9050600061230182612c88565b90507f000000000000000000000000000000000000000000000000000000000000070863ffffffff168163ffffffff161015612344575060009485945092505050565b6000612370837f0000000000000000000000000000000000000000000000000000000000000708612ed8565b509050600194506123a381877f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b463043061319c565b9350505050915091565b600080807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8587098587029250828110838203039150508060000361240457600084116123f957600080fd5b508290049050610c7e565b80841161241057600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b6000806124ef7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9846120837f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b46304307f0000000000000000000000000000000000000000000000000000000000000bb8612abd565b9050600060405180606001604052807f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b4630473ffffffffffffffffffffffffffffffffffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020017f0000000000000000000000000000000000000000000000000000000000000bb862ffffff168152506040516020016125cb9190815173ffffffffffffffffffffffffffffffffffffffff90811682526020808401519091169082015260409182015162ffffff169181019190915260600190565b604051602081830303815290604052905060003073ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b4630473ffffffffffffffffffffffffffffffffffffffff161090506000808473ffffffffffffffffffffffffffffffffffffffff1663128acb0830858a876126705773fffd8963efd1fc6a506488495d951d5263988d25612677565b6401000276a45b896040518663ffffffff1660e01b8152600401612698959493929190613f90565b60408051808303816000875af11580156126b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126da9190613fd7565b91509150826126e957816126eb565b805b6126f490613ffb565b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290612734908490613bbe565b909155505060028054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001612172565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080612846576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610b53565b50505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d116001600051141617169150508061290b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610b53565b5050505050565b6000803073ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b4630473ffffffffffffffffffffffffffffffffffffffff161090507f000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab673ffffffffffffffffffffffffffffffffffffffff1663f7729d437f000000000000000000000000949f8214a37929ea9c89971f7f2249e0c9b46304307f0000000000000000000000000000000000000000000000000000000000000bb88786612a055773fffd8963efd1fc6a506488495d951d5263988d25612a0c565b6401000276a45b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff9586166004820152938516602485015262ffffff9092166044840152606483015291909116608482015260a4016020604051808303816000875af1158015612a99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c7e9190613b76565b60408051606081018252600080825260208201819052918101919091528273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161115612b12579192915b506040805160608101825273ffffffffffffffffffffffffffffffffffffffff948516815292909316602083015262ffffff169181019190915290565b6000816020015173ffffffffffffffffffffffffffffffffffffffff16826000015173ffffffffffffffffffffffffffffffffffffffff1610612b9157600080fd5b8151602080840151604080860151815173ffffffffffffffffffffffffffffffffffffffff95861681860152949092168482015262ffffff90911660608085019190915281518085038201815260808501909252815191909201207fff0000000000000000000000000000000000000000000000000000000000000060a08401529085901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660a183015260b58201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d5820152610c7e9060f5016040516020818303038152906040528051906020012090565b60008060008373ffffffffffffffffffffffffffffffffffffffff16633850c7bd6040518163ffffffff1660e01b815260040160e060405180830381865afa158015612cd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cfc919061405a565b50505093509350505060008161ffff1611612d73576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f4e490000000000000000000000000000000000000000000000000000000000006044820152606401610b53565b60008073ffffffffffffffffffffffffffffffffffffffff861663252c09d784612d9e8760016140ee565b612da89190614143565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815261ffff9091166004820152602401608060405180830381865afa158015612dff573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e239190614176565b93505050915080612ec4576040517f252c09d70000000000000000000000000000000000000000000000000000000081526000600482015273ffffffffffffffffffffffffffffffffffffffff87169063252c09d790602401608060405180830381865afa158015612e99573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ebd9190614176565b5091935050505b612ece82426141d7565b9695505050505050565b6000808263ffffffff16600003612f4b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f42500000000000000000000000000000000000000000000000000000000000006044820152606401610b53565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612f8057612f80613c51565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612faf57612faf613c51565b602002602001019063ffffffff16908163ffffffff16815250506000808673ffffffffffffffffffffffffffffffffffffffff1663883bdbfd846040518263ffffffff1660e01b815260040161300591906141fc565b600060405180830381865afa158015613022573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261306891908101906142de565b9150915060008260008151811061308157613081613c51565b60200260200101518360018151811061309c5761309c613c51565b60200260200101516130ae91906143a1565b90506000826000815181106130c5576130c5613c51565b6020026020010151836001815181106130e0576130e0613c51565b60200260200101516130f29190614409565b905061310463ffffffff891683614436565b965060008260060b12801561312a575061312463ffffffff8916836144aa565b60060b15155b1561313d5786613139816144cc565b9750505b600061316373ffffffffffffffffffffffffffffffffffffffff63ffffffff8b16614529565b905061318d77ffffffffffffffffffffffffffffffffffffffff00000000602084901b1682614569565b96505050505050509250929050565b6000806131a886613376565b90506fffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff8216116132a75760006131f973ffffffffffffffffffffffffffffffffffffffff8316806145a0565b90508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1610613269576132647801000000000000000000000000000000000000000000000000876fffffffffffffffffffffffffffffffff16836123ad565b61329f565b61329f81876fffffffffffffffffffffffffffffffff1678010000000000000000000000000000000000000000000000006123ad565b92505061336d565b60006132d373ffffffffffffffffffffffffffffffffffffffff831680680100000000000000006123ad565b90508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161061333b57613336700100000000000000000000000000000000876fffffffffffffffffffffffffffffffff16836123ad565b613369565b61336981876fffffffffffffffffffffffffffffffff167001000000000000000000000000000000006123ad565b9250505b50949350505050565b60008060008360020b1261338d578260020b613395565b8260020b6000035b9050620d89e88111156133d4576040517f2bc80f3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000816001166000036133f85770010000000000000000000000000000000061340a565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561343e576ffff97272373d413259a46990580e213a0260801c5b600482161561345d576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b600882161561347c576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b601082161561349b576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156134ba576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156134d9576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156134f8576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615613518576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615613538576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615613558576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615613578576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615613598576fd097f3bdfd2022b8845ad8f792aa58250260801c5b6120008216156135b8576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156135d8576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156135f8576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615613619576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615613639576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615613658576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615613675576b048a170391f7dc42444e8fa20260801c5b60008460020b13156136b457807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff816136b0576136b0614114565b0490505b6401000000008106156136c85760016136cb565b60005b60ff16602082901c0192505050919050565b73ffffffffffffffffffffffffffffffffffffffff811681146136ff57600080fd5b50565b60006020828403121561371457600080fd5b8135610c7e816136dd565b60005b8381101561373a578181015183820152602001613722565b838111156128465750506000910152565b6000815180845261376381602086016020860161371f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c7e602083018461374b565b6000602082840312156137ba57600080fd5b5035919050565b600080604083850312156137d457600080fd5b82356137df816136dd565b946020939093013593505050565b60008060006060848603121561380257600080fd5b833561380d816136dd565b9250602084013561381d816136dd565b929592945050506040919091013590565b6000806040838503121561384157600080fd5b823591506020830135613853816136dd565b809150509250929050565b6080810160038610613899577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9481526020810193909352604083019190915260609091015290565b600080602083850312156138c857600080fd5b823567ffffffffffffffff808211156138e057600080fd5b818501915085601f8301126138f457600080fd5b81358181111561390357600080fd5b8660208260051b850101111561391857600080fd5b60209290920196919550909350505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561399d577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261398b85835161374b565b94509285019290850190600101613951565b5092979650505050505050565b6000806000606084860312156139bf57600080fd5b8335925060208401356139d1816136dd565b915060408401356139e1816136dd565b809150509250925092565b60ff811681146136ff57600080fd5b60008060008060008060c08789031215613a1457600080fd5b8635613a1f816136dd565b955060208701359450604087013593506060870135613a3d816139ec565b9598949750929560808101359460a0909101359350915050565b600080600080600080600060e0888a031215613a7257600080fd5b8735613a7d816136dd565b96506020880135613a8d816136dd565b955060408801359450606088013593506080880135613aab816139ec565b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215613adb57600080fd5b8235613ae6816136dd565b91506020830135613853816136dd565b60008060008060608587031215613b0c57600080fd5b8435935060208501359250604085013567ffffffffffffffff80821115613b3257600080fd5b818701915087601f830112613b4657600080fd5b813581811115613b5557600080fd5b886020828501011115613b6757600080fd5b95989497505060200194505050565b600060208284031215613b8857600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015613bd057613bd0613b8f565b500390565b600181811c90821680613be957607f821691505b602082108103610bc3577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613cb557600080fd5b83018035915067ffffffffffffffff821115613cd057600080fd5b602001915036819003821315613ce557600080fd5b9250929050565b8183823760009101908152919050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613d4357613d43613c22565b604052919050565b600060208284031215613d5d57600080fd5b815167ffffffffffffffff80821115613d7557600080fd5b818401915084601f830112613d8957600080fd5b815181811115613d9b57613d9b613c22565b613dcc60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613cfc565b9150808252856020828501011115613de357600080fd5b61336d81602084016020860161371f565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e2557613e25613b8f565b5060010190565b600060608284031215613e3e57600080fd5b6040516060810181811067ffffffffffffffff82111715613e6157613e61613c22565b6040528235613e6f816136dd565b81526020830135613e7f816136dd565b6020820152604083013562ffffff81168114613e9a57600080fd5b60408201529392505050565b60008219821115613eb957613eb9613b8f565b500190565b600080835481600182811c915080831680613eda57607f831692505b60208084108203613f12577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b818015613f265760018114613f5557613f82565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528489019650613f82565b60008a81526020902060005b86811015613f7a5781548b820152908501908301613f61565b505084890196505b509498975050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8088168352861515602084015285604084015280851660608401525060a060808301526126f460a083018461374b565b60008060408385031215613fea57600080fd5b505080516020909101519092909150565b60007f8000000000000000000000000000000000000000000000000000000000000000820361402c5761402c613b8f565b5060000390565b805161ffff8116811461404557600080fd5b919050565b8051801515811461404557600080fd5b600080600080600080600060e0888a03121561407557600080fd5b8751614080816136dd565b8097505060208801518060020b811461409857600080fd5b95506140a660408901614033565b94506140b460608901614033565b93506140c260808901614033565b925060a08801516140d2816139ec565b91506140e060c0890161404a565b905092959891949750929550565b600061ffff80831681851680830382111561410b5761410b613b8f565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061ffff8084168061415857614158614114565b92169190910692915050565b8051600681900b811461404557600080fd5b6000806000806080858703121561418c57600080fd5b845163ffffffff811681146141a057600080fd5b93506141ae60208601614164565b925060408501516141be816136dd565b91506141cc6060860161404a565b905092959194509250565b600063ffffffff838116908316818110156141f4576141f4613b8f565b039392505050565b6020808252825182820181905260009190848201906040850190845b8181101561423a57835163ffffffff1683529284019291840191600101614218565b50909695505050505050565b600067ffffffffffffffff82111561426057614260613c22565b5060051b60200190565b600082601f83011261427b57600080fd5b8151602061429061428b83614246565b613cfc565b82815260059290921b840181019181810190868411156142af57600080fd5b8286015b848110156142d35780516142c6816136dd565b83529183019183016142b3565b509695505050505050565b600080604083850312156142f157600080fd5b825167ffffffffffffffff8082111561430957600080fd5b818501915085601f83011261431d57600080fd5b8151602061432d61428b83614246565b82815260059290921b8401810191818101908984111561434c57600080fd5b948201945b838610156143715761436286614164565b82529482019490820190614351565b9188015191965090935050508082111561438a57600080fd5b506143978582860161426a565b9150509250929050565b60008160060b8360060b60008112817fffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000018312811516156143e4576143e4613b8f565b81667fffffffffffff0183138116156143ff576143ff613b8f565b5090039392505050565b600073ffffffffffffffffffffffffffffffffffffffff838116908316818110156141f4576141f4613b8f565b60008160060b8360060b8061444d5761444d614114565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81147fffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000831416156144a1576144a1613b8f565b90059392505050565b60008260060b806144bd576144bd614114565b808360060b0791505092915050565b60008160020b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800000810361450157614501613b8f565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0192915050565b600077ffffffffffffffffffffffffffffffffffffffffffffffff8083168185168183048111821515161561456057614560613b8f565b02949350505050565b600077ffffffffffffffffffffffffffffffffffffffffffffffff8084168061459457614594614114565b92169190910492915050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156145d8576145d8613b8f565b50029056fea2646970667358221220832f17e38b50631d77ff02822d449a12a858945c42ad88e3ce74476ca3f6feda64736f6c634300080d0033

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

00000000000000000000000077498edf4dd1cfb551976347e1ead990dde631490000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000d2f13f7789f00000000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab60000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000708000000000000000000000000000000000000000000000000000000000000001954696d656c657373205553444320795661756c74207850595400000000000000000000000000000000000000000000000000000000000000000000000000000fe2889e2d7976555344432d785059540000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : asset_ (address): 0x77498eDf4dD1Cfb551976347E1EaD990DDE63149
Arg [1] : name_ (string): Timeless USDC yVault xPYT
Arg [2] : symbol_ (string): ∞-yvUSDC-xPYT
Arg [3] : pounderRewardMultiplier_ (uint256): 50000000000000000
Arg [4] : minOutputMultiplier_ (uint256): 950000000000000000
Arg [5] : uniswapV3Factory_ (address): 0x1F98431c8aD98523631AE4a59f267346ea31F984
Arg [6] : uniswapV3Quoter_ (address): 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6
Arg [7] : uniswapV3PoolFee_ (uint24): 3000
Arg [8] : uniswapV3TwapSecondsAgo_ (uint32): 1800

-----Encoded View---------------
13 Constructor Arguments found :
Arg [0] : 00000000000000000000000077498edf4dd1cfb551976347e1ead990dde63149
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000120
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000160
Arg [3] : 00000000000000000000000000000000000000000000000000b1a2bc2ec50000
Arg [4] : 0000000000000000000000000000000000000000000000000d2f13f7789f0000
Arg [5] : 0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f984
Arg [6] : 000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab6
Arg [7] : 0000000000000000000000000000000000000000000000000000000000000bb8
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000708
Arg [9] : 0000000000000000000000000000000000000000000000000000000000000019
Arg [10] : 54696d656c657373205553444320795661756c74207850595400000000000000
Arg [11] : 000000000000000000000000000000000000000000000000000000000000000f
Arg [12] : e2889e2d7976555344432d785059540000000000000000000000000000000000


Deployed Bytecode Sourcemap

168412:7689:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;144500:447;;;;;;;;;;-1:-1:-1;144500:447:0;;;;;:::i;:::-;;:::i;:::-;;;571:25:1;;;559:2;544:18;144500:447:0;;;;;;;;145146:108;;;;;;;;;;-1:-1:-1;145234:12:0;;145146:108;;1042:18;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;54087:261::-;;;;;;;;;;-1:-1:-1;54087:261:0;;;;;:::i;:::-;;:::i;2519:217::-;;;;;;;;;;-1:-1:-1;2519:217:0;;;;;:::i;:::-;;:::i;:::-;;;2087:14:1;;2080:22;2062:41;;2050:2;2035:18;2519:217:0;1922:187:1;54754:259:0;;;;;;;;;;-1:-1:-1;54754:259:0;;;;;:::i;:::-;;:::i;1325:26::-;;;;;;;;;;;;;;;;3137:612;;;;;;;;;;-1:-1:-1;3137:612:0;;;;;:::i;:::-;;:::i;1098:31::-;;;;;;;;;;;;;;;;;;2747:4:1;2735:17;;;2717:36;;2705:2;2690:18;1098:31:0;2575:184:1;170463:40:0;;;;;;;;;;;;;;;;;;2938:8:1;2926:21;;;2908:40;;2896:2;2881:18;170463:40:0;2764:190:1;5479:179:0;;;;;;;;;;;;;:::i;50675:28::-;;;;;;;;;;;;;;;;;;3330:42:1;3318:55;;;3300:74;;3288:2;3273:18;50675:28:0;3141:239:1;55350:110:0;;;;;;;;;;-1:-1:-1;55350:110:0;;;;;:::i;:::-;-1:-1:-1;55435:17:0;;55350:110;170614:47;;;;;;;;;;;;;;;;;;3559:10:1;3547:23;;;3529:42;;3517:2;3502:18;170614:47:0;3385:192:1;55021:126:0;;;;;;;;;;-1:-1:-1;55021:126:0;;;;;:::i;:::-;;:::i;170343:40::-;;;;;;;;;;;;;;;139809:2114;;;;;;;;;;-1:-1:-1;139809:2114:0;;;;;:::i;:::-;;:::i;:::-;;;;4030:25:1;;;4086:2;4071:18;;4064:34;;;;4114:18;;;4107:34;4018:2;4003:18;139809:2114:0;3828:319:1;170243:41:0;;;;;;;;;;;;;;;51090:528;;;;;;;;;;-1:-1:-1;51090:528:0;;;;;:::i;:::-;;:::i;1360:44::-;;;;;;;;;;-1:-1:-1;1360:44:0;;;;;:::i;:::-;;;;;;;;;;;;;;136988:26;;;;;;;;;;;;;;;1786:41;;;;;;;;;;-1:-1:-1;1786:41:0;;;;;:::i;:::-;;;;;;;;;;;;;;137164:39;;;;;;;;;;;;;;;142259:1857;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;51626:478::-;;;;;;;;;;-1:-1:-1;51626:478:0;;;;;:::i;:::-;;:::i;1069:20::-;;;;;;;;;;;;;:::i;2744:385::-;;;;;;;;;;-1:-1:-1;2744:385:0;;;;;:::i;:::-;;:::i;77224:726::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;137528:48::-;;;;;;;;;;;;;;;54491:255;;;;;;;;;;-1:-1:-1;54491:255:0;;;;;:::i;:::-;;:::i;52112:699::-;;;;;;;;;;-1:-1:-1;52112:699:0;;;;;:::i;:::-;;:::i;52819:734::-;;;;;;;;;;-1:-1:-1;52819:734:0;;;;;:::i;:::-;;:::i;78557:317::-;;;;;;:::i;:::-;;:::i;:::-;;138036:27;;;;;;;;;;;;;;;;53818:261;;;;;;;;;;-1:-1:-1;53818:261:0;;;;;:::i;:::-;;:::i;55583:133::-;;;;;;;;;;-1:-1:-1;55583:133:0;;;;;:::i;:::-;;:::i;3944:1527::-;;;;;;;;;;-1:-1:-1;3944:1527:0;;;;;:::i;:::-;;:::i;55724:114::-;;;;;;;;;;-1:-1:-1;55724:114:0;;;;;:::i;:::-;55814:16;;55787:7;55814:16;;;:9;:16;;;;;;;55724:114;1413:64;;;;;;;;;;-1:-1:-1;1413:64:0;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;137363:44;;;;;;;;;;;;;;;54356:127;;;;;;;;;;-1:-1:-1;54356:127:0;;;;;:::i;:::-;;:::i;78292:257::-;;;;;;:::i;:::-;;:::i;171808:1147::-;;;;;;;;;;-1:-1:-1;171808:1147:0;;;;;:::i;:::-;;:::i;137075:30::-;;;;;;;;;;;;;;;144500:447;144636:12;;144603:30;;;;;144627:4;144603:30;;;3300:74:1;144559:14:0;;;;144603:15;:5;:15;;;;3273:18:1;;144603:30:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:45;;;;:::i;:::-;144586:62;;144755:22;144770:6;144755:14;:22::i;:::-;144746:31;;;144782:1;144745:38;144737:62;;;;;;;11227:2:1;144737:62:0;;;11209:21:1;11266:2;11246:18;;;11239:30;11305:13;11285:18;;;11278:41;11336:18;;144737:62:0;;;;;;;;;144812:23;144818:8;144828:6;144812:5;:23::i;:::-;144853:45;;;11539:25:1;;;11595:2;11580:18;;11573:34;;;144853:45:0;;;;144861:10;;144853:45;;11512:18:1;144853:45:0;;;;;;;144911:28;144924:6;144932;144911:12;:28::i;:::-;144575:372;144500:447;;;:::o;1042:18::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;54087:261::-;54194:11;;54157:7;;54277:11;;:63;;54300:40;54318:13;145234:12;;;145146:108;54318:13;54300:6;;54333;54300:17;:40::i;:::-;54277:63;;;54291:6;54277:63;54270:70;54087:261;-1:-1:-1;;;54087:261:0:o;2519:217::-;2620:10;2593:4;2610:21;;;:9;:21;;;;;;;;;:30;;;;;;;;;;:39;;;2667:37;2593:4;;2610:30;;2667:37;;;;2643:6;571:25:1;;559:2;544:18;;425:177;2667:37:0;;;;;;;;-1:-1:-1;2724:4:0;2519:217;;;;;:::o;54754:259::-;54861:11;;54824:7;;54944:11;;:61;;54967:38;54983:6;54991:13;145234:12;;;145146:108;54991:13;54967:6;;:38;:15;:38::i;3137:612::-;3294:15;;;3259:4;3294:15;;;:9;:15;;;;;;;;3310:10;3294:27;;;;;;;;3385:17;3374:28;;3370:80;;3434:16;3444:6;3434:7;:16;:::i;:::-;3404:15;;;;;;;:9;:15;;;;;;;;3420:10;3404:27;;;;;;;:46;3370:80;3463:15;;;;;;;:9;:15;;;;;:25;;3482:6;;3463:15;:25;;3482:6;;3463:25;:::i;:::-;;;;-1:-1:-1;;3639:13:0;;;;;;;;:9;:13;;;;;;;:23;;;;;;3691:26;3639:13;;3691:26;;;;;;;3656:6;571:25:1;;559:2;544:18;;425:177;3691:26:0;;;;;;;;-1:-1:-1;3737:4:0;;3137:612;-1:-1:-1;;;;3137:612:0:o;5479:179::-;5536:7;5580:16;5563:13;:33;:87;;5626:24;:22;:24::i;:::-;5556:94;;5479:179;:::o;5563:87::-;-1:-1:-1;5599:24:0;;5479:179::o;55021:126::-;55089:7;55116:23;55132:6;55116:15;:23::i;139809:2114::-;139945:19;139979:21;140015;56647:6;;56657:1;56647:11;56639:34;;;;;;;12262:2:1;56639:34:0;;;12244:21:1;12301:2;12281:18;;;12274:30;12340:12;12320:18;;;12313:40;12370:18;;56639:34:0;12060:334:1;56639:34:0;56695:1;56686:6;:10;140112:142:::1;::::0;;;;140158:4:::1;140112:142;::::0;::::1;12705:34:1::0;;;12755:18;;;12748:43;140112:23:0::1;140206:5;12827:15:1::0;;12807:18;;;12800:43;-1:-1:-1;12859:18:1;;;12852:43;140112:4:0::1;:23;::::0;::::1;::::0;12616:19:1;;140112:142:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;140098:156;;140345:12;140359:26:::0;140389::::1;140403:11;140389:13;:26::i;:::-;140344:71;;;;140431:7;140426:79;;140462:31;;;;;;;;;;;;;;140426:79;140515:24;140542:111;140572:18;140605:19;136728:6;140542:15;:111::i;:::-;140515:138;;140697:21;140721:18;140727:11;140721:5;:18::i;:::-;140697:42;;140770:16;140754:13;:32;140750:98;;;140810:26;;;;;;;;;;;;;;140750:98;140886:25;140914:30;140930:13;140914:15;:30::i;:::-;140886:58;;140955:35;140969:4;140976:13;140955:5;:35::i;:::-;141183:17;141169:11;:31;141153:47;;141231:126;141265:13;141297:23;136728:6;141231:15;:126::i;:::-;141578:12;::::0;;:26;::::1;:42:::0;;::::1;141563:57:::0;;141372:30;;;::::1;::::0;141215:142;-1:-1:-1;141680:57:0::1;:18;:5;:18;141699:22:::0;141215:142;141680:18:::1;:57::i;:::-;141755:160;::::0;;4030:25:1;;;4086:2;4071:18;;4064:34;;;4114:18;;;4107:34;;;141755:160:0::1;::::0;::::1;::::0;141775:10:::1;::::0;141755:160:::1;::::0;4018:2:1;4003:18;141755:160:0::1;;;;;;;-1:-1:-1::0;;56732:1:0;56723:6;:10;-1:-1:-1;139809:2114:0;;;;-1:-1:-1;139809:2114:0;;-1:-1:-1;;139809:2114:0:o;51090:528::-;51165:14;51286:22;51301:6;51286:14;:22::i;:::-;51277:31;;;51313:1;51276:38;51268:62;;;;;;;11227:2:1;51268:62:0;;;11209:21:1;11266:2;11246:18;;;11239:30;11305:13;11285:18;;;11278:41;11336:18;;51268:62:0;11025:335:1;51268:62:0;51413:57;:22;:5;:22;51436:10;51456:4;51463:6;51413:22;:57::i;:::-;51483:23;51489:8;51499:6;51483:5;:23::i;:::-;51524:45;;;11539:25:1;;;11595:2;11580:18;;11573:34;;;51524:45:0;;;;51532:10;;51524:45;;11512:18:1;51524:45:0;;;;;;;51582:28;51595:6;51603;51582:12;:28::i;142259:1857::-;142561:50;;;;;:28;142590:5;13159:15:1;;142561:50:0;;;13141:34:1;142605:4:0;13191:18:1;;;13184:43;-1:-1:-1;;;;;;;;142561:4:0;:28;;;;13053:18:1;;142561:50:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;142547:64;;142702:16;142720:26;142750:50;142778:11;142750:13;:50::i;:::-;142701:99;;;;142816:11;142811:94;;142852:31;142885:1;142888;142891;142844:49;;;;;;;;;;;;142811:94;142915:24;142942:111;142972:18;143005:19;136728:6;142942:15;:111::i;:::-;142915:138;;143109:21;143133:19;143140:11;143133:6;:19::i;:::-;143109:43;;143183:16;143167:13;:32;143163:124;;;143224:41;143267:1;143270;143273;143216:59;;;;;;;;;;;;;;143163:124;143325:25;143353:30;143369:13;143353:15;:30::i;:::-;143325:58;;143604:17;143590:11;:31;143574:47;;143652:126;143686:13;143718:23;136728:6;143652:15;:126::i;:::-;143636:142;;143956:13;143939:30;;;;144084:24;144072:36;;142487:1629;;;;;142259:1857;;;;;:::o;51626:478::-;51698:14;51734:19;51746:6;51734:11;:19::i;:::-;51725:28;-1:-1:-1;51899:57:0;:22;:5;:22;51922:10;51942:4;51725:28;51899:22;:57::i;:::-;51969:23;51975:8;51985:6;51969:5;:23::i;:::-;52010:45;;;11539:25:1;;;11595:2;11580:18;;11573:34;;;52010:45:0;;;;52018:10;;52010:45;;11512:18:1;52010:45:0;;;;;;;52068:28;52081:6;52089;52068:12;:28::i;1069:20::-;;;;;;;:::i;2744:385::-;2841:10;2814:4;2831:21;;;:9;:21;;;;;:31;;2856:6;;2831:21;2814:4;;2831:31;;2856:6;;2831:31;:::i;:::-;;;;-1:-1:-1;;3013:13:0;;;;;;;:9;:13;;;;;;;:23;;;;;;3065:32;3074:10;;3065:32;;;;3030:6;571:25:1;;559:2;544:18;;425:177;77224:726:0;77319:22;77381:4;77369:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;77359:34;;77409:9;77404:539;77424:15;;;77404:539;;;77462:12;;77507:4;77544;;77549:1;77544:7;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;77499:67;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;77461:105;;;;77588:7;77583:313;;77717:2;77701:6;:13;:18;77697:32;;;77721:8;;;77697:32;77802:4;77794:6;77790:17;77780:27;;77862:6;77851:28;;;;;;;;;;;;:::i;:::-;77844:36;;;;;;;;;;;:::i;77583:313::-;77925:6;77912:7;77920:1;77912:10;;;;;;;;:::i;:::-;;;;;;:19;;;;77446:497;;77441:3;;;;;:::i;:::-;;;;77404:539;;;;77224:726;;;;:::o;54491:255::-;54594:11;;54557:7;;54677:11;;:61;;54700:38;54716:13;145234:12;;;145146:108;54716:13;54700:6;;54731;54700:15;:38::i;52112:699::-;52237:14;52273:23;52289:6;52273:15;:23::i;:::-;52264:32;-1:-1:-1;52380:10:0;:19;;;;52376:232;;52434:16;;;52416:15;52434:16;;;:9;:16;;;;;;;;52451:10;52434:28;;;;;;;;52530:17;52519:28;;52515:81;;52580:16;52590:6;52580:7;:16;:::i;:::-;52549;;;;;;;:9;:16;;;;;;;;52566:10;52549:28;;;;;;;:47;52515:81;52401:207;52376:232;145409:12;:22;;;;;;;52663:20;52669:5;52676:6;52663:5;:20::i;:::-;52701:53;;;11539:25:1;;;11595:2;11580:18;;11573:34;;;52701:53:0;;;;;;;;;52710:10;;52701:53;;11512:18:1;52701:53:0;;;;;;;52767:36;:18;:5;:18;52786:8;52796:6;52767:18;:36::i;52819:734::-;52942:14;52973:10;:19;;;;52969:232;;53027:16;;;53009:15;53027:16;;;:9;:16;;;;;;;;53044:10;53027:28;;;;;;;;53123:17;53112:28;;53108:81;;53173:16;53183:6;53173:7;:16;:::i;:::-;53142;;;;;;;:9;:16;;;;;;;;53159:10;53142:28;;;;;;;:47;53108:81;52994:207;52969:232;53306:21;53320:6;53306:13;:21::i;:::-;53297:30;;;53332:1;53296:37;53288:61;;;;;;;15988:2:1;53288:61:0;;;15970:21:1;16027:2;16007:18;;;16000:30;16066:13;16046:18;;;16039:41;16097:18;;53288:61:0;15786:335:1;53288:61:0;145409:12;:22;;;;;;;53405:20;53411:5;53418:6;53405:5;:20::i;:::-;53443:53;;;11539:25:1;;;11595:2;11580:18;;11573:34;;;53443:53:0;;;;;;;;;53452:10;;53443:53;;11512:18:1;53443:53:0;;;;;;;53509:36;:18;:5;:18;53528:8;53538:6;53509:18;:36::i;78557:317::-;78758:42;;;;;78774:10;78758:42;;;13141:34:1;78794:4:0;13191:18:1;;;13184:43;78803:5:0;;78758:15;;;;;;13053:18:1;;78758:42:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:50;78754:112;;;78823:43;78834:5;78841;78848:8;78858:1;78861;78864;78823:10;:43::i;:::-;78557:317;;;;;;:::o;53818:261::-;53925:11;;53888:7;;54008:11;;:63;;54031:40;54049:6;54057:13;145234:12;;;145146:108;54057:13;54031:6;;:40;:17;:40::i;55583:133::-;55691:16;;;55648:7;55691:16;;;:9;:16;;;;;;55675:33;;:15;:33::i;3944:1527::-;4172:15;4160:8;:27;;4152:63;;;;;;;16328:2:1;4152:63:0;;;16310:21:1;16367:2;16347:18;;;16340:30;16406:25;16386:18;;;16379:53;16449:18;;4152:63:0;16126:347:1;4152:63:0;4385:24;4412:827;4552:18;:16;:18::i;:::-;5006:13;;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4637:458;;4682:167;4637:458;;;16765:25:1;16867:18;;;16860:43;;;;16939:15;;;16919:18;;;16912:43;16971:18;;;16964:34;;;17014:19;;;17007:35;;;;17058:19;;;;17051:35;;;4637:458:0;;;;;;;;;;16737:19:1;;;4637:458:0;;;4597:525;;;;;;;;17367:66:1;4472:673:0;;;17355:79:1;17450:11;;;17443:27;;;;17486:12;;;17479:28;;;;17523:12;;4472:673:0;;;;;;;;;;;;;4440:724;;4472:673;4440:724;;;;4412:827;;;;;;;;;17773:25:1;17846:4;17834:17;;17814:18;;;17807:45;17868:18;;;17861:34;;;17911:18;;;17904:34;;;17745:19;;4412:827:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4412:827:0;;;;;;-1:-1:-1;;5264:30:0;;;;;;;:59;;;5318:5;5298:25;;:16;:25;;;5264:59;5256:86;;;;;;;18151:2:1;5256:86:0;;;18133:21:1;18190:2;18170:18;;;18163:30;18229:16;18209:18;;;18202:44;18263:18;;5256:86:0;17949:338:1;5256:86:0;5359:27;;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5432:31;571:25:1;;;5359:36:0;;5432:31;;;;;544:18:1;5432:31:0;;;;;;;3944:1527;;;;;;;:::o;54356:127::-;54425:7;54452:23;54468:6;54452:15;:23::i;78292:257::-;78476:65;;;;;78489:10;78476:65;;;18664:34:1;78509:4:0;18714:18:1;;;18707:43;18766:18;;;18759:34;;;18809:18;;;18802:34;;;18885:4;18873:17;;18852:19;;;18845:46;18907:19;;;18900:35;;;18951:19;;;18944:35;;;78476:12:0;;;;;;18575:19:1;;78476:65:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;78292:257;;;;;;:::o;171808:1147::-;171992:19;172041:1;172026:12;:16;172022:244;;;-1:-1:-1;172081:12:0;172022:244;;;172131:1;172116:12;:16;172112:154;;;-1:-1:-1;172171:12:0;172112:154;;;172224:30;;;;;;;;;;;;;;172112:154;172311:36;172350:73;;;;172375:4;172350:73;:::i;:::-;172311:112;;172462:12;172477:252;172518:16;172549:169;172598:12;:20;;;172646:12;:21;;;172687:12;:16;;;172549:22;:169::i;:::-;172477:26;:252::i;:::-;172462:267;-1:-1:-1;172744:10:0;:27;;;;172740:91;;172795:24;;;;;;;;;;;;;;172740:91;172889:20;;:58;;:33;;172923:10;172935:11;172889:33;:58::i;:::-;171945:1010;;;171808:1147;;;;:::o;6323:335::-;6409:6;6394:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;6566:13:0;;;;;;;:9;:13;;;;;;;;:23;;;;;;6618:32;571:25:1;;;6618:32:0;;544:18:1;6618:32:0;;;;;;;;6323:335;;:::o;145458:150::-;145594:6;145578:12;;:22;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;145458:150:0:o;43207:552::-;43420:9;;;43554:19;;43547:27;43579:9;;43593;;;43590:16;;43576:31;43543:65;43533:123;;43639:1;43636;43629:12;43533:123;43722:19;;43207:552;-1:-1:-1;;43207:552:0:o;43767:771::-;43978:9;;;44112:19;;44105:27;44137:9;;44151;;;44148:16;;44134:31;44101:65;44091:123;;44197:1;44194;44187:12;44091:123;44517:1;44503:11;44499:1;44496;44492:9;44488:27;44484:35;44479:1;44472:9;44465:17;44461:59;44456:64;;43767:771;;;;;:::o;5666:457::-;5731:7;5832:95;5966:4;5950:22;;;;;;:::i;:::-;;;;;;;;;;5799:301;;;21562:25:1;;;;21603:18;;21596:34;;;;5995:14:0;21646:18:1;;;21639:34;6032:13:0;21689:18:1;;;21682:34;6076:4:0;21732:19:1;;;21725:84;21534:19;;5799:301:0;;;;;;;;;;;;5771:344;;;;;;5751:364;;5666:457;:::o;173181:1220::-;173310:12;173324:21;173395:15;173413:218;173454:16;173485:135;173534:3;173565:4;173589:16;173485:22;:135::i;173413:218::-;173395:236;;173742:34;173779:67;173838:7;173779:58;:67::i;:::-;173742:104;;173891:23;173861:53;;:27;:53;;;173857:103;;;-1:-1:-1;173939:5:0;;;;-1:-1:-1;173181:1220:0;-1:-1:-1;;;173181:1220:0:o;173857:103::-;174016:24;174046:92;174082:7;174104:23;174046:21;:92::i;:::-;174015:123;;;174200:4;174190:14;;174231:162;174274:18;174315:11;174350:3;174377:4;174231:28;:162::i;:::-;174215:178;;173352:1049;;;173181:1220;;;:::o;72194:4221::-;72310:14;;;72886:6;72883:1;72880;72873:20;72927:1;72924;72920:9;72911:18;;72983:5;72979:2;72976:13;72968:5;72964:2;72960:14;72956:34;72947:43;;;73088:5;73097:1;73088:10;73084:209;;73141:1;73127:11;:15;73119:24;;;;;;-1:-1:-1;73204:23:0;;;;-1:-1:-1;73264:13:0;;73084:209;73436:5;73422:11;:19;73414:28;;;;;;73751:17;73837:11;73834:1;73831;73824:25;74240:12;74256:15;;;74255:31;;74395:22;;;;;75290:1;75271;:15;;75270:21;;75537:17;;;75533:21;;75526:28;75600:17;;;75596:21;;75589:28;75664:17;;;75660:21;;75653:28;75728:17;;;75724:21;;75717:28;75792:17;;;75788:21;;75781:28;75857:17;;;75853:21;;;75846:28;74818:12;;;;74814:23;;;74839:1;74810:31;73986:20;;;73975:32;;;74879:12;;;;74034:21;;;;74544:16;;;;74870:21;;;;76357:11;;;;;-1:-1:-1;;72194:4221:0;;;;;:::o;174435:1135::-;174542:21;174613:22;174667:246;174712:16;174747:151;174800:3;174835:4;174863:16;174747:22;:151::i;174667:246::-;174613:311;;174957:29;175014:153;;;;;;;;175073:3;175014:153;;;;;;175107:4;175014:153;;;;;;175135:16;175014:153;;;;;174989:189;;;;;;;22117:13:1;;22044:42;22113:22;;;22095:41;;22196:4;22184:17;;;22178:24;22174:33;;;22152:20;;;22145:63;22268:4;22256:17;;;22250:24;22276:8;22246:39;22224:20;;;22217:69;;;;22022:2;22007:18;;21820:472;174989:189:0;;;;;;;;;;;;;174957:221;;175189:15;175230:4;175207:28;;175215:3;175207:28;;;175189:46;;175247:14;175263;175281:7;:12;;;175316:4;175336:10;175368:11;175395:10;:63;;169934:49;175395:63;;;169671:10;175395:63;175473:16;175281:219;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;175246:254;;;;175530:10;:30;;175553:7;175530:30;;;175543:7;175530:30;175528:33;;;:::i;:::-;175513:49;174435:1135;-1:-1:-1;;;;;;;174435:1135:0:o;6666:338::-;6739:15;;;;;;;:9;:15;;;;;:25;;6758:6;;6739:15;:25;;6758:6;;6739:25;:::i;:::-;;;;-1:-1:-1;;6912:11:0;:21;;;;;;;6962:34;;571:25:1;;;-1:-1:-1;;6962:34:0;;;;;;559:2:1;544:18;6962:34:0;425:177: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;;;;;;;23540:2:1;11316:35:0;;;23522:21:1;23579:2;23559:18;;;23552:30;23618:17;23598:18;;;23591:45;23653:18;;11316:35:0;23338:339:1;11316:35:0;9980:1379;9874:1485;;;:::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;;;;;;;23884:2:1;9818:40:0;;;23866:21:1;23923:2;23903:18;;;23896:30;23962:22;23942:18;;;23935:50;24002:18;;9818:40:0;23682:344:1;9818:40:0;8395:1471;8262:1604;;;;:::o;175604:494::-;175712:21;175751:15;175792:4;175769:28;;175777:3;175769:28;;;175751:46;;175828:15;:37;;;175892:3;175923:4;175947:16;175982:11;176012:10;:63;;169934:49;176012:63;;;169671:10;176012:63;175828:262;;;;;;;;;;24298:42:1;24367:15;;;175828:262:0;;;24349:34:1;24419:15;;;24399:18;;;24392:43;24483:8;24471:21;;;24451:18;;;24444:49;24509:18;;;24502:34;24573:15;;;;24552:19;;;24545:44;24260:19;;175828:262:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;147782:281::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;147943:6:0;147934:15;;:6;:15;;;147930:56;;;147971:6;;147979;147930:56;-1:-1:-1;148004:51:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;147782:281::o;148316:446::-;148427:12;148478:3;:10;;;148465:23;;:3;:10;;;:23;;;148457:32;;;;;;148639:10;;148651;;;;;148663:7;;;;;148628:43;;24810:42:1;24879:15;;;148628:43:0;;;24861:34:1;24931:15;;;;24911:18;;;24904:43;24995:8;24983:21;;;24963:18;;;;24956:49;;;;148628:43:0;;;;;;;;;24773:18:1;;;148628:43:0;;;148618:54;;;;;;;25314:66:1;148531:194:0;;;25302:79:1;25414:15;;;;25431:66;25410:88;25397:11;;;25390:109;25515:12;;;25508:28;147225:66:0;25552:12:1;;;25545:28;148507:247:0;;25589:12:1;;148531:194:0;;;;;;;;;;;;148507:229;;;;;;59242:10;59123:140;162218:927;162322:17;162402:23;162440:29;162531:4;162516:26;;;:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;162357:187;;;;;;;;;162588:1;162563:22;:26;;;162555:41;;;;;;;27031:2:1;162555:41:0;;;27013:21:1;27070:1;27050:18;;;27043:29;27108:4;27088:18;;;27081:32;27130:18;;162555:41:0;26829:325:1;162555:41:0;162610:27;;162663:57;;;;162746:22;162722:20;:16;162741:1;162722:20;:::i;:::-;162721:47;;;;:::i;:::-;162663:106;;;;;;;;;;27936:6:1;27924:19;;;162663:106:0;;;27906:38:1;27879:18;;162663:106:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;162609:160;;;;;;162959:11;162954:112;;163018:36;;;;;163052:1;163018:36;;;571:25:1;163018:33:0;;;;;;544:18:1;;163018:36:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;162987:67:0;;-1:-1:-1;;;162954:112:0;163091:46;163117:20;163098:15;163091:46;:::i;:::-;163078:59;162218:927;-1:-1:-1;;;;;;162218:927:0:o;158957:1427::-;159057:24;159083:29;159138:10;:15;;159152:1;159138:15;159130:30;;;;;;;29322:2:1;159130:30:0;;;29304:21:1;29361:1;29341:18;;;29334:29;29399:4;29379:18;;;29372:32;29421:18;;159130:30:0;29120:325:1;159130:30:0;159203:15;;;159216:1;159203:15;;;;;;;;159173:27;;159203:15;;;;;;;;;;-1:-1:-1;159203:15:0;159173:45;;159246:10;159229:11;159241:1;159229:14;;;;;;;;:::i;:::-;;;;;;:27;;;;;;;;;;;159284:1;159267:11;159279:1;159267:14;;;;;;;;:::i;:::-;;;;;;:18;;;;;;;;;;;159313:30;159358:51;159438:4;159423:28;;;159452:11;159423:41;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;159298:166;;;;159477:26;159527:15;159543:1;159527:18;;;;;;;;:::i;:::-;;;;;;;159506:15;159522:1;159506:18;;;;;;;;:::i;:::-;;;;;;;:39;;;;:::i;:::-;159477:68;;159556:43;159674:34;159709:1;159674:37;;;;;;;;:::i;:::-;;;;;;;159602:34;159655:1;159602:69;;;;;;;;:::i;:::-;;;;;;;:109;;;;:::i;:::-;159556:155;-1:-1:-1;159765:48:0;159794:18;;;159765:20;:48;:::i;:::-;159724:100;;159922:1;159899:20;:24;;;:96;;;;-1:-1:-1;159941:48:0;159970:18;;;159941:20;:48;:::i;:::-;:53;;;;159899:96;159881:146;;;160007:20;;;;:::i;:::-;;;;159881:146;160158:22;160183:39;160205:17;160183:19;;;:39;:::i;:::-;160158:64;-1:-1:-1;160279:86:0;160314:50;160362:2;160314:50;;;;160158:64;160279:86;:::i;:::-;160233:143;;159119:1265;;;;;;158957:1427;;;;;:::o;160896:1041::-;161057:19;161089:20;161112:33;161140:4;161112:27;:33::i;:::-;161089:56;-1:-1:-1;161283:17:0;161267:33;;;;161263:667;;161317:17;161337:36;;;;;;:::i;:::-;161317:56;;161414:10;161402:22;;:9;:22;;;:158;;161512:48;161528:8;161538:10;161512:48;;161550:9;161512:15;:48::i;:::-;161402:158;;;161444:48;161460:9;161471:10;161444:48;;161483:8;161444:15;:48::i;:::-;161388:172;;161302:270;161263:667;;;161593:17;161613:118;;;;;161709:7;161613:15;:118::i;:::-;161593:138;;161772:10;161760:22;;:9;:22;;;:158;;161870:48;161886:8;161896:10;161870:48;;161908:9;161870:15;:48::i;:::-;161760:158;;;161802:48;161818:9;161829:10;161802:48;;161841:8;161802:15;:48::i;:::-;161746:172;;161578:352;161263:667;161078:859;160896:1041;;;;;;:::o;150106:2794::-;150169:20;150227:15;150252:1;150245:4;:8;;;:57;;150296:4;150289:12;;150245:57;;;150272:4;150265:12;;150264:13;;150245:57;150227:75;-1:-1:-1;149376:9:0;150321:35;;150317:51;;;150365:3;;;;;;;;;;;;;;150317:51;150385:13;150401:7;150411:3;150401:13;150418:1;150401:18;:127;;150493:35;150401:127;;;150439:34;150401:127;150385:143;;;-1:-1:-1;150557:3:0;150547:13;;:18;150543:83;;150584:34;150576:42;150623:3;150575:51;150543:83;150655:3;150645:13;;:18;150641:83;;150682:34;150674:42;150721:3;150673:51;150641:83;150753:3;150743:13;;:18;150739:83;;150780:34;150772:42;150819:3;150771:51;150739:83;150851:4;150841:14;;:19;150837:84;;150879:34;150871:42;150918:3;150870:51;150837:84;150950:4;150940:14;;:19;150936:84;;150978:34;150970:42;151017:3;150969:51;150936:84;151049:4;151039:14;;:19;151035:84;;151077:34;151069:42;151116:3;151068:51;151035:84;151148:4;151138:14;;:19;151134:84;;151176:34;151168:42;151215:3;151167:51;151134:84;151247:5;151237:15;;:20;151233:85;;151276:34;151268:42;151315:3;151267:51;151233:85;151347:5;151337:15;;:20;151333:85;;151376:34;151368:42;151415:3;151367:51;151333:85;151447:5;151437:15;;:20;151433:85;;151476:34;151468:42;151515:3;151467:51;151433:85;151547:5;151537:15;;:20;151533:85;;151576:34;151568:42;151615:3;151567:51;151533:85;151647:6;151637:16;;:21;151633:86;;151677:34;151669:42;151716:3;151668:51;151633:86;151748:6;151738:16;;:21;151734:86;;151778:34;151770:42;151817:3;151769:51;151734:86;151849:6;151839:16;;:21;151835:86;;151879:34;151871:42;151918:3;151870:51;151835:86;151950:6;151940:16;;:21;151936:86;;151980:34;151972:42;152019:3;151971:51;151936:86;152051:7;152041:17;;:22;152037:86;;152082:33;152074:41;152120:3;152073:50;152037:86;152152:7;152142:17;;:22;152138:85;;152183:32;152175:40;152220:3;152174:49;152138:85;152252:7;152242:17;;:22;152238:83;;152283:30;152275:38;152318:3;152274:47;152238:83;152350:7;152340:17;;:22;152336:78;;152381:25;152373:33;152411:3;152372:42;152336:78;152442:1;152435:4;:8;;;152431:47;;;152473:5;152453:17;:25;;;;;:::i;:::-;;152445:33;;152431:47;152858:7;152849:5;:17;:22;:30;;152878:1;152849:30;;;152874:1;152849:30;152832:48;;152842:2;152833:5;:11;;152832:48;152809:72;;150202:2691;;150106:2794;;;:::o;14:154:1:-;100:42;93:5;89:54;82:5;79:65;69:93;;158:1;155;148:12;69:93;14:154;:::o;173:247::-;232:6;285:2;273:9;264:7;260:23;256:32;253:52;;;301:1;298;291:12;253:52;340:9;327:23;359:31;384:5;359:31;:::i;607:258::-;679:1;689:113;703:6;700:1;697:13;689:113;;;779:11;;;773:18;760:11;;;753:39;725:2;718:10;689:113;;;820:6;817:1;814:13;811:48;;;-1:-1:-1;;855:1:1;837:16;;830:27;607:258::o;870:317::-;912:3;950:5;944:12;977:6;972:3;965:19;993:63;1049:6;1042:4;1037:3;1033:14;1026:4;1019:5;1015:16;993:63;:::i;:::-;1101:2;1089:15;1106:66;1085:88;1076:98;;;;1176:4;1072:109;;870:317;-1:-1:-1;;870:317:1:o;1192:220::-;1341:2;1330:9;1323:21;1304:4;1361:45;1402:2;1391:9;1387:18;1379:6;1361:45;:::i;1417:180::-;1476:6;1529:2;1517:9;1508:7;1504:23;1500:32;1497:52;;;1545:1;1542;1535:12;1497:52;-1:-1:-1;1568:23:1;;1417:180;-1:-1:-1;1417:180:1:o;1602:315::-;1670:6;1678;1731:2;1719:9;1710:7;1706:23;1702:32;1699:52;;;1747:1;1744;1737:12;1699:52;1786:9;1773:23;1805:31;1830:5;1805:31;:::i;:::-;1855:5;1907:2;1892:18;;;;1879:32;;-1:-1:-1;;;1602:315:1:o;2114:456::-;2191:6;2199;2207;2260:2;2248:9;2239:7;2235:23;2231:32;2228:52;;;2276:1;2273;2266:12;2228:52;2315:9;2302:23;2334:31;2359:5;2334:31;:::i;:::-;2384:5;-1:-1:-1;2441:2:1;2426:18;;2413:32;2454:33;2413:32;2454:33;:::i;:::-;2114:456;;2506:7;;-1:-1:-1;;;2560:2:1;2545:18;;;;2532:32;;2114:456::o;4383:315::-;4451:6;4459;4512:2;4500:9;4491:7;4487:23;4483:32;4480:52;;;4528:1;4525;4518:12;4480:52;4564:9;4551:23;4541:33;;4624:2;4613:9;4609:18;4596:32;4637:31;4662:5;4637:31;:::i;:::-;4687:5;4677:15;;;4383:315;;;;;:::o;5205:625::-;5447:3;5432:19;;5481:1;5470:13;;5460:201;;5517:77;5514:1;5507:88;5618:4;5615:1;5608:15;5646:4;5643:1;5636:15;5460:201;5670:25;;;5726:2;5711:18;;5704:34;;;;5769:2;5754:18;;5747:34;;;;5812:2;5797:18;;;5790:34;5205:625;:::o;5835:626::-;5932:6;5940;5993:2;5981:9;5972:7;5968:23;5964:32;5961:52;;;6009:1;6006;5999:12;5961:52;6049:9;6036:23;6078:18;6119:2;6111:6;6108:14;6105:34;;;6135:1;6132;6125:12;6105:34;6173:6;6162:9;6158:22;6148:32;;6218:7;6211:4;6207:2;6203:13;6199:27;6189:55;;6240:1;6237;6230:12;6189:55;6280:2;6267:16;6306:2;6298:6;6295:14;6292:34;;;6322:1;6319;6312:12;6292:34;6375:7;6370:2;6360:6;6357:1;6353:14;6349:2;6345:23;6341:32;6338:45;6335:65;;;6396:1;6393;6386:12;6335:65;6427:2;6419:11;;;;;6449:6;;-1:-1:-1;5835:626:1;;-1:-1:-1;;;;5835:626:1:o;6466:860::-;6626:4;6655:2;6695;6684:9;6680:18;6725:2;6714:9;6707:21;6748:6;6783;6777:13;6814:6;6806;6799:22;6852:2;6841:9;6837:18;6830:25;;6914:2;6904:6;6901:1;6897:14;6886:9;6882:30;6878:39;6864:53;;6952:2;6944:6;6940:15;6973:1;6983:314;6997:6;6994:1;6991:13;6983:314;;;7086:66;7074:9;7066:6;7062:22;7058:95;7053:3;7046:108;7177:40;7210:6;7201;7195:13;7177:40;:::i;:::-;7167:50;-1:-1:-1;7275:12:1;;;;7240:15;;;;7019:1;7012:9;6983:314;;;-1:-1:-1;7314:6:1;;6466:860;-1:-1:-1;;;;;;;6466:860:1:o;7331:456::-;7408:6;7416;7424;7477:2;7465:9;7456:7;7452:23;7448:32;7445:52;;;7493:1;7490;7483:12;7445:52;7529:9;7516:23;7506:33;;7589:2;7578:9;7574:18;7561:32;7602:31;7627:5;7602:31;:::i;:::-;7652:5;-1:-1:-1;7709:2:1;7694:18;;7681:32;7722:33;7681:32;7722:33;:::i;:::-;7774:7;7764:17;;;7331:456;;;;;:::o;7792:114::-;7876:4;7869:5;7865:16;7858:5;7855:27;7845:55;;7896:1;7893;7886:12;7911:672;8026:6;8034;8042;8050;8058;8066;8119:3;8107:9;8098:7;8094:23;8090:33;8087:53;;;8136:1;8133;8126:12;8087:53;8175:9;8162:23;8194:31;8219:5;8194:31;:::i;:::-;8244:5;-1:-1:-1;8296:2:1;8281:18;;8268:32;;-1:-1:-1;8347:2:1;8332:18;;8319:32;;-1:-1:-1;8403:2:1;8388:18;;8375:32;8416:31;8375:32;8416:31;:::i;:::-;7911:672;;;;-1:-1:-1;7911:672:1;;8520:3;8505:19;;8492:33;;8572:3;8557:19;;;8544:33;;-1:-1:-1;7911:672:1;-1:-1:-1;;7911:672:1:o;8588:801::-;8699:6;8707;8715;8723;8731;8739;8747;8800:3;8788:9;8779:7;8775:23;8771:33;8768:53;;;8817:1;8814;8807:12;8768:53;8856:9;8843:23;8875:31;8900:5;8875:31;:::i;:::-;8925:5;-1:-1:-1;8982:2:1;8967:18;;8954:32;8995:33;8954:32;8995:33;:::i;:::-;9047:7;-1:-1:-1;9101:2:1;9086:18;;9073:32;;-1:-1:-1;9152:2:1;9137:18;;9124:32;;-1:-1:-1;9208:3:1;9193:19;;9180:33;9222:31;9180:33;9222:31;:::i;:::-;8588:801;;;;-1:-1:-1;8588:801:1;;;;9272:7;9326:3;9311:19;;9298:33;;-1:-1:-1;9378:3:1;9363:19;;;9350:33;;8588:801;-1:-1:-1;;8588:801:1:o;9394:388::-;9462:6;9470;9523:2;9511:9;9502:7;9498:23;9494:32;9491:52;;;9539:1;9536;9529:12;9491:52;9578:9;9565:23;9597:31;9622:5;9597:31;:::i;:::-;9647:5;-1:-1:-1;9704:2:1;9689:18;;9676:32;9717:33;9676:32;9717:33;:::i;9787:725::-;9873:6;9881;9889;9897;9950:2;9938:9;9929:7;9925:23;9921:32;9918:52;;;9966:1;9963;9956:12;9918:52;10002:9;9989:23;9979:33;;10059:2;10048:9;10044:18;10031:32;10021:42;;10114:2;10103:9;10099:18;10086:32;10137:18;10178:2;10170:6;10167:14;10164:34;;;10194:1;10191;10184:12;10164:34;10232:6;10221:9;10217:22;10207:32;;10277:7;10270:4;10266:2;10262:13;10258:27;10248:55;;10299:1;10296;10289:12;10248:55;10339:2;10326:16;10365:2;10357:6;10354:14;10351:34;;;10381:1;10378;10371:12;10351:34;10426:7;10421:2;10412:6;10408:2;10404:15;10400:24;10397:37;10394:57;;;10447:1;10444;10437:12;10394:57;9787:725;;;;-1:-1:-1;;10478:2:1;10470:11;;-1:-1:-1;;;9787:725:1:o;10517:184::-;10587:6;10640:2;10628:9;10619:7;10615:23;10611:32;10608:52;;;10656:1;10653;10646:12;10608:52;-1:-1:-1;10679:16:1;;10517:184;-1:-1:-1;10517:184:1:o;10706:::-;10758:77;10755:1;10748:88;10855:4;10852:1;10845:15;10879:4;10876:1;10869:15;10895:125;10935:4;10963:1;10960;10957:8;10954:34;;;10968:18;;:::i;:::-;-1:-1:-1;11005:9:1;;10895:125::o;11618:437::-;11697:1;11693:12;;;;11740;;;11761:61;;11815:4;11807:6;11803:17;11793:27;;11761:61;11868:2;11860:6;11857:14;11837:18;11834:38;11831:218;;11905:77;11902:1;11895:88;12006:4;12003:1;11996:15;12034:4;12031:1;12024:15;13238:184;13290:77;13287:1;13280:88;13387:4;13384:1;13377:15;13411:4;13408:1;13401:15;13427:184;13479:77;13476:1;13469:88;13576:4;13573:1;13566:15;13600:4;13597:1;13590:15;13616:580;13693:4;13699:6;13759:11;13746:25;13849:66;13838:8;13822:14;13818:29;13814:102;13794:18;13790:127;13780:155;;13931:1;13928;13921:12;13780:155;13958:33;;14010:20;;;-1:-1:-1;14053:18:1;14042:30;;14039:50;;;14085:1;14082;14075:12;14039:50;14118:4;14106:17;;-1:-1:-1;14149:14:1;14145:27;;;14135:38;;14132:58;;;14186:1;14183;14176:12;14132:58;13616:580;;;;;:::o;14201:271::-;14384:6;14376;14371:3;14358:33;14340:3;14410:16;;14435:13;;;14410:16;14201:271;-1:-1:-1;14201:271:1:o;14477:334::-;14548:2;14542:9;14604:2;14594:13;;14609:66;14590:86;14578:99;;14707:18;14692:34;;14728:22;;;14689:62;14686:88;;;14754:18;;:::i;:::-;14790:2;14783:22;14477:334;;-1:-1:-1;14477:334:1:o;14816:765::-;14896:6;14949:2;14937:9;14928:7;14924:23;14920:32;14917:52;;;14965:1;14962;14955:12;14917:52;14998:9;14992:16;15027:18;15068:2;15060:6;15057:14;15054:34;;;15084:1;15081;15074:12;15054:34;15122:6;15111:9;15107:22;15097:32;;15167:7;15160:4;15156:2;15152:13;15148:27;15138:55;;15189:1;15186;15179:12;15138:55;15218:2;15212:9;15240:2;15236;15233:10;15230:36;;;15246:18;;:::i;:::-;15288:112;15396:2;15327:66;15320:4;15316:2;15312:13;15308:86;15304:95;15288:112;:::i;:::-;15275:125;;15423:2;15416:5;15409:17;15463:7;15458:2;15453;15449;15445:11;15441:20;15438:33;15435:53;;;15484:1;15481;15474:12;15435:53;15497:54;15548:2;15543;15536:5;15532:14;15527:2;15523;15519:11;15497:54;:::i;15586:195::-;15625:3;15656:66;15649:5;15646:77;15643:103;;15726:18;;:::i;:::-;-1:-1:-1;15773:1:1;15762:13;;15586:195::o;18990:822::-;19083:6;19136:2;19124:9;19115:7;19111:23;19107:32;19104:52;;;19152:1;19149;19142:12;19104:52;19185:2;19179:9;19227:2;19219:6;19215:15;19296:6;19284:10;19281:22;19260:18;19248:10;19245:34;19242:62;19239:88;;;19307:18;;:::i;:::-;19343:2;19336:22;19380:23;;19412:31;19380:23;19412:31;:::i;:::-;19452:21;;19525:2;19510:18;;19497:32;19538:33;19497:32;19538:33;:::i;:::-;19599:2;19587:15;;19580:32;19664:2;19649:18;;19636:32;19712:8;19699:22;;19687:35;;19677:63;;19736:1;19733;19726:12;19677:63;19768:2;19756:15;;19749:32;19760:6;18990:822;-1:-1:-1;;;18990:822:1:o;19817:128::-;19857:3;19888:1;19884:6;19881:1;19878:13;19875:39;;;19894:18;;:::i;:::-;-1:-1:-1;19930:9:1;;19817:128::o;20079:1219::-;20209:3;20238:1;20271:6;20265:13;20301:3;20323:1;20351:9;20347:2;20343:18;20333:28;;20411:2;20400:9;20396:18;20433;20423:61;;20477:4;20469:6;20465:17;20455:27;;20423:61;20503:2;20551;20543:6;20540:14;20520:18;20517:38;20514:222;;20590:77;20585:3;20578:90;20691:4;20688:1;20681:15;20721:4;20716:3;20709:17;20514:222;20752:18;20779:162;;;;20955:1;20950:323;;;;20745:528;;20779:162;20827:66;20816:9;20812:82;20807:3;20800:95;20924:6;20919:3;20915:16;20908:23;;20779:162;;20950:323;20026:1;20019:14;;;20063:4;20050:18;;21048:1;21062:165;21076:6;21073:1;21070:13;21062:165;;;21154:14;;21141:11;;;21134:35;21197:16;;;;21091:10;;21062:165;;;21066:3;;21256:6;21251:3;21247:16;21240:23;;20745:528;-1:-1:-1;21289:3:1;;20079:1219;-1:-1:-1;;;;;;;;20079:1219:1:o;22297:592::-;22511:4;22540:42;22621:2;22613:6;22609:15;22598:9;22591:34;22675:6;22668:14;22661:22;22656:2;22645:9;22641:18;22634:50;22720:6;22715:2;22704:9;22700:18;22693:34;22775:2;22767:6;22763:15;22758:2;22747:9;22743:18;22736:43;;22816:3;22810;22799:9;22795:19;22788:32;22837:46;22878:3;22867:9;22863:19;22855:6;22837:46;:::i;22894:243::-;22971:6;22979;23032:2;23020:9;23011:7;23007:23;23003:32;23000:52;;;23048:1;23045;23038:12;23000:52;-1:-1:-1;;23071:16:1;;23127:2;23112:18;;;23106:25;23071:16;;23106:25;;-1:-1:-1;22894:243:1:o;23142:191::-;23177:3;23208:66;23201:5;23198:77;23195:103;;23278:18;;:::i;:::-;-1:-1:-1;23318:1:1;23314:13;;23142:191::o;25612:163::-;25690:13;;25743:6;25732:18;;25722:29;;25712:57;;25765:1;25762;25755:12;25712:57;25612:163;;;:::o;25780:164::-;25856:13;;25905;;25898:21;25888:32;;25878:60;;25934:1;25931;25924:12;25949:875;26063:6;26071;26079;26087;26095;26103;26111;26164:3;26152:9;26143:7;26139:23;26135:33;26132:53;;;26181:1;26178;26171:12;26132:53;26213:9;26207:16;26232:31;26257:5;26232:31;:::i;:::-;26282:5;26272:15;;;26332:2;26321:9;26317:18;26311:25;26381:7;26378:1;26367:22;26358:7;26355:35;26345:63;;26404:1;26401;26394:12;26345:63;26427:7;-1:-1:-1;26453:48:1;26497:2;26482:18;;26453:48;:::i;:::-;26443:58;;26520:48;26564:2;26553:9;26549:18;26520:48;:::i;:::-;26510:58;;26587:49;26631:3;26620:9;26616:19;26587:49;:::i;:::-;26577:59;;26681:3;26670:9;26666:19;26660:26;26695:31;26718:7;26695:31;:::i;:::-;26745:7;-1:-1:-1;26771:47:1;26813:3;26798:19;;26771:47;:::i;:::-;26761:57;;25949:875;;;;;;;;;;:::o;27159:224::-;27198:3;27226:6;27259:2;27256:1;27252:10;27289:2;27286:1;27282:10;27320:3;27316:2;27312:12;27307:3;27304:21;27301:47;;;27328:18;;:::i;:::-;27364:13;;27159:224;-1:-1:-1;;;;27159:224:1:o;27388:184::-;27440:77;27437:1;27430:88;27537:4;27534:1;27527:15;27561:4;27558:1;27551:15;27577:179;27608:1;27634:6;27667:2;27664:1;27660:10;27689:3;27679:37;;27696:18;;:::i;:::-;27734:10;;27730:20;;;;;27577:179;-1:-1:-1;;27577:179:1:o;27955:164::-;28032:13;;28085:1;28074:20;;;28064:31;;28054:59;;28109:1;28106;28099:12;28124:575;28215:6;28223;28231;28239;28292:3;28280:9;28271:7;28267:23;28263:33;28260:53;;;28309:1;28306;28299:12;28260:53;28341:9;28335:16;28391:10;28384:5;28380:22;28373:5;28370:33;28360:61;;28417:1;28414;28407:12;28360:61;28440:5;-1:-1:-1;28464:47:1;28507:2;28492:18;;28464:47;:::i;:::-;28454:57;;28556:2;28545:9;28541:18;28535:25;28569:33;28594:7;28569:33;:::i;:::-;28621:7;-1:-1:-1;28647:46:1;28689:2;28674:18;;28647:46;:::i;:::-;28637:56;;28124:575;;;;;;;:::o;28894:221::-;28933:4;28962:10;29022;;;;28992;;29044:12;;;29041:38;;;29059:18;;:::i;:::-;29096:13;;28894:221;-1:-1:-1;;;28894:221:1:o;29450:647::-;29619:2;29671:21;;;29741:13;;29644:18;;;29763:22;;;29590:4;;29619:2;29842:15;;;;29816:2;29801:18;;;29590:4;29885:186;29899:6;29896:1;29893:13;29885:186;;;29964:13;;29979:10;29960:30;29948:43;;30046:15;;;;30011:12;;;;29921:1;29914:9;29885:186;;;-1:-1:-1;30088:3:1;;29450:647;-1:-1:-1;;;;;;29450:647:1:o;30102:181::-;30160:4;30193:18;30185:6;30182:30;30179:56;;;30215:18;;:::i;:::-;-1:-1:-1;30260:1:1;30256:14;30272:4;30252:25;;30102:181::o;30288:732::-;30353:5;30406:3;30399:4;30391:6;30387:17;30383:27;30373:55;;30424:1;30421;30414:12;30373:55;30453:6;30447:13;30479:4;30503:58;30519:41;30557:2;30519:41;:::i;:::-;30503:58;:::i;:::-;30595:15;;;30681:1;30677:10;;;;30665:23;;30661:32;;;30626:12;;;;30705:15;;;30702:35;;;30733:1;30730;30723:12;30702:35;30769:2;30761:6;30757:15;30781:210;30797:6;30792:3;30789:15;30781:210;;;30870:3;30864:10;30887:31;30912:5;30887:31;:::i;:::-;30931:18;;30969:12;;;;30814;;30781:210;;;-1:-1:-1;31009:5:1;30288:732;-1:-1:-1;;;;;;30288:732:1:o;31025:1152::-;31152:6;31160;31213:2;31201:9;31192:7;31188:23;31184:32;31181:52;;;31229:1;31226;31219:12;31181:52;31262:9;31256:16;31291:18;31332:2;31324:6;31321:14;31318:34;;;31348:1;31345;31338:12;31318:34;31386:6;31375:9;31371:22;31361:32;;31431:7;31424:4;31420:2;31416:13;31412:27;31402:55;;31453:1;31450;31443:12;31402:55;31482:2;31476:9;31504:4;31528:58;31544:41;31582:2;31544:41;:::i;31528:58::-;31620:15;;;31702:1;31698:10;;;;31690:19;;31686:28;;;31651:12;;;;31726:19;;;31723:39;;;31758:1;31755;31748:12;31723:39;31782:11;;;;31802:157;31818:6;31813:3;31810:15;31802:157;;;31884:32;31912:3;31884:32;:::i;:::-;31872:45;;31835:12;;;;31937;;;;31802:157;;;32014:18;;;32008:25;31978:5;;-1:-1:-1;32008:25:1;;-1:-1:-1;;;32045:16:1;;;32042:36;;;32074:1;32071;32064:12;32042:36;;32097:74;32163:7;32152:8;32141:9;32137:24;32097:74;:::i;:::-;32087:84;;;31025:1152;;;;;:::o;32182:404::-;32220:4;32264:1;32261;32250:16;32300:1;32297;32286:16;32330:1;32325:3;32321:11;32441:3;32373:66;32369:76;32364:3;32360:86;32355:2;32348:10;32344:103;32341:129;;;32450:18;;:::i;:::-;32521:3;32503:16;32499:26;32494:3;32490:36;32486:2;32482:45;32479:71;;;32530:18;;:::i;:::-;-1:-1:-1;32567:13:1;;;32182:404;-1:-1:-1;;;32182:404:1:o;32591:254::-;32631:4;32660:42;32752:10;;;;32722;;32774:12;;;32771:38;;;32789:18;;:::i;32850:389::-;32888:1;32929;32926;32915:16;32965:1;32962;32951:16;32986:3;32976:37;;32993:18;;:::i;:::-;33114:66;33109:3;33106:75;33037:66;33032:3;33029:75;33025:157;33022:183;;;33185:18;;:::i;:::-;33219:14;;;32850:389;-1:-1:-1;;;32850:389:1:o;33244:166::-;33274:1;33315;33312;33301:16;33336:3;33326:37;;33343:18;;:::i;:::-;33400:3;33396:1;33393;33382:16;33377:27;33372:32;;;33244:166;;;;:::o;33415:306::-;33452:3;33499:5;33496:1;33485:20;33529:66;33520:7;33517:79;33514:105;;33599:18;;:::i;:::-;33648:66;33635:80;;33415:306;-1:-1:-1;;33415:306:1:o;33726:303::-;33766:7;33798:50;33875:2;33872:1;33868:10;33905:2;33902:1;33898:10;33961:3;33957:2;33953:12;33948:3;33945:21;33938:3;33931:11;33924:19;33920:47;33917:73;;;33970:18;;:::i;:::-;34010:13;;33726:303;-1:-1:-1;;;;33726:303:1:o;34034:232::-;34074:1;34100:50;34177:2;34174:1;34170:10;34199:3;34189:37;;34206:18;;:::i;:::-;34244:10;;34240:20;;;;;34034:232;-1:-1:-1;;34034:232:1:o;34271:228::-;34311:7;34437:1;34369:66;34365:74;34362:1;34359:81;34354:1;34347:9;34340:17;34336:105;34333:131;;;34444:18;;:::i;:::-;-1:-1:-1;34484:9:1;;34271:228::o

Swarm Source

ipfs://832f17e38b50631d77ff02822d449a12a858945c42ad88e3ce74476ca3f6feda
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.