Contract 0x6b2c4Bbc7cd0938a3ee4361AD713155c663aACc2 1

Premia 
 

Contract Overview

Premia: Pool Write Implementation
Balance:
0 ETH

ETH Value:
$0.00
Txn Hash Method
Block
From
To
Value [Txn Fee]
0x70d253c6af72fec91975c39fbb9d7819adaad16f70958f8ef5fd3657e78203e60x6102006033047282021-11-24 18:27:47855 days 14 hrs ago0xc7f8d87734ab2cbf70030ac8aa82abfe3e8126cb IN  Create: PoolWrite0 ETH0.182132961764 ETH1.107887576
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0xbda4281a7693cf387e8da59dbb92a3499203f0b15db2f7bb0804d7fd4ba4b20835038832021-12-02 3:54:25848 days 4 hrs ago 0x5c9e30def85334e587cf36eb07bdd6a72bf1452d Premia: Pool Write Implementation0 ETH
0xbcd60cb43731329fce67540fc76ea2c9cdb3fb674f176e1cb8d9850bbedc87d235037472021-12-02 3:48:19848 days 4 hrs ago 0x5c9e30def85334e587cf36eb07bdd6a72bf1452d Premia: Pool Write Implementation0 ETH
0x73c97ac353d9d001e369a4b206819b1d2e314dc2d20a35d36b2ea67eb98b927a35037062021-12-02 3:44:43848 days 4 hrs ago 0x5c9e30def85334e587cf36eb07bdd6a72bf1452d Premia: Pool Write Implementation0 ETH
0x58998e740a759c828a68f06766f54aeff6bd400285fed66086cc30066c11cad435035682021-12-02 3:37:17848 days 5 hrs ago 0x5c9e30def85334e587cf36eb07bdd6a72bf1452d Premia: Pool Write Implementation0.7572155375399999 ETH
0x5a53be7fa160b25f598c1f04f941056d69ee398a48c0e25846543d9fe751837235023652021-12-02 2:48:18848 days 5 hrs ago 0x167539702b5501aadd9b0b85e53532fd57cc71a9 Premia: Pool Write Implementation0.833568149596 ETH
0x446010cf0cc100dcc0452c591ed9ba8a6239fde257baed8c3350f2fd3312a64435010022021-12-02 1:04:22848 days 7 hrs ago 0x468d1ef7a1204d592537dab35794aac640c97505 Premia: Pool Write Implementation0.084198104398 ETH
0x6a7e689eafabbc8b586e8e678a1ded039a43442f52396a5cd43f17e85605a8f035005992021-12-02 0:34:39848 days 8 hrs ago 0x9e90d6fe95ee0bb754261ee3fc3d8a9c11e97a8e Premia: Pool Write Implementation0 ETH
0x0f4aee2942cc8a25c4a48bce9a793de535f2f40066312a41f7204266659a724135005932021-12-02 0:34:39848 days 8 hrs ago 0x9e90d6fe95ee0bb754261ee3fc3d8a9c11e97a8e Premia: Pool Write Implementation0 ETH
0xb8fbc85e8493c7e920c7ba3a487860c999251cecc3adc139acd1893f35b05cae35005382021-12-02 0:29:16848 days 8 hrs ago 0x9e90d6fe95ee0bb754261ee3fc3d8a9c11e97a8e Premia: Pool Write Implementation0 ETH
0x675857adf9189cf6b8c53c1e5e946b331ab63df2320399b70aeadb576ec98e2534999462021-12-01 23:48:21848 days 8 hrs ago 0x6e29e81925e8b7e4ebead1a5f5e176cd55d26edb Premia: Pool Write Implementation1.831301066023 ETH
0x14ae70912217bf4c6df9beefcb0b5b2263a81a26774075ba4ebdf511ded1501634998072021-12-01 23:38:56848 days 9 hrs ago 0xa31c143f2de92a43e5b663a43045c8f2b77f566f Premia: Pool Write Implementation0 ETH
0x6a3fd5deb1f67f50575ffc94babab8bb20c674b6eb5df2415235b7611c8183fc34985982021-12-01 22:21:31848 days 10 hrs ago 0xea9d77121a5fe0d58e1a0734dc4349f21e64b9dc Premia: Pool Write Implementation0.008841189832999998 ETH
0x09f4dc8bbf19070bd63dd48bdb2cac2ce27fcd73bc8e86056aa9e5d587be767034982582021-12-01 22:03:45848 days 10 hrs ago 0x22bffefb6da1f90c7f8200381efb47f894d46c4c Premia: Pool Write Implementation0 ETH
0x0f00ad432628d90132b3e4ca531ffe7212ffd90ea6115d0f69e0f4983b87241a34979272021-12-01 21:47:09848 days 10 hrs ago 0xef4f1790727e07ae98ff9c6e4fcf5b9b50211369 Premia: Pool Write Implementation0.113153056795 ETH
0x239a0d86b3d83f4f666137358288dfb9e6cea6b0fdb160906975699096b0aaf934961262021-12-01 20:12:28848 days 12 hrs ago 0x5789527ec57ff76286e42673e7b97e692b13f6d0 Premia: Pool Write Implementation0.031659841577999996 ETH
0x8b7b22adc924593e77ad66b0ade190efd9ccb0f633e1160bc9cf0d820bce55bd34958622021-12-01 19:57:08848 days 12 hrs ago 0xaf0acd71df2e5f3d637ead63fe3fe3420eec43c7 Premia: Pool Write Implementation0.005576948512 ETH
0xb416089e7e05185427a7499792f384c513e7572865000369bfdccc7b32136e3134952862021-12-01 19:27:01848 days 13 hrs ago 0xc9ac7d67d204257cb1063c710630c676e05fb378 Premia: Pool Write Implementation0.048503483611 ETH
0x266c5a3947fbf9a17607b5ae0f60031626fad851e7f3e6059c8a32167ee3d75734949622021-12-01 19:11:28848 days 13 hrs ago 0xcb7d87f5502fc91529e0fe92373dddd8ff1f3d7c Premia: Pool Write Implementation0.979966673835 ETH
0x2a0a3cd0fb407c2eb6d39eda465eb104592e3ec4f989989b720ab3003a688c9d34943172021-12-01 18:37:23848 days 14 hrs ago 0x5efa253bfa8c626000393c6c654611267261d942 Premia: Pool Write Implementation0 ETH
0x746f63d33af9aee7b861351b5f9f48090a4ec0028f7644c7033f31c7e115e56434937872021-12-01 18:05:20848 days 14 hrs ago 0x6e29e81925e8b7e4ebead1a5f5e176cd55d26edb Premia: Pool Write Implementation0.9241338061650001 ETH
0x1aaa4e23c0907331b64a1d991cff6de48cadf59ed594bc73a66aacd12a6421ff34929352021-12-01 17:14:34848 days 15 hrs ago 0x81d98c8fda0410ee3e9d7586cb949cd19fa4cf38 Premia: Pool Write Implementation0.355330143634 ETH
0xb75d63693b9ab3f6091195200669276dab8aee5df5b2ecd925b3ae14d09bbd0f34902732021-12-01 14:53:23848 days 17 hrs ago 0x75a35f615cb1bc590e83fbbbc1fe10aa7b58fdc1 Premia: Pool Write Implementation0.071731822565 ETH
0x6859f24302a63fb95ea6a052c15c299827f3793cb826c1e57457c9205d0675f334897702021-12-01 14:27:15848 days 18 hrs ago 0x21bfa6e4db97d0c827465261b8b7e0e99bcaad61 Premia: Pool Write Implementation0 ETH
0x6cd42f7601bccb0bd0dc412681b48e014cdafbb490359c06aabc15357dcada4d34830142021-12-01 8:27:16849 days 12 mins ago 0xad7951e4d4608ad0890c10ae5d03ab2a4d87b42e Premia: Pool Write Implementation0.982724371876 ETH
0xe5ef16353f555367b3470068c1d032b307c1796dd997e8b0716c2365332fe31034821012021-12-01 7:47:51849 days 51 mins ago 0x37900736d4baa4fc9930c1a89352391228628c64 Premia: Pool Write Implementation0.009181986861 ETH
[ Download CSV Export 
Loading
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:
PoolWrite

Compiler Version
v0.8.9+commit.e5eed63a

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 38 : PoolWrite.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";
import {IERC20} from "@solidstate/contracts/token/ERC20/IERC20.sol";
import {ERC1155BaseStorage} from "@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol";
import {SafeERC20} from "@solidstate/contracts/utils/SafeERC20.sol";
import {EnumerableSet} from "@solidstate/contracts/utils/EnumerableSet.sol";

import {ABDKMath64x64Token} from "../libraries/ABDKMath64x64Token.sol";
import {PoolSwap} from "./PoolSwap.sol";
import {PoolStorage} from "./PoolStorage.sol";
import {IPoolWrite} from "./IPoolWrite.sol";

/**
 * @title Premia option pool
 * @dev deployed standalone and referenced by PoolProxy
 */
contract PoolWrite is IPoolWrite, PoolSwap {
    using SafeERC20 for IERC20;
    using ABDKMath64x64 for int128;
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;
    using PoolStorage for PoolStorage.Layout;

    constructor(
        address ivolOracle,
        address weth,
        address premiaMining,
        address feeReceiver,
        address feeDiscountAddress,
        int128 fee64x64,
        address uniswapV2Factory,
        address sushiswapFactory
    )
        PoolSwap(
            ivolOracle,
            weth,
            premiaMining,
            feeReceiver,
            feeDiscountAddress,
            fee64x64,
            uniswapV2Factory,
            sushiswapFactory
        )
    {}

    /**
     * @notice calculate price of option contract
     * @param feePayer address of the fee payer
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param contractSize size of option contract
     * @param isCall true for call, false for put
     * @return baseCost64x64 64x64 fixed point representation of option cost denominated in underlying currency (without fee)
     * @return feeCost64x64 64x64 fixed point representation of option fee cost denominated in underlying currency for call, or base currency for put
     * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after purchase
     * @return slippageCoefficient64x64 64x64 fixed point representation of slippage coefficient for given order size
     */
    function quote(
        address feePayer,
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall
    )
        external
        view
        override
        returns (
            int128 baseCost64x64,
            int128 feeCost64x64,
            int128 cLevel64x64,
            int128 slippageCoefficient64x64
        )
    {
        int128 spot64x64;

        PoolStorage.Layout storage l = PoolStorage.layout();
        if (l.updatedAt != block.timestamp) {
            spot64x64 = l.fetchPriceUpdate();
        } else {
            spot64x64 = l.getPriceUpdate(block.timestamp);
        }

        PoolStorage.QuoteResultInternal memory quoteResult = _quote(
            PoolStorage.QuoteArgsInternal(
                feePayer,
                maturity,
                strike64x64,
                spot64x64,
                contractSize,
                isCall
            )
        );

        return (
            quoteResult.baseCost64x64,
            quoteResult.feeCost64x64,
            quoteResult.cLevel64x64,
            quoteResult.slippageCoefficient64x64
        );
    }

    /**
     * @notice purchase option
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param contractSize size of option contract
     * @param isCall true for call, false for put
     * @param maxCost maximum acceptable cost after accounting for slippage
     * @return baseCost quantity of tokens required to purchase long position
     * @return feeCost quantity of tokens required to pay fees
     */
    function purchase(
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall,
        uint256 maxCost
    ) external payable override returns (uint256 baseCost, uint256 feeCost) {
        return
            _purchase(
                maturity,
                strike64x64,
                contractSize,
                isCall,
                maxCost,
                false
            );
    }

    /**
     * @notice purchase option
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param contractSize size of option contract
     * @param isCall true for call, false for put
     * @param maxCost maximum acceptable cost after accounting for slippage
     * @param skipWethDeposit if false, will not try to deposit weth from attach eth
     * @return baseCost quantity of tokens required to purchase long position
     * @return feeCost quantity of tokens required to pay fees
     */
    function _purchase(
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall,
        uint256 maxCost,
        bool skipWethDeposit
    ) internal returns (uint256 baseCost, uint256 feeCost) {
        PoolStorage.Layout storage l = PoolStorage.layout();

        require(maturity >= block.timestamp + (1 days), "exp < 1 day");
        require(maturity < block.timestamp + (91 days), "exp > 90 days");
        require(maturity % (8 hours) == 0, "exp must be 8-hour increment");

        int128 newPrice64x64 = _update(l);

        if (isCall) {
            require(
                strike64x64 <= newPrice64x64 << 1 &&
                    strike64x64 >= (newPrice64x64 * 8) / 10,
                "strike out of range"
            );
        } else {
            require(
                strike64x64 <= (newPrice64x64 * 12) / 10 &&
                    strike64x64 >= newPrice64x64 >> 1,
                "strike out of range"
            );
        }

        (baseCost, feeCost) = _purchase(
            l,
            msg.sender,
            maturity,
            strike64x64,
            isCall,
            contractSize,
            newPrice64x64
        );

        require(baseCost + feeCost <= maxCost, "excess slipp");

        _pullFrom(
            msg.sender,
            _getPoolToken(isCall),
            baseCost + feeCost,
            skipWethDeposit
        );
    }

    /**
     * @notice swap tokens and purchase option
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param contractSize size of option contract
     * @param isCall true for call, false for put
     * @param maxCost maximum acceptable cost after accounting for slippage
     * @param amountOut amount out of tokens requested. If 0, we will swap exact amount necessary to pay the quote
     * @param amountInMax amount in max of tokens
     * @param path swap path
     * @param isSushi whether we use sushi or uniV2 for the swap
     * @return baseCost quantity of tokens required to purchase long position
     * @return feeCost quantity of tokens required to pay fees
     */
    function swapAndPurchase(
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall,
        uint256 maxCost,
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        bool isSushi
    ) public payable override returns (uint256 baseCost, uint256 feeCost) {
        // If value is passed, amountInMax must be 0, as the value wont be used
        // If amountInMax is not 0, user wants to do a swap from an ERC20, and therefore no value should be attached
        require(
            msg.value == 0 || amountInMax == 0,
            "value and amountInMax passed"
        );

        // If no amountOut has been passed, we swap the exact amount required to pay the quote
        if (amountOut == 0) {
            PoolStorage.Layout storage l = PoolStorage.layout();

            int128 newPrice64x64 = _update(l);

            PoolStorage.QuoteArgsInternal memory quoteArgs;
            quoteArgs.feePayer = msg.sender;
            quoteArgs.maturity = maturity;
            quoteArgs.strike64x64 = strike64x64;
            quoteArgs.spot64x64 = newPrice64x64;
            quoteArgs.contractSize = contractSize;
            quoteArgs.isCall = isCall;

            PoolStorage.QuoteResultInternal memory purchaseQuote = _quote(
                quoteArgs
            );

            amountOut = ABDKMath64x64Token.toDecimals(
                purchaseQuote.baseCost64x64.add(purchaseQuote.feeCost64x64),
                l.getTokenDecimals(isCall)
            );
        }

        if (msg.value > 0) {
            _swapETHForExactTokens(amountOut, path, isSushi);
        } else {
            _swapTokensForExactTokens(amountOut, amountInMax, path, isSushi);
        }

        return
            _purchase(
                maturity,
                strike64x64,
                contractSize,
                isCall,
                maxCost,
                true
            );
    }

    /**
     * @notice write option without using liquidity from the pool on behalf of another address
     * @param underwriter underwriter of the option from who collateral will be deposited
     * @param longReceiver address who will receive the long token (Can be the underwriter)
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param contractSize quantity of option contract tokens to exercise
     * @param isCall whether this is a call or a put
     * @return longTokenId token id of the long call
     * @return shortTokenId token id of the short call
     */
    function writeFrom(
        address underwriter,
        address longReceiver,
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall
    )
        external
        payable
        override
        returns (uint256 longTokenId, uint256 shortTokenId)
    {
        require(
            msg.sender == underwriter ||
                ERC1155BaseStorage.layout().operatorApprovals[underwriter][
                    msg.sender
                ],
            "not approved"
        );

        address token = _getPoolToken(isCall);

        uint256 tokenAmount = isCall
            ? contractSize
            : PoolStorage.layout().fromUnderlyingToBaseDecimals(
                strike64x64.mulu(contractSize)
            );

        _pullFrom(underwriter, token, tokenAmount, false);

        longTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, true),
            maturity,
            strike64x64
        );
        shortTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, false),
            maturity,
            strike64x64
        );

        // mint long option token for designated receiver
        _mint(longReceiver, longTokenId, contractSize);
        // mint short option token for underwriter
        _mint(underwriter, shortTokenId, contractSize);

        emit Underwrite(
            underwriter,
            longReceiver,
            shortTokenId,
            contractSize,
            0,
            true
        );
    }

    /**
     * @notice Update pool data
     */
    function update() external override {
        _update(PoolStorage.layout());
    }
}

File 2 of 38 : ABDKMath64x64.sol
// SPDX-License-Identifier: BSD-4-Clause
/*
 * ABDK Math 64.64 Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
 * Author: Mikhail Vladimirov <[email protected]>
 */
pragma solidity ^0.8.0;

/**
 * Smart contract library of mathematical functions operating with signed
 * 64.64-bit fixed point numbers.  Signed 64.64-bit fixed point number is
 * basically a simple fraction whose numerator is signed 128-bit integer and
 * denominator is 2^64.  As long as denominator is always the same, there is no
 * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
 * represented by int128 type holding only the numerator.
 */
library ABDKMath64x64 {
  /*
   * Minimum value signed 64.64-bit fixed point number may have. 
   */
  int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;

  /*
   * Maximum value signed 64.64-bit fixed point number may have. 
   */
  int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

  /**
   * Convert signed 256-bit integer number into signed 64.64-bit fixed point
   * number.  Revert on overflow.
   *
   * @param x signed 256-bit integer number
   * @return signed 64.64-bit fixed point number
   */
  function fromInt (int256 x) internal pure returns (int128) {
    unchecked {
      require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);
      return int128 (x << 64);
    }
  }

  /**
   * Convert signed 64.64 fixed point number into signed 64-bit integer number
   * rounding down.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64-bit integer number
   */
  function toInt (int128 x) internal pure returns (int64) {
    unchecked {
      return int64 (x >> 64);
    }
  }

  /**
   * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
   * number.  Revert on overflow.
   *
   * @param x unsigned 256-bit integer number
   * @return signed 64.64-bit fixed point number
   */
  function fromUInt (uint256 x) internal pure returns (int128) {
    unchecked {
      require (x <= 0x7FFFFFFFFFFFFFFF);
      return int128 (int256 (x << 64));
    }
  }

  /**
   * Convert signed 64.64 fixed point number into unsigned 64-bit integer
   * number rounding down.  Revert on underflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return unsigned 64-bit integer number
   */
  function toUInt (int128 x) internal pure returns (uint64) {
    unchecked {
      require (x >= 0);
      return uint64 (uint128 (x >> 64));
    }
  }

  /**
   * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point
   * number rounding down.  Revert on overflow.
   *
   * @param x signed 128.128-bin fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function from128x128 (int256 x) internal pure returns (int128) {
    unchecked {
      int256 result = x >> 64;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Convert signed 64.64 fixed point number into signed 128.128 fixed point
   * number.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 128.128 fixed point number
   */
  function to128x128 (int128 x) internal pure returns (int256) {
    unchecked {
      return int256 (x) << 64;
    }
  }

  /**
   * Calculate x + y.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function add (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      int256 result = int256(x) + y;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate x - y.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function sub (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      int256 result = int256(x) - y;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate x * y rounding down.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function mul (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      int256 result = int256(x) * y >> 64;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point
   * number and y is signed 256-bit integer number.  Revert on overflow.
   *
   * @param x signed 64.64 fixed point number
   * @param y signed 256-bit integer number
   * @return signed 256-bit integer number
   */
  function muli (int128 x, int256 y) internal pure returns (int256) {
    unchecked {
      if (x == MIN_64x64) {
        require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&
          y <= 0x1000000000000000000000000000000000000000000000000);
        return -y << 63;
      } else {
        bool negativeResult = false;
        if (x < 0) {
          x = -x;
          negativeResult = true;
        }
        if (y < 0) {
          y = -y; // We rely on overflow behavior here
          negativeResult = !negativeResult;
        }
        uint256 absoluteResult = mulu (x, uint256 (y));
        if (negativeResult) {
          require (absoluteResult <=
            0x8000000000000000000000000000000000000000000000000000000000000000);
          return -int256 (absoluteResult); // We rely on overflow behavior here
        } else {
          require (absoluteResult <=
            0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
          return int256 (absoluteResult);
        }
      }
    }
  }

  /**
   * Calculate x * y rounding down, where x is signed 64.64 fixed point number
   * and y is unsigned 256-bit integer number.  Revert on overflow.
   *
   * @param x signed 64.64 fixed point number
   * @param y unsigned 256-bit integer number
   * @return unsigned 256-bit integer number
   */
  function mulu (int128 x, uint256 y) internal pure returns (uint256) {
    unchecked {
      if (y == 0) return 0;

      require (x >= 0);

      uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;
      uint256 hi = uint256 (int256 (x)) * (y >> 128);

      require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
      hi <<= 64;

      require (hi <=
        0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);
      return hi + lo;
    }
  }

  /**
   * Calculate x / y rounding towards zero.  Revert on overflow or when y is
   * zero.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function div (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      require (y != 0);
      int256 result = (int256 (x) << 64) / y;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate x / y rounding towards zero, where x and y are signed 256-bit
   * integer numbers.  Revert on overflow or when y is zero.
   *
   * @param x signed 256-bit integer number
   * @param y signed 256-bit integer number
   * @return signed 64.64-bit fixed point number
   */
  function divi (int256 x, int256 y) internal pure returns (int128) {
    unchecked {
      require (y != 0);

      bool negativeResult = false;
      if (x < 0) {
        x = -x; // We rely on overflow behavior here
        negativeResult = true;
      }
      if (y < 0) {
        y = -y; // We rely on overflow behavior here
        negativeResult = !negativeResult;
      }
      uint128 absoluteResult = divuu (uint256 (x), uint256 (y));
      if (negativeResult) {
        require (absoluteResult <= 0x80000000000000000000000000000000);
        return -int128 (absoluteResult); // We rely on overflow behavior here
      } else {
        require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int128 (absoluteResult); // We rely on overflow behavior here
      }
    }
  }

  /**
   * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
   * integer numbers.  Revert on overflow or when y is zero.
   *
   * @param x unsigned 256-bit integer number
   * @param y unsigned 256-bit integer number
   * @return signed 64.64-bit fixed point number
   */
  function divu (uint256 x, uint256 y) internal pure returns (int128) {
    unchecked {
      require (y != 0);
      uint128 result = divuu (x, y);
      require (result <= uint128 (MAX_64x64));
      return int128 (result);
    }
  }

  /**
   * Calculate -x.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function neg (int128 x) internal pure returns (int128) {
    unchecked {
      require (x != MIN_64x64);
      return -x;
    }
  }

  /**
   * Calculate |x|.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function abs (int128 x) internal pure returns (int128) {
    unchecked {
      require (x != MIN_64x64);
      return x < 0 ? -x : x;
    }
  }

  /**
   * Calculate 1 / x rounding towards zero.  Revert on overflow or when x is
   * zero.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function inv (int128 x) internal pure returns (int128) {
    unchecked {
      require (x != 0);
      int256 result = int256 (0x100000000000000000000000000000000) / x;
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function avg (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      return int128 ((int256 (x) + int256 (y)) >> 1);
    }
  }

  /**
   * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.
   * Revert on overflow or in case x * y is negative.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function gavg (int128 x, int128 y) internal pure returns (int128) {
    unchecked {
      int256 m = int256 (x) * int256 (y);
      require (m >= 0);
      require (m <
          0x4000000000000000000000000000000000000000000000000000000000000000);
      return int128 (sqrtu (uint256 (m)));
    }
  }

  /**
   * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number
   * and y is unsigned 256-bit integer number.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @param y uint256 value
   * @return signed 64.64-bit fixed point number
   */
  function pow (int128 x, uint256 y) internal pure returns (int128) {
    unchecked {
      bool negative = x < 0 && y & 1 == 1;

      uint256 absX = uint128 (x < 0 ? -x : x);
      uint256 absResult;
      absResult = 0x100000000000000000000000000000000;

      if (absX <= 0x10000000000000000) {
        absX <<= 63;
        while (y != 0) {
          if (y & 0x1 != 0) {
            absResult = absResult * absX >> 127;
          }
          absX = absX * absX >> 127;

          if (y & 0x2 != 0) {
            absResult = absResult * absX >> 127;
          }
          absX = absX * absX >> 127;

          if (y & 0x4 != 0) {
            absResult = absResult * absX >> 127;
          }
          absX = absX * absX >> 127;

          if (y & 0x8 != 0) {
            absResult = absResult * absX >> 127;
          }
          absX = absX * absX >> 127;

          y >>= 4;
        }

        absResult >>= 64;
      } else {
        uint256 absXShift = 63;
        if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }
        if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }
        if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }
        if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }
        if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }
        if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }

        uint256 resultShift = 0;
        while (y != 0) {
          require (absXShift < 64);

          if (y & 0x1 != 0) {
            absResult = absResult * absX >> 127;
            resultShift += absXShift;
            if (absResult > 0x100000000000000000000000000000000) {
              absResult >>= 1;
              resultShift += 1;
            }
          }
          absX = absX * absX >> 127;
          absXShift <<= 1;
          if (absX >= 0x100000000000000000000000000000000) {
              absX >>= 1;
              absXShift += 1;
          }

          y >>= 1;
        }

        require (resultShift < 64);
        absResult >>= 64 - resultShift;
      }
      int256 result = negative ? -int256 (absResult) : int256 (absResult);
      require (result >= MIN_64x64 && result <= MAX_64x64);
      return int128 (result);
    }
  }

  /**
   * Calculate sqrt (x) rounding down.  Revert if x < 0.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function sqrt (int128 x) internal pure returns (int128) {
    unchecked {
      require (x >= 0);
      return int128 (sqrtu (uint256 (int256 (x)) << 64));
    }
  }

  /**
   * Calculate binary logarithm of x.  Revert if x <= 0.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function log_2 (int128 x) internal pure returns (int128) {
    unchecked {
      require (x > 0);

      int256 msb = 0;
      int256 xc = x;
      if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
      if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
      if (xc >= 0x10000) { xc >>= 16; msb += 16; }
      if (xc >= 0x100) { xc >>= 8; msb += 8; }
      if (xc >= 0x10) { xc >>= 4; msb += 4; }
      if (xc >= 0x4) { xc >>= 2; msb += 2; }
      if (xc >= 0x2) msb += 1;  // No need to shift xc anymore

      int256 result = msb - 64 << 64;
      uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);
      for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {
        ux *= ux;
        uint256 b = ux >> 255;
        ux >>= 127 + b;
        result += bit * int256 (b);
      }

      return int128 (result);
    }
  }

  /**
   * Calculate natural logarithm of x.  Revert if x <= 0.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function ln (int128 x) internal pure returns (int128) {
    unchecked {
      require (x > 0);

      return int128 (int256 (
          uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));
    }
  }

  /**
   * Calculate binary exponent of x.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function exp_2 (int128 x) internal pure returns (int128) {
    unchecked {
      require (x < 0x400000000000000000); // Overflow

      if (x < -0x400000000000000000) return 0; // Underflow

      uint256 result = 0x80000000000000000000000000000000;

      if (x & 0x8000000000000000 > 0)
        result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
      if (x & 0x4000000000000000 > 0)
        result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
      if (x & 0x2000000000000000 > 0)
        result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
      if (x & 0x1000000000000000 > 0)
        result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
      if (x & 0x800000000000000 > 0)
        result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
      if (x & 0x400000000000000 > 0)
        result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
      if (x & 0x200000000000000 > 0)
        result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
      if (x & 0x100000000000000 > 0)
        result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
      if (x & 0x80000000000000 > 0)
        result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
      if (x & 0x40000000000000 > 0)
        result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
      if (x & 0x20000000000000 > 0)
        result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
      if (x & 0x10000000000000 > 0)
        result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
      if (x & 0x8000000000000 > 0)
        result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
      if (x & 0x4000000000000 > 0)
        result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
      if (x & 0x2000000000000 > 0)
        result = result * 0x1000162E525EE054754457D5995292026 >> 128;
      if (x & 0x1000000000000 > 0)
        result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
      if (x & 0x800000000000 > 0)
        result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
      if (x & 0x400000000000 > 0)
        result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
      if (x & 0x200000000000 > 0)
        result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;
      if (x & 0x100000000000 > 0)
        result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
      if (x & 0x80000000000 > 0)
        result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
      if (x & 0x40000000000 > 0)
        result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
      if (x & 0x20000000000 > 0)
        result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
      if (x & 0x10000000000 > 0)
        result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
      if (x & 0x8000000000 > 0)
        result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
      if (x & 0x4000000000 > 0)
        result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
      if (x & 0x2000000000 > 0)
        result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
      if (x & 0x1000000000 > 0)
        result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
      if (x & 0x800000000 > 0)
        result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
      if (x & 0x400000000 > 0)
        result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
      if (x & 0x200000000 > 0)
        result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
      if (x & 0x100000000 > 0)
        result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
      if (x & 0x80000000 > 0)
        result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
      if (x & 0x40000000 > 0)
        result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
      if (x & 0x20000000 > 0)
        result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;
      if (x & 0x10000000 > 0)
        result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
      if (x & 0x8000000 > 0)
        result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
      if (x & 0x4000000 > 0)
        result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
      if (x & 0x2000000 > 0)
        result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
      if (x & 0x1000000 > 0)
        result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;
      if (x & 0x800000 > 0)
        result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
      if (x & 0x400000 > 0)
        result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;
      if (x & 0x200000 > 0)
        result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;
      if (x & 0x100000 > 0)
        result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;
      if (x & 0x80000 > 0)
        result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
      if (x & 0x40000 > 0)
        result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
      if (x & 0x20000 > 0)
        result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;
      if (x & 0x10000 > 0)
        result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
      if (x & 0x8000 > 0)
        result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
      if (x & 0x4000 > 0)
        result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;
      if (x & 0x2000 > 0)
        result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;
      if (x & 0x1000 > 0)
        result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
      if (x & 0x800 > 0)
        result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
      if (x & 0x400 > 0)
        result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;
      if (x & 0x200 > 0)
        result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;
      if (x & 0x100 > 0)
        result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;
      if (x & 0x80 > 0)
        result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
      if (x & 0x40 > 0)
        result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
      if (x & 0x20 > 0)
        result = result * 0x100000000000000162E42FEFA39EF366F >> 128;
      if (x & 0x10 > 0)
        result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;
      if (x & 0x8 > 0)
        result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
      if (x & 0x4 > 0)
        result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
      if (x & 0x2 > 0)
        result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;
      if (x & 0x1 > 0)
        result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;

      result >>= uint256 (int256 (63 - (x >> 64)));
      require (result <= uint256 (int256 (MAX_64x64)));

      return int128 (int256 (result));
    }
  }

  /**
   * Calculate natural exponent of x.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function exp (int128 x) internal pure returns (int128) {
    unchecked {
      require (x < 0x400000000000000000); // Overflow

      if (x < -0x400000000000000000) return 0; // Underflow

      return exp_2 (
          int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));
    }
  }

  /**
   * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
   * integer numbers.  Revert on overflow or when y is zero.
   *
   * @param x unsigned 256-bit integer number
   * @param y unsigned 256-bit integer number
   * @return unsigned 64.64-bit fixed point number
   */
  function divuu (uint256 x, uint256 y) private pure returns (uint128) {
    unchecked {
      require (y != 0);

      uint256 result;

      if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        result = (x << 64) / y;
      else {
        uint256 msb = 192;
        uint256 xc = x >> 192;
        if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
        if (xc >= 0x10000) { xc >>= 16; msb += 16; }
        if (xc >= 0x100) { xc >>= 8; msb += 8; }
        if (xc >= 0x10) { xc >>= 4; msb += 4; }
        if (xc >= 0x4) { xc >>= 2; msb += 2; }
        if (xc >= 0x2) msb += 1;  // No need to shift xc anymore

        result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);
        require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

        uint256 hi = result * (y >> 128);
        uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

        uint256 xh = x >> 192;
        uint256 xl = x << 64;

        if (xl < lo) xh -= 1;
        xl -= lo; // We rely on overflow behavior here
        lo = hi << 128;
        if (xl < lo) xh -= 1;
        xl -= lo; // We rely on overflow behavior here

        assert (xh == hi >> 128);

        result += xl / y;
      }

      require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
      return uint128 (result);
    }
  }

  /**
   * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
   * number.
   *
   * @param x unsigned 256-bit integer number
   * @return unsigned 128-bit integer number
   */
  function sqrtu (uint256 x) private pure returns (uint128) {
    unchecked {
      if (x == 0) return 0;
      else {
        uint256 xx = x;
        uint256 r = 1;
        if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }
        if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }
        if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }
        if (xx >= 0x10000) { xx >>= 16; r <<= 8; }
        if (xx >= 0x100) { xx >>= 8; r <<= 4; }
        if (xx >= 0x10) { xx >>= 4; r <<= 2; }
        if (xx >= 0x8) { r <<= 1; }
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1; // Seven iterations should be enough
        uint256 r1 = x / r;
        return uint128 (r < r1 ? r : r1);
      }
    }
  }
}

File 3 of 38 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC20Internal } from './IERC20Internal.sol';

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 is IERC20Internal {
    /**
     * @notice query the total minted token supply
     * @return token supply
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice query the token balance of given account
     * @param account address to query
     * @return token balance
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice query the allowance granted from given holder to given spender
     * @param holder approver of allowance
     * @param spender recipient of allowance
     * @return token allowance
     */
    function allowance(address holder, address spender)
        external
        view
        returns (uint256);

    /**
     * @notice grant approval to spender to spend tokens
     * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
     * @param spender recipient of allowance
     * @param amount quantity of tokens approved for spending
     * @return success status (always true; otherwise function should revert)
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @notice transfer tokens to given recipient
     * @param recipient beneficiary of token transfer
     * @param amount quantity of tokens to transfer
     * @return success status (always true; otherwise function should revert)
     */
    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    /**
     * @notice transfer tokens to given recipient on behalf of given holder
     * @param holder holder of tokens prior to transfer
     * @param recipient beneficiary of token transfer
     * @param amount quantity of tokens to transfer
     * @return success status (always true; otherwise function should revert)
     */
    function transferFrom(
        address holder,
        address recipient,
        uint256 amount
    ) external returns (bool);
}

File 4 of 38 : ERC1155BaseStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library ERC1155BaseStorage {
    struct Layout {
        mapping(uint256 => mapping(address => uint256)) balances;
        mapping(address => mapping(address => bool)) operatorApprovals;
    }

    bytes32 internal constant STORAGE_SLOT =
        keccak256('solidstate.contracts.storage.ERC1155Base');

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }
}

File 5 of 38 : SafeERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC20 } from '../token/ERC20/IERC20.sol';
import { AddressUtils } from './AddressUtils.sol';

/**
 * @title Safe ERC20 interaction library
 * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
 */
library SafeERC20 {
    using AddressUtils for address;

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

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

    /**
     * @dev safeApprove (like approve) should only be called when setting an initial allowance or when resetting it to zero; otherwise prefer safeIncreaseAllowance and safeDecreaseAllowance
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            'SafeERC20: approve from non-zero to non-zero allowance'
        );

        _callOptionalReturn(
            token,
            abi.encodeWithSelector(token.approve.selector, spender, value)
        );
    }

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

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(
                oldAllowance >= value,
                'SafeERC20: decreased allowance below zero'
            );
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(
                token,
                abi.encodeWithSelector(
                    token.approve.selector,
                    spender,
                    newAllowance
                )
            );
        }
    }

    /**
     * @notice send transaction data and check validity of return value, if present
     * @param token ERC20 token interface
     * @param data transaction data
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        bytes memory returndata = address(token).functionCall(
            data,
            'SafeERC20: low-level call failed'
        );

        if (returndata.length > 0) {
            require(
                abi.decode(returndata, (bool)),
                'SafeERC20: ERC20 operation did not succeed'
            );
        }
    }
}

File 6 of 38 : EnumerableSet.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Set implementation with enumeration functions
 * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
 */
library EnumerableSet {
    struct Set {
        bytes32[] _values;
        // 1-indexed to allow 0 to signify nonexistence
        mapping(bytes32 => uint256) _indexes;
    }

    struct Bytes32Set {
        Set _inner;
    }

    struct AddressSet {
        Set _inner;
    }

    struct UintSet {
        Set _inner;
    }

    function at(Bytes32Set storage set, uint256 index)
        internal
        view
        returns (bytes32)
    {
        return _at(set._inner, index);
    }

    function at(AddressSet storage set, uint256 index)
        internal
        view
        returns (address)
    {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    function at(UintSet storage set, uint256 index)
        internal
        view
        returns (uint256)
    {
        return uint256(_at(set._inner, index));
    }

    function contains(Bytes32Set storage set, bytes32 value)
        internal
        view
        returns (bool)
    {
        return _contains(set._inner, value);
    }

    function contains(AddressSet storage set, address value)
        internal
        view
        returns (bool)
    {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    function contains(UintSet storage set, uint256 value)
        internal
        view
        returns (bool)
    {
        return _contains(set._inner, bytes32(value));
    }

    function indexOf(Bytes32Set storage set, bytes32 value)
        internal
        view
        returns (uint256)
    {
        return _indexOf(set._inner, value);
    }

    function indexOf(AddressSet storage set, address value)
        internal
        view
        returns (uint256)
    {
        return _indexOf(set._inner, bytes32(uint256(uint160(value))));
    }

    function indexOf(UintSet storage set, uint256 value)
        internal
        view
        returns (uint256)
    {
        return _indexOf(set._inner, bytes32(value));
    }

    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function add(Bytes32Set storage set, bytes32 value)
        internal
        returns (bool)
    {
        return _add(set._inner, value);
    }

    function add(AddressSet storage set, address value)
        internal
        returns (bool)
    {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    function remove(Bytes32Set storage set, bytes32 value)
        internal
        returns (bool)
    {
        return _remove(set._inner, value);
    }

    function remove(AddressSet storage set, address value)
        internal
        returns (bool)
    {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    function remove(UintSet storage set, uint256 value)
        internal
        returns (bool)
    {
        return _remove(set._inner, bytes32(value));
    }

    function _at(Set storage set, uint256 index)
        private
        view
        returns (bytes32)
    {
        require(
            set._values.length > index,
            'EnumerableSet: index out of bounds'
        );
        return set._values[index];
    }

    function _contains(Set storage set, bytes32 value)
        private
        view
        returns (bool)
    {
        return set._indexes[value] != 0;
    }

    function _indexOf(Set storage set, bytes32 value)
        private
        view
        returns (uint256)
    {
        unchecked {
            return set._indexes[value] - 1;
        }
    }

    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    function _remove(Set storage set, bytes32 value) private returns (bool) {
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            uint256 index = valueIndex - 1;
            bytes32 last = set._values[set._values.length - 1];

            // move last value to now-vacant index

            set._values[index] = last;
            set._indexes[last] = index + 1;

            // clear last index

            set._values.pop();
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }
}

File 7 of 38 : ABDKMath64x64Token.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";

library ABDKMath64x64Token {
    using ABDKMath64x64 for int128;

    /**
     * @notice convert 64x64 fixed point representation of token amount to decimal
     * @param value64x64 64x64 fixed point representation of token amount
     * @param decimals token display decimals
     * @return value decimal representation of token amount
     */
    function toDecimals(int128 value64x64, uint8 decimals)
        internal
        pure
        returns (uint256 value)
    {
        value = value64x64.mulu(10**decimals);
    }

    /**
     * @notice convert decimal representation of token amount to 64x64 fixed point
     * @param value decimal representation of token amount
     * @param decimals token display decimals
     * @return value64x64 64x64 fixed point representation of token amount
     */
    function fromDecimals(uint256 value, uint8 decimals)
        internal
        pure
        returns (int128 value64x64)
    {
        value64x64 = ABDKMath64x64.divu(value, 10**decimals);
    }

    /**
     * @notice convert 64x64 fixed point representation of token amount to wei (18 decimals)
     * @param value64x64 64x64 fixed point representation of token amount
     * @return value wei representation of token amount
     */
    function toWei(int128 value64x64) internal pure returns (uint256 value) {
        value = toDecimals(value64x64, 18);
    }

    /**
     * @notice convert wei representation (18 decimals) of token amount to 64x64 fixed point
     * @param value wei representation of token amount
     * @return value64x64 64x64 fixed point representation of token amount
     */
    function fromWei(uint256 value) internal pure returns (int128 value64x64) {
        value64x64 = fromDecimals(value, 18);
    }
}

File 8 of 38 : PoolSwap.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

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

import {IWETH} from "@solidstate/contracts/utils/IWETH.sol";
import {IUniswapV2Pair} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import {SafeERC20} from "@solidstate/contracts/utils/SafeERC20.sol";
import {IERC20} from "@solidstate/contracts/token/ERC20/IERC20.sol";
import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";
import {PoolInternal} from "./PoolInternal.sol";

/**
 * @title Premia option pool
 * @dev deployed standalone and referenced by PoolProxy
 */
abstract contract PoolSwap is PoolInternal {
    using SafeERC20 for IERC20;
    using ABDKMath64x64 for int128;
    using PoolStorage for PoolStorage.Layout;

    address internal immutable UNISWAP_V2_FACTORY;
    address internal immutable SUSHISWAP_FACTORY;

    constructor(
        address ivolOracle,
        address weth,
        address premiaMining,
        address feeReceiver,
        address feeDiscountAddress,
        int128 fee64x64,
        address uniswapV2Factory,
        address sushiswapFactory
    )
        PoolInternal(
            ivolOracle,
            weth,
            premiaMining,
            feeReceiver,
            feeDiscountAddress,
            fee64x64
        )
    {
        UNISWAP_V2_FACTORY = uniswapV2Factory;
        SUSHISWAP_FACTORY = sushiswapFactory;
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function _pairFor(
        address factory,
        address tokenA,
        address tokenB,
        bool isSushi
    ) internal pure returns (address pair) {
        (address token0, address token1) = _sortTokens(tokenA, tokenB);
        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(abi.encodePacked(token0, token1)),
                            isSushi
                                ? hex"e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303"
                                : hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash
                        )
                    )
                )
            )
        );
    }

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

    // performs chained getAmountIn calculations on any number of pairs
    function _getAmountsIn(
        address factory,
        uint256 amountOut,
        address[] memory path,
        bool isSushi
    ) internal view returns (uint256[] memory amounts) {
        require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[amounts.length - 1] = amountOut;
        for (uint256 i = path.length - 1; i > 0; i--) {
            (uint256 reserveIn, uint256 reserveOut) = _getReserves(
                factory,
                path[i - 1],
                path[i],
                isSushi
            );
            amounts[i - 1] = _getAmountIn(amounts[i], reserveIn, reserveOut);
        }
    }

    // fetches and sorts the reserves for a pair
    function _getReserves(
        address factory,
        address tokenA,
        address tokenB,
        bool isSushi
    ) internal view returns (uint256 reserveA, uint256 reserveB) {
        (address token0, ) = _sortTokens(tokenA, tokenB);
        (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
            _pairFor(factory, tokenA, tokenB, isSushi)
        ).getReserves();
        (reserveA, reserveB) = tokenA == token0
            ? (reserve0, reserve1)
            : (reserve1, reserve0);
    }

    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    function _getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256 amountIn) {
        require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT");
        require(
            reserveIn > 0 && reserveOut > 0,
            "UniswapV2Library: INSUFFICIENT_LIQUIDITY"
        );
        uint256 numerator = reserveIn * amountOut * 1000;
        uint256 denominator = (reserveOut - amountOut) * 997;
        amountIn = (numerator / denominator) + 1;
    }

    // requires the initial amount to have already been sent to the first pair
    function _swap(
        uint256[] memory amounts,
        address[] memory path,
        address _to,
        bool isSushi
    ) internal {
        for (uint256 i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0, ) = _sortTokens(input, output);
            uint256 amountOut = amounts[i + 1];
            (uint256 amount0Out, uint256 amount1Out) = input == token0
                ? (uint256(0), amountOut)
                : (amountOut, uint256(0));
            address to = i < path.length - 2
                ? _pairFor(
                    isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
                    output,
                    path[i + 2],
                    isSushi
                )
                : _to;
            IUniswapV2Pair(
                _pairFor(
                    isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
                    input,
                    output,
                    isSushi
                )
            ).swap(amount0Out, amount1Out, to, new bytes(0));
        }
    }

    function _swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        bool isSushi
    ) internal returns (uint256[] memory amounts) {
        amounts = _getAmountsIn(
            isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
            amountOut,
            path,
            isSushi
        );
        require(
            amounts[0] <= amountInMax,
            "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT"
        );
        IERC20(path[0]).safeTransferFrom(
            msg.sender,
            _pairFor(
                isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
                path[0],
                path[1],
                isSushi
            ),
            amounts[0]
        );
        _swap(amounts, path, msg.sender, isSushi);
    }

    function _swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        bool isSushi
    ) internal returns (uint256[] memory amounts) {
        require(path[0] == WETH_ADDRESS, "UniswapV2Router: INVALID_PATH");
        amounts = _getAmountsIn(
            isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
            amountOut,
            path,
            isSushi
        );
        require(
            amounts[0] <= msg.value,
            "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT"
        );
        IWETH(WETH_ADDRESS).deposit{value: amounts[0]}();
        assert(
            IWETH(WETH_ADDRESS).transfer(
                _pairFor(
                    isSushi ? SUSHISWAP_FACTORY : UNISWAP_V2_FACTORY,
                    path[0],
                    path[1],
                    isSushi
                ),
                amounts[0]
            )
        );

        _swap(amounts, path, msg.sender, isSushi);

        // refund dust eth, if any
        if (msg.value > amounts[0]) {
            (bool success, ) = payable(msg.sender).call{
                value: msg.value - amounts[0]
            }(new bytes(0));
            require(
                success,
                "TransferHelper::safeTransferETH: ETH transfer failed"
            );
        }
    }
}

File 9 of 38 : PoolStorage.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {AggregatorInterface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {EnumerableSet, ERC1155EnumerableStorage} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol";

import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";
import {ABDKMath64x64Token} from "../libraries/ABDKMath64x64Token.sol";
import {OptionMath} from "../libraries/OptionMath.sol";

library PoolStorage {
    using ABDKMath64x64 for int128;
    using PoolStorage for PoolStorage.Layout;

    enum TokenType {
        UNDERLYING_FREE_LIQ,
        BASE_FREE_LIQ,
        UNDERLYING_RESERVED_LIQ,
        BASE_RESERVED_LIQ,
        LONG_CALL,
        SHORT_CALL,
        LONG_PUT,
        SHORT_PUT
    }

    struct PoolSettings {
        address underlying;
        address base;
        address underlyingOracle;
        address baseOracle;
    }

    struct QuoteArgsInternal {
        address feePayer; // address of the fee payer
        uint64 maturity; // timestamp of option maturity
        int128 strike64x64; // 64x64 fixed point representation of strike price
        int128 spot64x64; // 64x64 fixed point representation of spot price
        uint256 contractSize; // size of option contract
        bool isCall; // true for call, false for put
    }

    struct QuoteResultInternal {
        int128 baseCost64x64; // 64x64 fixed point representation of option cost denominated in underlying currency (without fee)
        int128 feeCost64x64; // 64x64 fixed point representation of option fee cost denominated in underlying currency for call, or base currency for put
        int128 cLevel64x64; // 64x64 fixed point representation of C-Level of Pool after purchase
        int128 slippageCoefficient64x64; // 64x64 fixed point representation of slippage coefficient for given order size
    }

    struct BatchData {
        uint256 eta;
        uint256 totalPendingDeposits;
    }

    bytes32 internal constant STORAGE_SLOT =
        keccak256("premia.contracts.storage.Pool");

    uint256 private constant C_DECAY_BUFFER = 12 hours;
    uint256 private constant C_DECAY_INTERVAL = 4 hours;

    struct Layout {
        // ERC20 token addresses
        address base;
        address underlying;
        // AggregatorV3Interface oracle addresses
        address baseOracle;
        address underlyingOracle;
        // token metadata
        uint8 underlyingDecimals;
        uint8 baseDecimals;
        // minimum amounts
        uint256 baseMinimum;
        uint256 underlyingMinimum;
        // deposit caps
        uint256 basePoolCap;
        uint256 underlyingPoolCap;
        // market state
        int128 _deprecated_steepness64x64;
        int128 cLevelBase64x64;
        int128 cLevelUnderlying64x64;
        uint256 cLevelBaseUpdatedAt;
        uint256 cLevelUnderlyingUpdatedAt;
        uint256 updatedAt;
        // User -> isCall -> depositedAt
        mapping(address => mapping(bool => uint256)) depositedAt;
        mapping(address => mapping(bool => uint256)) divestmentTimestamps;
        // doubly linked list of free liquidity intervals
        // isCall -> User -> User
        mapping(bool => mapping(address => address)) liquidityQueueAscending;
        mapping(bool => mapping(address => address)) liquidityQueueDescending;
        // minimum resolution price bucket => price
        mapping(uint256 => int128) bucketPrices64x64;
        // sequence id (minimum resolution price bucket / 256) => price update sequence
        mapping(uint256 => uint256) priceUpdateSequences;
        // isCall -> batch data
        mapping(bool => BatchData) nextDeposits;
        // user -> batch timestamp -> isCall -> pending amount
        mapping(address => mapping(uint256 => mapping(bool => uint256))) pendingDeposits;
        EnumerableSet.UintSet tokenIds;
        // user -> isCallPool -> total value locked of user (Used for liquidity mining)
        mapping(address => mapping(bool => uint256)) userTVL;
        // isCallPool -> total value locked
        mapping(bool => uint256) totalTVL;
        // steepness values
        int128 steepnessBase64x64;
        int128 steepnessUnderlying64x64;
    }

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }

    /**
     * @notice calculate ERC1155 token id for given option parameters
     * @param tokenType TokenType enum
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @return tokenId token id
     */
    function formatTokenId(
        TokenType tokenType,
        uint64 maturity,
        int128 strike64x64
    ) internal pure returns (uint256 tokenId) {
        tokenId =
            (uint256(tokenType) << 248) +
            (uint256(maturity) << 128) +
            uint256(int256(strike64x64));
    }

    /**
     * @notice derive option maturity and strike price from ERC1155 token id
     * @param tokenId token id
     * @return tokenType TokenType enum
     * @return maturity timestamp of option maturity
     * @return strike64x64 option strike price
     */
    function parseTokenId(uint256 tokenId)
        internal
        pure
        returns (
            TokenType tokenType,
            uint64 maturity,
            int128 strike64x64
        )
    {
        assembly {
            tokenType := shr(248, tokenId)
            maturity := shr(128, tokenId)
            strike64x64 := tokenId
        }
    }

    function getTokenDecimals(Layout storage l, bool isCall)
        internal
        view
        returns (uint8 decimals)
    {
        decimals = isCall ? l.underlyingDecimals : l.baseDecimals;
    }

    /**
     * @notice get the total supply of free liquidity tokens, minus pending deposits
     * @param l storage layout struct
     * @param isCall whether query is for call or put pool
     * @return 64x64 fixed point representation of total free liquidity
     */
    function totalFreeLiquiditySupply64x64(Layout storage l, bool isCall)
        internal
        view
        returns (int128)
    {
        uint256 tokenId = formatTokenId(
            isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,
            0,
            0
        );

        return
            ABDKMath64x64Token.fromDecimals(
                ERC1155EnumerableStorage.layout().totalSupply[tokenId] -
                    l.nextDeposits[isCall].totalPendingDeposits,
                l.getTokenDecimals(isCall)
            );
    }

    function getReinvestmentStatus(
        Layout storage l,
        address account,
        bool isCallPool
    ) internal view returns (bool) {
        uint256 timestamp = l.divestmentTimestamps[account][isCallPool];
        return timestamp == 0 || timestamp > block.timestamp;
    }

    function addUnderwriter(
        Layout storage l,
        address account,
        bool isCallPool
    ) internal {
        require(account != address(0));

        mapping(address => address) storage asc = l.liquidityQueueAscending[
            isCallPool
        ];
        mapping(address => address) storage desc = l.liquidityQueueDescending[
            isCallPool
        ];

        if (_isInQueue(account, asc, desc)) return;

        address last = desc[address(0)];

        asc[last] = account;
        desc[account] = last;
        desc[address(0)] = account;
    }

    function removeUnderwriter(
        Layout storage l,
        address account,
        bool isCallPool
    ) internal {
        require(account != address(0));

        mapping(address => address) storage asc = l.liquidityQueueAscending[
            isCallPool
        ];
        mapping(address => address) storage desc = l.liquidityQueueDescending[
            isCallPool
        ];

        if (!_isInQueue(account, asc, desc)) return;

        address prev = desc[account];
        address next = asc[account];
        asc[prev] = next;
        desc[next] = prev;
        delete asc[account];
        delete desc[account];
    }

    function isInQueue(
        Layout storage l,
        address account,
        bool isCallPool
    ) internal view returns (bool) {
        mapping(address => address) storage asc = l.liquidityQueueAscending[
            isCallPool
        ];
        mapping(address => address) storage desc = l.liquidityQueueDescending[
            isCallPool
        ];

        return _isInQueue(account, asc, desc);
    }

    function _isInQueue(
        address account,
        mapping(address => address) storage asc,
        mapping(address => address) storage desc
    ) private view returns (bool) {
        return asc[account] != address(0) || desc[address(0)] == account;
    }

    /**
     * @notice get current C-Level, without accounting for pending adjustments
     * @param l storage layout struct
     * @param isCall whether query is for call or put pool
     * @return cLevel64x64 64x64 fixed point representation of C-Level
     */
    function getRawCLevel64x64(Layout storage l, bool isCall)
        internal
        view
        returns (int128 cLevel64x64)
    {
        cLevel64x64 = isCall ? l.cLevelUnderlying64x64 : l.cLevelBase64x64;
    }

    /**
     * @notice get current C-Level, accounting for unrealized decay
     * @param l storage layout struct
     * @param isCall whether query is for call or put pool
     * @return cLevel64x64 64x64 fixed point representation of C-Level
     */
    function getDecayAdjustedCLevel64x64(Layout storage l, bool isCall)
        internal
        view
        returns (int128 cLevel64x64)
    {
        // get raw C-Level from storage
        cLevel64x64 = l.getRawCLevel64x64(isCall);

        // account for C-Level decay
        cLevel64x64 = l.applyCLevelDecayAdjustment(cLevel64x64, isCall);
    }

    /**
     * @notice calculate updated C-Level, accounting for unrealized decay
     * @param l storage layout struct
     * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for decay
     * @param isCall whether query is for call or put pool
     * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after accounting for decay
     */
    function applyCLevelDecayAdjustment(
        Layout storage l,
        int128 oldCLevel64x64,
        bool isCall
    ) internal view returns (int128 cLevel64x64) {
        uint256 timeElapsed = block.timestamp -
            (isCall ? l.cLevelUnderlyingUpdatedAt : l.cLevelBaseUpdatedAt);

        // do not apply C decay if less than 24 hours have elapsed

        if (timeElapsed > C_DECAY_BUFFER) {
            timeElapsed -= C_DECAY_BUFFER;
        } else {
            return oldCLevel64x64;
        }

        int128 timeIntervalsElapsed64x64 = ABDKMath64x64.divu(
            timeElapsed,
            C_DECAY_INTERVAL
        );

        uint256 tokenId = formatTokenId(
            isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,
            0,
            0
        );

        uint256 tvl = l.totalTVL[isCall];

        int128 utilization = ABDKMath64x64.divu(
            tvl -
                (ERC1155EnumerableStorage.layout().totalSupply[tokenId] -
                    l.nextDeposits[isCall].totalPendingDeposits),
            tvl
        );

        return
            OptionMath.calculateCLevelDecay(
                OptionMath.CalculateCLevelDecayArgs(
                    timeIntervalsElapsed64x64,
                    oldCLevel64x64,
                    utilization,
                    0xb333333333333333, // 0.7
                    0xe666666666666666, // 0.9
                    0x10000000000000000, // 1.0
                    0x10000000000000000, // 1.0
                    0xe666666666666666, // 0.9
                    0x56fc2a2c515da32ea // 2e
                )
            );
    }

    /**
     * @notice calculate updated C-Level, accounting for pending deposits
     * @param l storage layout struct
     * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change
     * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity
     * @param isCall whether to update C-Level for call or put pool
     * @return cLevel64x64 64x64 fixed point representation of C-Level
     * @return liquidity64x64 64x64 fixed point representation of new liquidity amount
     */
    function applyCLevelPendingDepositAdjustment(
        Layout storage l,
        int128 oldCLevel64x64,
        int128 oldLiquidity64x64,
        bool isCall
    ) internal view returns (int128 cLevel64x64, int128 liquidity64x64) {
        PoolStorage.BatchData storage batchData = l.nextDeposits[isCall];
        int128 pendingDeposits64x64;

        if (
            batchData.totalPendingDeposits > 0 &&
            batchData.eta != 0 &&
            block.timestamp >= batchData.eta
        ) {
            pendingDeposits64x64 = ABDKMath64x64Token.fromDecimals(
                batchData.totalPendingDeposits,
                l.getTokenDecimals(isCall)
            );

            liquidity64x64 = oldLiquidity64x64.add(pendingDeposits64x64);

            cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(
                oldCLevel64x64,
                oldLiquidity64x64,
                liquidity64x64,
                isCall
            );
        } else {
            cLevel64x64 = oldCLevel64x64;
            liquidity64x64 = oldLiquidity64x64;
        }
    }

    /**
     * @notice calculate updated C-Level, accounting for change in liquidity
     * @param l storage layout struct
     * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change
     * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity
     * @param newLiquidity64x64 64x64 fixed point representation of current liquidity
     * @param isCallPool whether to update C-Level for call or put pool
     * @return cLevel64x64 64x64 fixed point representation of C-Level
     */
    function applyCLevelLiquidityChangeAdjustment(
        Layout storage l,
        int128 oldCLevel64x64,
        int128 oldLiquidity64x64,
        int128 newLiquidity64x64,
        bool isCallPool
    ) internal view returns (int128 cLevel64x64) {
        int128 steepness64x64 = isCallPool
            ? l.steepnessUnderlying64x64
            : l.steepnessBase64x64;

        // fallback to deprecated storage value if side-specific value is not set
        if (steepness64x64 == 0) steepness64x64 = l._deprecated_steepness64x64;

        cLevel64x64 = OptionMath.calculateCLevel(
            oldCLevel64x64,
            oldLiquidity64x64,
            newLiquidity64x64,
            steepness64x64
        );

        if (cLevel64x64 < 0xb333333333333333) {
            cLevel64x64 = int128(0xb333333333333333); // 64x64 fixed point representation of 0.7
        }
    }

    /**
     * @notice set C-Level to arbitrary pre-calculated value
     * @param cLevel64x64 new C-Level of pool
     * @param isCallPool whether to update C-Level for call or put pool
     */
    function setCLevel(
        Layout storage l,
        int128 cLevel64x64,
        bool isCallPool
    ) internal {
        if (isCallPool) {
            l.cLevelUnderlying64x64 = cLevel64x64;
            l.cLevelUnderlyingUpdatedAt = block.timestamp;
        } else {
            l.cLevelBase64x64 = cLevel64x64;
            l.cLevelBaseUpdatedAt = block.timestamp;
        }
    }

    function setOracles(
        Layout storage l,
        address baseOracle,
        address underlyingOracle
    ) internal {
        require(
            AggregatorV3Interface(baseOracle).decimals() ==
                AggregatorV3Interface(underlyingOracle).decimals(),
            "Pool: oracle decimals must match"
        );

        l.baseOracle = baseOracle;
        l.underlyingOracle = underlyingOracle;
    }

    function fetchPriceUpdate(Layout storage l)
        internal
        view
        returns (int128 price64x64)
    {
        int256 priceUnderlying = AggregatorInterface(l.underlyingOracle)
            .latestAnswer();
        int256 priceBase = AggregatorInterface(l.baseOracle).latestAnswer();

        return ABDKMath64x64.divi(priceUnderlying, priceBase);
    }

    /**
     * @notice set price update for hourly bucket corresponding to given timestamp
     * @param l storage layout struct
     * @param timestamp timestamp to update
     * @param price64x64 64x64 fixed point representation of price
     */
    function setPriceUpdate(
        Layout storage l,
        uint256 timestamp,
        int128 price64x64
    ) internal {
        uint256 bucket = timestamp / (1 hours);
        l.bucketPrices64x64[bucket] = price64x64;
        l.priceUpdateSequences[bucket >> 8] += 1 << (255 - (bucket & 255));
    }

    /**
     * @notice get price update for hourly bucket corresponding to given timestamp
     * @param l storage layout struct
     * @param timestamp timestamp to query
     * @return 64x64 fixed point representation of price
     */
    function getPriceUpdate(Layout storage l, uint256 timestamp)
        internal
        view
        returns (int128)
    {
        return l.bucketPrices64x64[timestamp / (1 hours)];
    }

    /**
     * @notice get first price update available following given timestamp
     * @param l storage layout struct
     * @param timestamp timestamp to query
     * @return 64x64 fixed point representation of price
     */
    function getPriceUpdateAfter(Layout storage l, uint256 timestamp)
        internal
        view
        returns (int128)
    {
        // price updates are grouped into hourly buckets
        uint256 bucket = timestamp / (1 hours);
        // divide by 256 to get the index of the relevant price update sequence
        uint256 sequenceId = bucket >> 8;

        // get position within sequence relevant to current price update

        uint256 offset = bucket & 255;
        // shift to skip buckets from earlier in sequence
        uint256 sequence = (l.priceUpdateSequences[sequenceId] << offset) >>
            offset;

        // iterate through future sequences until a price update is found
        // sequence corresponding to current timestamp used as upper bound

        uint256 currentPriceUpdateSequenceId = block.timestamp / (256 hours);

        while (sequence == 0 && sequenceId <= currentPriceUpdateSequenceId) {
            sequence = l.priceUpdateSequences[++sequenceId];
        }

        // if no price update is found (sequence == 0) function will return 0
        // this should never occur, as each relevant external function triggers a price update

        // the most significant bit of the sequence corresponds to the offset of the relevant bucket

        uint256 msb;

        for (uint256 i = 128; i > 0; i >>= 1) {
            if (sequence >> i > 0) {
                msb += i;
                sequence >>= i;
            }
        }

        return l.bucketPrices64x64[((sequenceId + 1) << 8) - msb - 1];
    }

    function fromBaseToUnderlyingDecimals(Layout storage l, uint256 value)
        internal
        view
        returns (uint256)
    {
        int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(
            value,
            l.baseDecimals
        );
        return
            ABDKMath64x64Token.toDecimals(
                valueFixed64x64,
                l.underlyingDecimals
            );
    }

    function fromUnderlyingToBaseDecimals(Layout storage l, uint256 value)
        internal
        view
        returns (uint256)
    {
        int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(
            value,
            l.underlyingDecimals
        );
        return ABDKMath64x64Token.toDecimals(valueFixed64x64, l.baseDecimals);
    }
}

File 10 of 38 : IPoolWrite.sol
// SPDX-License-Identifier: LGPL-3.0-or-later

pragma solidity ^0.8.0;

interface IPoolWrite {
    function quote(
        address feePayer,
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall
    )
        external
        view
        returns (
            int128 baseCost64x64,
            int128 feeCost64x64,
            int128 cLevel64x64,
            int128 slippageCoefficient64x64
        );

    function purchase(
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall,
        uint256 maxCost
    ) external payable returns (uint256 baseCost, uint256 feeCost);

    function swapAndPurchase(
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall,
        uint256 maxCost,
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        bool isSushi
    ) external payable returns (uint256 baseCost, uint256 feeCost);

    function writeFrom(
        address underwriter,
        address longReceiver,
        uint64 maturity,
        int128 strike64x64,
        uint256 contractSize,
        bool isCall
    ) external payable returns (uint256 longTokenId, uint256 shortTokenId);

    function update() external;
}

File 11 of 38 : IERC20Internal.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Partial ERC20 interface needed by internal functions
 */
interface IERC20Internal {
    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

File 12 of 38 : AddressUtils.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library AddressUtils {
    function toString(address account) internal pure returns (string memory) {
        bytes32 value = bytes32(uint256(uint160(account)));
        bytes memory alphabet = '0123456789abcdef';
        bytes memory chars = new bytes(42);

        chars[0] = '0';
        chars[1] = 'x';

        for (uint256 i = 0; i < 20; i++) {
            chars[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];
            chars[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
        }

        return string(chars);
    }

    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    function sendValue(address payable account, uint256 amount) internal {
        (bool success, ) = account.call{ value: amount }('');
        require(success, 'AddressUtils: failed to send value');
    }

    function functionCall(address target, bytes memory data)
        internal
        returns (bytes memory)
    {
        return
            functionCall(target, data, 'AddressUtils: failed low-level call');
    }

    function functionCall(
        address target,
        bytes memory data,
        string memory error
    ) internal returns (bytes memory) {
        return _functionCallWithValue(target, data, 0, error);
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return
            functionCallWithValue(
                target,
                data,
                value,
                'AddressUtils: failed low-level call with value'
            );
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory error
    ) internal returns (bytes memory) {
        require(
            address(this).balance >= value,
            'AddressUtils: insufficient balance for call'
        );
        return _functionCallWithValue(target, data, value, error);
    }

    function _functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory error
    ) private returns (bytes memory) {
        require(
            isContract(target),
            'AddressUtils: function call to non-contract'
        );

        (bool success, bytes memory returnData) = target.call{ value: value }(
            data
        );

        if (success) {
            return returnData;
        } else if (returnData.length > 0) {
            assembly {
                let returnData_size := mload(returnData)
                revert(add(32, returnData), returnData_size)
            }
        } else {
            revert(error);
        }
    }
}

File 13 of 38 : IWETH.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC20 } from '../token/ERC20/IERC20.sol';
import { IERC20Metadata } from '../token/ERC20/metadata/IERC20Metadata.sol';

/**
 * @title WETH (Wrapped ETH) interface
 */
interface IWETH is IERC20, IERC20Metadata {
    /**
     * @notice convert ETH to WETH
     */
    function deposit() external payable;

    /**
     * @notice convert WETH to ETH
     * @dev if caller is a contract, it should have a fallback or receive function
     * @param amount quantity of WETH to convert, denominated in wei
     */
    function withdraw(uint256 amount) external;
}

File 14 of 38 : IUniswapV2Pair.sol
pragma solidity >=0.5.0;

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

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

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

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

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

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

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

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

    function initialize(address, address) external;
}

File 15 of 38 : PoolInternal.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {IERC173} from "@solidstate/contracts/access/IERC173.sol";
import {OwnableStorage} from "@solidstate/contracts/access/OwnableStorage.sol";
import {IERC20} from "@solidstate/contracts/token/ERC20/IERC20.sol";
import {ERC1155EnumerableInternal, ERC1155EnumerableStorage, EnumerableSet} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol";
import {IWETH} from "@solidstate/contracts/utils/IWETH.sol";

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

import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";
import {ABDKMath64x64Token} from "../libraries/ABDKMath64x64Token.sol";
import {OptionMath} from "../libraries/OptionMath.sol";
import {IFeeDiscount} from "../staking/IFeeDiscount.sol";
import {IPoolEvents} from "./IPoolEvents.sol";
import {IPremiaMining} from "../mining/IPremiaMining.sol";
import {IVolatilitySurfaceOracle} from "../oracle/IVolatilitySurfaceOracle.sol";

/**
 * @title Premia option pool
 * @dev deployed standalone and referenced by PoolProxy
 */
contract PoolInternal is IPoolEvents, ERC1155EnumerableInternal {
    using ABDKMath64x64 for int128;
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;
    using PoolStorage for PoolStorage.Layout;

    address internal immutable WETH_ADDRESS;
    address internal immutable PREMIA_MINING_ADDRESS;
    address internal immutable FEE_RECEIVER_ADDRESS;
    address internal immutable FEE_DISCOUNT_ADDRESS;
    address internal immutable IVOL_ORACLE_ADDRESS;

    int128 internal immutable FEE_64x64;

    uint256 internal immutable UNDERLYING_FREE_LIQ_TOKEN_ID;
    uint256 internal immutable BASE_FREE_LIQ_TOKEN_ID;

    uint256 internal immutable UNDERLYING_RESERVED_LIQ_TOKEN_ID;
    uint256 internal immutable BASE_RESERVED_LIQ_TOKEN_ID;

    uint256 internal constant INVERSE_BASIS_POINT = 1e4;
    uint256 internal constant BATCHING_PERIOD = 260;

    // Minimum APY for capital locked up to underwrite options.
    // The quote will return a minimum price corresponding to this APY
    int128 internal constant MIN_APY_64x64 = 0x4ccccccccccccccd; // 0.3

    constructor(
        address ivolOracle,
        address weth,
        address premiaMining,
        address feeReceiver,
        address feeDiscountAddress,
        int128 fee64x64
    ) {
        IVOL_ORACLE_ADDRESS = ivolOracle;
        WETH_ADDRESS = weth;
        PREMIA_MINING_ADDRESS = premiaMining;
        FEE_RECEIVER_ADDRESS = feeReceiver;
        // PremiaFeeDiscount contract address
        FEE_DISCOUNT_ADDRESS = feeDiscountAddress;
        FEE_64x64 = fee64x64;

        UNDERLYING_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(
            PoolStorage.TokenType.UNDERLYING_FREE_LIQ,
            0,
            0
        );
        BASE_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(
            PoolStorage.TokenType.BASE_FREE_LIQ,
            0,
            0
        );

        UNDERLYING_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(
            PoolStorage.TokenType.UNDERLYING_RESERVED_LIQ,
            0,
            0
        );
        BASE_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(
            PoolStorage.TokenType.BASE_RESERVED_LIQ,
            0,
            0
        );
    }

    modifier onlyProtocolOwner() {
        require(
            msg.sender == IERC173(OwnableStorage.layout().owner).owner(),
            "Not protocol owner"
        );
        _;
    }

    function _getFeeDiscount(address feePayer)
        internal
        view
        returns (uint256 discount)
    {
        if (FEE_DISCOUNT_ADDRESS != address(0)) {
            discount = IFeeDiscount(FEE_DISCOUNT_ADDRESS).getDiscount(feePayer);
        }
    }

    function _getFeeWithDiscount(address feePayer, uint256 fee)
        internal
        view
        returns (uint256)
    {
        uint256 discount = _getFeeDiscount(feePayer);
        return fee - ((fee * discount) / INVERSE_BASIS_POINT);
    }

    function _withdrawFees(bool isCall) internal returns (uint256 amount) {
        uint256 tokenId = _getReservedLiquidityTokenId(isCall);
        amount = _balanceOf(FEE_RECEIVER_ADDRESS, tokenId);

        if (amount > 0) {
            _burn(FEE_RECEIVER_ADDRESS, tokenId, amount);
            emit FeeWithdrawal(isCall, amount);
        }
    }

    /**
     * @notice calculate price of option contract
     * @param args structured quote arguments
     * @return result quote result
     */
    function _quote(PoolStorage.QuoteArgsInternal memory args)
        internal
        view
        returns (PoolStorage.QuoteResultInternal memory result)
    {
        require(
            args.strike64x64 > 0 && args.spot64x64 > 0 && args.maturity > 0,
            "invalid args"
        );
        PoolStorage.Layout storage l = PoolStorage.layout();

        int128 contractSize64x64 = ABDKMath64x64Token.fromDecimals(
            args.contractSize,
            l.underlyingDecimals
        );
        bool isCall = args.isCall;

        (int128 adjustedCLevel64x64, int128 oldLiquidity64x64) = l
            .applyCLevelPendingDepositAdjustment(
                l.getDecayAdjustedCLevel64x64(isCall),
                l.totalFreeLiquiditySupply64x64(isCall),
                isCall
            );

        require(oldLiquidity64x64 > 0, "no liq");

        int128 timeToMaturity64x64 = ABDKMath64x64.divu(
            args.maturity - block.timestamp,
            365 days
        );

        int128 annualizedVolatility64x64 = IVolatilitySurfaceOracle(
            IVOL_ORACLE_ADDRESS
        ).getAnnualizedVolatility64x64(
                l.base,
                l.underlying,
                args.spot64x64,
                args.strike64x64,
                timeToMaturity64x64,
                isCall
            );

        require(annualizedVolatility64x64 > 0, "vol = 0");

        (
            int128 price64x64,
            int128 cLevel64x64,
            int128 slippageCoefficient64x64
        ) = OptionMath.quotePrice(
                OptionMath.QuoteArgs(
                    annualizedVolatility64x64.mul(annualizedVolatility64x64),
                    args.strike64x64,
                    args.spot64x64,
                    timeToMaturity64x64,
                    adjustedCLevel64x64,
                    oldLiquidity64x64,
                    oldLiquidity64x64.sub(contractSize64x64),
                    0x10000000000000000, // 64x64 fixed point representation of 1
                    MIN_APY_64x64,
                    isCall
                )
            );

        result.baseCost64x64 = isCall
            ? price64x64.mul(contractSize64x64).div(args.spot64x64)
            : price64x64.mul(contractSize64x64);
        result.feeCost64x64 = result.baseCost64x64.mul(FEE_64x64);
        result.cLevel64x64 = cLevel64x64;
        result.slippageCoefficient64x64 = slippageCoefficient64x64;

        int128 discount = ABDKMath64x64.divu(
            _getFeeDiscount(args.feePayer),
            INVERSE_BASIS_POINT
        );
        result.feeCost64x64 -= result.feeCost64x64.mul(discount);
    }

    /**
     * @notice burn corresponding long and short option tokens
     * @param account holder of tokens to annihilate
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param isCall true for call, false for put
     * @param contractSize quantity of option contract tokens to annihilate
     */
    function _annihilate(
        address account,
        uint64 maturity,
        int128 strike64x64,
        bool isCall,
        uint256 contractSize
    ) internal {
        uint256 longTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, true),
            maturity,
            strike64x64
        );
        uint256 shortTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, false),
            maturity,
            strike64x64
        );

        _burn(account, longTokenId, contractSize);
        _burn(account, shortTokenId, contractSize);

        emit Annihilate(shortTokenId, contractSize);
    }

    /**
     * @notice purchase call option
     * @param l storage layout struct
     * @param account recipient of purchased option
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param isCall true for call, false for put
     * @param contractSize size of option contract
     * @param newPrice64x64 64x64 fixed point representation of current spot price
     * @return baseCost quantity of tokens required to purchase long position
     * @return feeCost quantity of tokens required to pay fees
     */
    function _purchase(
        PoolStorage.Layout storage l,
        address account,
        uint64 maturity,
        int128 strike64x64,
        bool isCall,
        uint256 contractSize,
        int128 newPrice64x64
    ) internal returns (uint256 baseCost, uint256 feeCost) {
        require(maturity > block.timestamp, "expired");
        require(contractSize >= l.underlyingMinimum, "too small");

        {
            uint256 size = isCall
                ? contractSize
                : l.fromUnderlyingToBaseDecimals(
                    strike64x64.mulu(contractSize)
                );

            require(
                size <=
                    ERC1155EnumerableStorage.layout().totalSupply[
                        _getFreeLiquidityTokenId(isCall)
                    ] -
                        l.nextDeposits[isCall].totalPendingDeposits,
                "insuf liq"
            );
        }

        PoolStorage.QuoteResultInternal memory quote = _quote(
            PoolStorage.QuoteArgsInternal(
                account,
                maturity,
                strike64x64,
                newPrice64x64,
                contractSize,
                isCall
            )
        );

        baseCost = ABDKMath64x64Token.toDecimals(
            quote.baseCost64x64,
            l.getTokenDecimals(isCall)
        );

        feeCost = ABDKMath64x64Token.toDecimals(
            quote.feeCost64x64,
            l.getTokenDecimals(isCall)
        );

        uint256 longTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, true),
            maturity,
            strike64x64
        );

        uint256 shortTokenId = PoolStorage.formatTokenId(
            _getTokenType(isCall, false),
            maturity,
            strike64x64
        );

        // mint long option token for buyer
        _mint(account, longTokenId, contractSize);

        int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);
        // burn free liquidity tokens from other underwriters
        _mintShortTokenLoop(
            l,
            account,
            contractSize,
            baseCost,
            shortTokenId,
            isCall
        );
        int128 newLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);

        _setCLevel(l, oldLiquidity64x64, newLiquidity64x64, isCall);

        // mint reserved liquidity tokens for fee receiver
        _mint(
            FEE_RECEIVER_ADDRESS,
            _getReservedLiquidityTokenId(isCall),
            feeCost
        );

        emit Purchase(
            account,
            longTokenId,
            contractSize,
            baseCost,
            feeCost,
            newPrice64x64
        );
    }

    /**
     * @notice reassign short position to new underwriter
     * @param l storage layout struct
     * @param account holder of positions to be reassigned
     * @param maturity timestamp of option maturity
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param isCall true for call, false for put
     * @param contractSize quantity of option contract tokens to reassign
     * @param newPrice64x64 64x64 fixed point representation of current spot price
     * @return baseCost quantity of tokens required to reassign short position
     * @return feeCost quantity of tokens required to pay fees
     * @return amountOut quantity of liquidity freed
     */
    function _reassign(
        PoolStorage.Layout storage l,
        address account,
        uint64 maturity,
        int128 strike64x64,
        bool isCall,
        uint256 contractSize,
        int128 newPrice64x64
    )
        internal
        returns (
            uint256 baseCost,
            uint256 feeCost,
            uint256 amountOut
        )
    {
        (baseCost, feeCost) = _purchase(
            l,
            account,
            maturity,
            strike64x64,
            isCall,
            contractSize,
            newPrice64x64
        );

        _annihilate(account, maturity, strike64x64, isCall, contractSize);

        uint256 annihilateAmount = isCall
            ? contractSize
            : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));

        amountOut = annihilateAmount - baseCost - feeCost;
    }

    /**
     * @notice exercise call option on behalf of holder
     * @dev used for processing of expired options if passed holder is zero address
     * @param holder owner of long option tokens to exercise
     * @param longTokenId long option token id
     * @param contractSize quantity of tokens to exercise
     */
    function _exercise(
        address holder,
        uint256 longTokenId,
        uint256 contractSize
    ) internal {
        uint64 maturity;
        int128 strike64x64;
        bool isCall;

        bool onlyExpired = holder == address(0);

        {
            PoolStorage.TokenType tokenType;
            (tokenType, maturity, strike64x64) = PoolStorage.parseTokenId(
                longTokenId
            );
            require(
                tokenType == PoolStorage.TokenType.LONG_CALL ||
                    tokenType == PoolStorage.TokenType.LONG_PUT,
                "invalid type"
            );
            require(!onlyExpired || maturity < block.timestamp, "not expired");
            isCall = tokenType == PoolStorage.TokenType.LONG_CALL;
        }

        PoolStorage.Layout storage l = PoolStorage.layout();

        int128 spot64x64 = _update(l);

        if (maturity < block.timestamp) {
            spot64x64 = l.getPriceUpdateAfter(maturity);
        }

        require(
            onlyExpired ||
                (
                    isCall
                        ? (spot64x64 > strike64x64)
                        : (spot64x64 < strike64x64)
                ),
            "not ITM"
        );

        uint256 exerciseValue;
        // option has a non-zero exercise value
        if (isCall) {
            if (spot64x64 > strike64x64) {
                exerciseValue = spot64x64.sub(strike64x64).div(spot64x64).mulu(
                    contractSize
                );
            }
        } else {
            if (spot64x64 < strike64x64) {
                exerciseValue = l.fromUnderlyingToBaseDecimals(
                    strike64x64.sub(spot64x64).mulu(contractSize)
                );
            }
        }

        uint256 totalFee;

        if (onlyExpired) {
            totalFee += _burnLongTokenLoop(
                contractSize,
                exerciseValue,
                longTokenId,
                isCall
            );
        } else {
            // burn long option tokens from sender
            _burn(holder, longTokenId, contractSize);

            if (exerciseValue > 0) {
                uint256 fee = _getFeeWithDiscount(
                    holder,
                    FEE_64x64.mulu(exerciseValue)
                );
                totalFee += fee;

                _pushTo(holder, _getPoolToken(isCall), exerciseValue - fee);

                emit Exercise(
                    holder,
                    longTokenId,
                    contractSize,
                    exerciseValue,
                    fee
                );
            }
        }

        totalFee += _burnShortTokenLoop(
            contractSize,
            exerciseValue,
            PoolStorage.formatTokenId(
                _getTokenType(isCall, false),
                maturity,
                strike64x64
            ),
            isCall
        );

        _mint(
            FEE_RECEIVER_ADDRESS,
            _getReservedLiquidityTokenId(isCall),
            totalFee
        );
    }

    function _mintShortTokenLoop(
        PoolStorage.Layout storage l,
        address buyer,
        uint256 contractSize,
        uint256 premium,
        uint256 shortTokenId,
        bool isCall
    ) internal {
        uint256 freeLiqTokenId = _getFreeLiquidityTokenId(isCall);
        (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);

        uint256 toPay = isCall
            ? contractSize
            : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));

        while (toPay > 0) {
            address underwriter = l.liquidityQueueAscending[isCall][address(0)];
            uint256 balance = _balanceOf(underwriter, freeLiqTokenId);

            // If dust left, we remove underwriter and skip to next
            if (balance < _getMinimumAmount(l, isCall)) {
                l.removeUnderwriter(underwriter, isCall);
                continue;
            }

            if (!l.getReinvestmentStatus(underwriter, isCall)) {
                _burn(underwriter, freeLiqTokenId, balance);
                _mint(
                    underwriter,
                    _getReservedLiquidityTokenId(isCall),
                    balance
                );
                _subUserTVL(l, underwriter, isCall, balance);
                continue;
            }

            // amount of liquidity provided by underwriter, accounting for reinvested premium
            uint256 intervalContractSize = ((balance -
                l.pendingDeposits[underwriter][l.nextDeposits[isCall].eta][
                    isCall
                ]) * (toPay + premium)) / toPay;
            if (intervalContractSize == 0) continue;
            if (intervalContractSize > toPay) intervalContractSize = toPay;

            // amount of premium paid to underwriter
            uint256 intervalPremium = (premium * intervalContractSize) / toPay;
            premium -= intervalPremium;
            toPay -= intervalContractSize;
            _addUserTVL(l, underwriter, isCall, intervalPremium);

            // burn free liquidity tokens from underwriter
            _burn(
                underwriter,
                freeLiqTokenId,
                intervalContractSize - intervalPremium
            );

            if (isCall == false) {
                // For PUT, conversion to contract amount is done here (Prior to this line, it is token amount)
                intervalContractSize = l.fromBaseToUnderlyingDecimals(
                    strike64x64.inv().mulu(intervalContractSize)
                );
            }

            // mint short option tokens for underwriter
            // toPay == 0 ? contractSize : intervalContractSize : To prevent minting less than amount,
            // because of rounding (Can happen for put, because of fixed point precision)
            _mint(
                underwriter,
                shortTokenId,
                toPay == 0 ? contractSize : intervalContractSize
            );

            emit Underwrite(
                underwriter,
                buyer,
                shortTokenId,
                toPay == 0 ? contractSize : intervalContractSize,
                intervalPremium,
                false
            );

            contractSize -= intervalContractSize;
        }
    }

    function _burnLongTokenLoop(
        uint256 contractSize,
        uint256 exerciseValue,
        uint256 longTokenId,
        bool isCall
    ) internal returns (uint256 totalFee) {
        EnumerableSet.AddressSet storage holders = ERC1155EnumerableStorage
            .layout()
            .accountsByToken[longTokenId];

        while (contractSize > 0) {
            address longTokenHolder = holders.at(holders.length() - 1);

            uint256 intervalContractSize = _balanceOf(
                longTokenHolder,
                longTokenId
            );
            if (intervalContractSize > contractSize)
                intervalContractSize = contractSize;

            uint256 intervalExerciseValue;

            uint256 fee;
            if (exerciseValue > 0) {
                intervalExerciseValue =
                    (exerciseValue * intervalContractSize) /
                    contractSize;

                fee = _getFeeWithDiscount(
                    longTokenHolder,
                    FEE_64x64.mulu(intervalExerciseValue)
                );
                totalFee += fee;

                exerciseValue -= intervalExerciseValue;
                _pushTo(
                    longTokenHolder,
                    _getPoolToken(isCall),
                    intervalExerciseValue - fee
                );
            }

            contractSize -= intervalContractSize;

            emit Exercise(
                longTokenHolder,
                longTokenId,
                intervalContractSize,
                intervalExerciseValue - fee,
                fee
            );

            _burn(longTokenHolder, longTokenId, intervalContractSize);
        }
    }

    function _burnShortTokenLoop(
        uint256 contractSize,
        uint256 exerciseValue,
        uint256 shortTokenId,
        bool isCall
    ) internal returns (uint256 totalFee) {
        EnumerableSet.AddressSet storage underwriters = ERC1155EnumerableStorage
            .layout()
            .accountsByToken[shortTokenId];
        (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);

        while (contractSize > 0) {
            address underwriter = underwriters.at(underwriters.length() - 1);

            // amount of liquidity provided by underwriter
            uint256 intervalContractSize = _balanceOf(
                underwriter,
                shortTokenId
            );
            if (intervalContractSize > contractSize)
                intervalContractSize = contractSize;

            // amount of value claimed by buyer
            uint256 intervalExerciseValue = (exerciseValue *
                intervalContractSize) / contractSize;
            exerciseValue -= intervalExerciseValue;
            contractSize -= intervalContractSize;

            uint256 freeLiq = isCall
                ? intervalContractSize - intervalExerciseValue
                : PoolStorage.layout().fromUnderlyingToBaseDecimals(
                    strike64x64.mulu(intervalContractSize)
                ) - intervalExerciseValue;

            uint256 fee = _getFeeWithDiscount(
                underwriter,
                FEE_64x64.mulu(freeLiq)
            );
            totalFee += fee;

            uint256 tvlToSubtract = intervalExerciseValue;

            // mint free liquidity tokens for underwriter
            if (
                PoolStorage.layout().getReinvestmentStatus(underwriter, isCall)
            ) {
                _addToDepositQueue(underwriter, freeLiq - fee, isCall);
                tvlToSubtract += fee;
            } else {
                _mint(
                    underwriter,
                    _getReservedLiquidityTokenId(isCall),
                    freeLiq - fee
                );
                tvlToSubtract += freeLiq;
            }

            _subUserTVL(
                PoolStorage.layout(),
                underwriter,
                isCall,
                tvlToSubtract
            );

            // burn short option tokens from underwriter
            _burn(underwriter, shortTokenId, intervalContractSize);

            emit AssignExercise(
                underwriter,
                shortTokenId,
                freeLiq - fee,
                intervalContractSize,
                fee
            );
        }
    }

    function _addToDepositQueue(
        address account,
        uint256 amount,
        bool isCallPool
    ) internal {
        PoolStorage.Layout storage l = PoolStorage.layout();

        _mint(account, _getFreeLiquidityTokenId(isCallPool), amount);

        uint256 nextBatch = (block.timestamp / BATCHING_PERIOD) *
            BATCHING_PERIOD +
            BATCHING_PERIOD;
        l.pendingDeposits[account][nextBatch][isCallPool] += amount;

        PoolStorage.BatchData storage batchData = l.nextDeposits[isCallPool];
        batchData.totalPendingDeposits += amount;
        batchData.eta = nextBatch;
    }

    function _processPendingDeposits(PoolStorage.Layout storage l, bool isCall)
        internal
    {
        PoolStorage.BatchData storage data = l.nextDeposits[isCall];

        if (data.eta == 0 || block.timestamp < data.eta) return;

        int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);

        _setCLevel(
            l,
            oldLiquidity64x64,
            oldLiquidity64x64.add(
                ABDKMath64x64Token.fromDecimals(
                    data.totalPendingDeposits,
                    l.getTokenDecimals(isCall)
                )
            ),
            isCall
        );

        delete l.nextDeposits[isCall];
    }

    function _getFreeLiquidityTokenId(bool isCall)
        internal
        view
        returns (uint256 freeLiqTokenId)
    {
        freeLiqTokenId = isCall
            ? UNDERLYING_FREE_LIQ_TOKEN_ID
            : BASE_FREE_LIQ_TOKEN_ID;
    }

    function _getReservedLiquidityTokenId(bool isCall)
        internal
        view
        returns (uint256 reservedLiqTokenId)
    {
        reservedLiqTokenId = isCall
            ? UNDERLYING_RESERVED_LIQ_TOKEN_ID
            : BASE_RESERVED_LIQ_TOKEN_ID;
    }

    function _getPoolToken(bool isCall) internal view returns (address token) {
        token = isCall
            ? PoolStorage.layout().underlying
            : PoolStorage.layout().base;
    }

    function _getTokenType(bool isCall, bool isLong)
        internal
        pure
        returns (PoolStorage.TokenType tokenType)
    {
        if (isCall) {
            tokenType = isLong
                ? PoolStorage.TokenType.LONG_CALL
                : PoolStorage.TokenType.SHORT_CALL;
        } else {
            tokenType = isLong
                ? PoolStorage.TokenType.LONG_PUT
                : PoolStorage.TokenType.SHORT_PUT;
        }
    }

    function _getMinimumAmount(PoolStorage.Layout storage l, bool isCall)
        internal
        view
        returns (uint256 minimumAmount)
    {
        minimumAmount = isCall ? l.underlyingMinimum : l.baseMinimum;
    }

    function _getPoolCapAmount(PoolStorage.Layout storage l, bool isCall)
        internal
        view
        returns (uint256 poolCapAmount)
    {
        poolCapAmount = isCall ? l.underlyingPoolCap : l.basePoolCap;
    }

    function _setCLevel(
        PoolStorage.Layout storage l,
        int128 oldLiquidity64x64,
        int128 newLiquidity64x64,
        bool isCallPool
    ) internal {
        int128 oldCLevel64x64 = l.getDecayAdjustedCLevel64x64(isCallPool);

        int128 cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(
            oldCLevel64x64,
            oldLiquidity64x64,
            newLiquidity64x64,
            isCallPool
        );

        l.setCLevel(cLevel64x64, isCallPool);

        emit UpdateCLevel(
            isCallPool,
            cLevel64x64,
            oldLiquidity64x64,
            newLiquidity64x64
        );
    }

    /**
     * @notice calculate and store updated market state
     * @param l storage layout struct
     * @return newPrice64x64 64x64 fixed point representation of current spot price
     */
    function _update(PoolStorage.Layout storage l)
        internal
        returns (int128 newPrice64x64)
    {
        if (l.updatedAt == block.timestamp) {
            return (l.getPriceUpdate(block.timestamp));
        }

        newPrice64x64 = l.fetchPriceUpdate();

        if (l.getPriceUpdate(block.timestamp) == 0) {
            l.setPriceUpdate(block.timestamp, newPrice64x64);
        }

        l.updatedAt = block.timestamp;

        _processPendingDeposits(l, true);
        _processPendingDeposits(l, false);
    }

    /**
     * @notice transfer ERC20 tokens to message sender
     * @param token ERC20 token address
     * @param amount quantity of token to transfer
     */
    function _pushTo(
        address to,
        address token,
        uint256 amount
    ) internal {
        if (amount == 0) return;

        require(IERC20(token).transfer(to, amount), "ERC20 transfer failed");
    }

    /**
     * @notice transfer ERC20 tokens from message sender
     * @param from address from which tokens are pulled from
     * @param token ERC20 token address
     * @param amount quantity of token to transfer
     * @param skipWethDeposit if false, will not try to deposit weth from attach eth
     */
    function _pullFrom(
        address from,
        address token,
        uint256 amount,
        bool skipWethDeposit
    ) internal {
        if (!skipWethDeposit) {
            if (token == WETH_ADDRESS) {
                if (msg.value > 0) {
                    if (msg.value > amount) {
                        IWETH(WETH_ADDRESS).deposit{value: amount}();

                        (bool success, ) = payable(msg.sender).call{
                            value: msg.value - amount
                        }("");

                        require(success, "ETH refund failed");

                        amount = 0;
                    } else {
                        unchecked {
                            amount -= msg.value;
                        }

                        IWETH(WETH_ADDRESS).deposit{value: msg.value}();
                    }
                }
            } else {
                require(msg.value == 0, "not WETH deposit");
            }
        }

        if (amount > 0) {
            require(
                IERC20(token).transferFrom(from, address(this), amount),
                "ERC20 transfer failed"
            );
        }
    }

    function _mint(
        address account,
        uint256 tokenId,
        uint256 amount
    ) internal {
        _mint(account, tokenId, amount, "");
    }

    function _addUserTVL(
        PoolStorage.Layout storage l,
        address user,
        bool isCallPool,
        uint256 amount
    ) internal {
        uint256 userTVL = l.userTVL[user][isCallPool];
        uint256 totalTVL = l.totalTVL[isCallPool];

        IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(
            user,
            address(this),
            isCallPool,
            userTVL,
            userTVL + amount,
            totalTVL
        );

        l.userTVL[user][isCallPool] = userTVL + amount;
        l.totalTVL[isCallPool] = totalTVL + amount;
    }

    function _subUserTVL(
        PoolStorage.Layout storage l,
        address user,
        bool isCallPool,
        uint256 amount
    ) internal {
        uint256 userTVL = l.userTVL[user][isCallPool];
        uint256 totalTVL = l.totalTVL[isCallPool];

        IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(
            user,
            address(this),
            isCallPool,
            userTVL,
            userTVL - amount,
            totalTVL
        );
        l.userTVL[user][isCallPool] = userTVL - amount;
        l.totalTVL[isCallPool] = totalTVL - amount;
    }

    /**
     * @notice ERC1155 hook: track eligible underwriters
     * @param operator transaction sender
     * @param from token sender
     * @param to token receiver
     * @param ids token ids transferred
     * @param amounts token quantities transferred
     * @param data data payload
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual override {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);

        PoolStorage.Layout storage l = PoolStorage.layout();

        for (uint256 i; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            if (amount == 0) continue;

            if (from == address(0)) {
                l.tokenIds.add(id);
            }

            if (
                to == address(0) &&
                ERC1155EnumerableStorage.layout().totalSupply[id] == 0
            ) {
                l.tokenIds.remove(id);
            }

            // prevent transfer of free and reserved liquidity during waiting period

            if (
                id == UNDERLYING_FREE_LIQ_TOKEN_ID ||
                id == BASE_FREE_LIQ_TOKEN_ID ||
                id == UNDERLYING_RESERVED_LIQ_TOKEN_ID ||
                id == BASE_RESERVED_LIQ_TOKEN_ID
            ) {
                if (from != address(0) && to != address(0)) {
                    bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID ||
                        id == UNDERLYING_RESERVED_LIQ_TOKEN_ID;

                    require(
                        l.depositedAt[from][isCallPool] + (1 days) <
                            block.timestamp,
                        "liq lock 1d"
                    );
                }
            }

            if (
                id == UNDERLYING_FREE_LIQ_TOKEN_ID ||
                id == BASE_FREE_LIQ_TOKEN_ID
            ) {
                bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID;
                uint256 minimum = _getMinimumAmount(l, isCallPool);

                if (from != address(0)) {
                    uint256 balance = _balanceOf(from, id);

                    if (balance > minimum && balance <= amount + minimum) {
                        require(
                            balance -
                                l.pendingDeposits[from][
                                    l.nextDeposits[isCallPool].eta
                                ][isCallPool] >=
                                amount,
                            "Insuf balance"
                        );
                        l.removeUnderwriter(from, isCallPool);
                    }

                    if (to != address(0)) {
                        _subUserTVL(l, from, isCallPool, amounts[i]);
                        _addUserTVL(l, to, isCallPool, amounts[i]);
                    }
                }

                if (to != address(0)) {
                    uint256 balance = _balanceOf(to, id);

                    if (balance <= minimum && balance + amount > minimum) {
                        l.addUnderwriter(to, isCallPool);
                    }
                }
            }

            // Update userTVL on SHORT options transfers
            (
                PoolStorage.TokenType tokenType,
                ,
                int128 strike64x64
            ) = PoolStorage.parseTokenId(id);

            if (
                (from != address(0) && to != address(0)) &&
                (tokenType == PoolStorage.TokenType.SHORT_CALL ||
                    tokenType == PoolStorage.TokenType.SHORT_PUT)
            ) {
                bool isCall = tokenType == PoolStorage.TokenType.SHORT_CALL;
                uint256 collateral = isCall
                    ? amount
                    : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(amount));

                _subUserTVL(l, from, isCall, collateral);
                _addUserTVL(l, to, isCall, collateral);
            }
        }
    }
}

File 16 of 38 : AggregatorInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorInterface {
  function latestAnswer()
    external
    view
    returns (
      int256
    );
  
  function latestTimestamp()
    external
    view
    returns (
      uint256
    );

  function latestRound()
    external
    view
    returns (
      uint256
    );

  function getAnswer(
    uint256 roundId
  )
    external
    view
    returns (
      int256
    );

  function getTimestamp(
    uint256 roundId
  )
    external
    view
    returns (
      uint256
    );

  event AnswerUpdated(
    int256 indexed current,
    uint256 indexed roundId,
    uint256 updatedAt
  );

  event NewRound(
    uint256 indexed roundId,
    address indexed startedBy,
    uint256 startedAt
  );
}

File 17 of 38 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {

  function decimals()
    external
    view
    returns (
      uint8
    );

  function description()
    external
    view
    returns (
      string memory
    );

  function version()
    external
    view
    returns (
      uint256
    );

  // getRoundData and latestRoundData should both raise "No data present"
  // if they do not have data to report, instead of returning unset values
  // which could be misinterpreted as actual reported values.
  function getRoundData(
    uint80 _roundId
  )
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

}

File 18 of 38 : ERC1155EnumerableStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { EnumerableSet } from '../../../utils/EnumerableSet.sol';

library ERC1155EnumerableStorage {
    struct Layout {
        mapping(uint256 => uint256) totalSupply;
        mapping(uint256 => EnumerableSet.AddressSet) accountsByToken;
        mapping(address => EnumerableSet.UintSet) tokensByAccount;
    }

    bytes32 internal constant STORAGE_SLOT =
        keccak256('solidstate.contracts.storage.ERC1155Enumerable');

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }
}

File 19 of 38 : OptionMath.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";

library OptionMath {
    using ABDKMath64x64 for int128;

    struct QuoteArgs {
        int128 varianceAnnualized64x64; // 64x64 fixed point representation of annualized variance
        int128 strike64x64; // 64x64 fixed point representation of strike price
        int128 spot64x64; // 64x64 fixed point representation of spot price
        int128 timeToMaturity64x64; // 64x64 fixed point representation of duration of option contract (in years)
        int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level of Pool before purchase
        int128 oldPoolState; // 64x64 fixed point representation of current state of the pool
        int128 newPoolState; // 64x64 fixed point representation of state of the pool after trade
        int128 steepness64x64; // 64x64 fixed point representation of Pool state delta multiplier
        int128 minAPY64x64; // 64x64 fixed point representation of minimum APY for capital locked up to underwrite options
        bool isCall; // whether to price "call" or "put" option
    }

    struct CalculateCLevelDecayArgs {
        int128 timeIntervalsElapsed64x64; // 64x64 fixed point representation of quantity of discrete arbitrary intervals elapsed since last update
        int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level prior to accounting for decay
        int128 utilization64x64; // 64x64 fixed point representation of pool capital utilization rate
        int128 utilizationLowerBound64x64;
        int128 utilizationUpperBound64x64;
        int128 cLevelLowerBound64x64;
        int128 cLevelUpperBound64x64;
        int128 cConvergenceULowerBound64x64;
        int128 cConvergenceUUpperBound64x64;
    }

    // 64x64 fixed point integer constants
    int128 internal constant ONE_64x64 = 0x10000000000000000;
    int128 internal constant THREE_64x64 = 0x30000000000000000;

    // 64x64 fixed point constants used in Choudhury’s approximation of the Black-Scholes CDF
    int128 private constant CDF_CONST_0 = 0x09109f285df452394; // 2260 / 3989
    int128 private constant CDF_CONST_1 = 0x19abac0ea1da65036; // 6400 / 3989
    int128 private constant CDF_CONST_2 = 0x0d3c84b78b749bd6b; // 3300 / 3989

    /**
     * @notice recalculate C-Level based on change in liquidity
     * @param initialCLevel64x64 64x64 fixed point representation of C-Level of Pool before update
     * @param oldPoolState64x64 64x64 fixed point representation of liquidity in pool before update
     * @param newPoolState64x64 64x64 fixed point representation of liquidity in pool after update
     * @param steepness64x64 64x64 fixed point representation of steepness coefficient
     * @return 64x64 fixed point representation of new C-Level
     */
    function calculateCLevel(
        int128 initialCLevel64x64,
        int128 oldPoolState64x64,
        int128 newPoolState64x64,
        int128 steepness64x64
    ) external pure returns (int128) {
        return
            newPoolState64x64
                .sub(oldPoolState64x64)
                .div(
                    oldPoolState64x64 > newPoolState64x64
                        ? oldPoolState64x64
                        : newPoolState64x64
                )
                .mul(steepness64x64)
                .neg()
                .exp()
                .mul(initialCLevel64x64);
    }

    /**
     * @notice calculate the price of an option using the Premia Finance model
     * @param args arguments of quotePrice
     * @return premiaPrice64x64 64x64 fixed point representation of Premia option price
     * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after purchase
     */
    function quotePrice(QuoteArgs memory args)
        external
        pure
        returns (
            int128 premiaPrice64x64,
            int128 cLevel64x64,
            int128 slippageCoefficient64x64
        )
    {
        int128 deltaPoolState64x64 = args
            .newPoolState
            .sub(args.oldPoolState)
            .div(args.oldPoolState)
            .mul(args.steepness64x64);
        int128 tradingDelta64x64 = deltaPoolState64x64.neg().exp();

        int128 blackScholesPrice64x64 = _blackScholesPrice(
            args.varianceAnnualized64x64,
            args.strike64x64,
            args.spot64x64,
            args.timeToMaturity64x64,
            args.isCall
        );

        cLevel64x64 = tradingDelta64x64.mul(args.oldCLevel64x64);
        slippageCoefficient64x64 = ONE_64x64.sub(tradingDelta64x64).div(
            deltaPoolState64x64
        );

        premiaPrice64x64 = blackScholesPrice64x64.mul(cLevel64x64).mul(
            slippageCoefficient64x64
        );

        int128 intrinsicValue64x64;

        if (args.isCall && args.strike64x64 < args.spot64x64) {
            intrinsicValue64x64 = args.spot64x64.sub(args.strike64x64);
        } else if (!args.isCall && args.strike64x64 > args.spot64x64) {
            intrinsicValue64x64 = args.strike64x64.sub(args.spot64x64);
        }

        int128 collateralValue64x64 = args.isCall
            ? args.spot64x64
            : args.strike64x64;

        int128 minPrice64x64 = intrinsicValue64x64.add(
            collateralValue64x64.mul(args.minAPY64x64).mul(
                args.timeToMaturity64x64
            )
        );

        if (minPrice64x64 > premiaPrice64x64) {
            premiaPrice64x64 = minPrice64x64;
        }
    }

    /**
     * @notice calculate the decay of C-Level based on heat diffusion function
     * @param args structured CalculateCLevelDecayArgs
     * @return cLevelDecayed64x64 C-Level after accounting for decay
     */
    function calculateCLevelDecay(CalculateCLevelDecayArgs memory args)
        external
        pure
        returns (int128 cLevelDecayed64x64)
    {
        int128 convFHighU64x64 = (args.utilization64x64 >=
            args.utilizationUpperBound64x64 &&
            args.oldCLevel64x64 <= args.cLevelLowerBound64x64)
            ? ONE_64x64
            : int128(0);

        int128 convFLowU64x64 = (args.utilization64x64 <=
            args.utilizationLowerBound64x64 &&
            args.oldCLevel64x64 >= args.cLevelUpperBound64x64)
            ? ONE_64x64
            : int128(0);

        cLevelDecayed64x64 = args
            .oldCLevel64x64
            .sub(args.cConvergenceULowerBound64x64.mul(convFLowU64x64))
            .sub(args.cConvergenceUUpperBound64x64.mul(convFHighU64x64))
            .mul(
                convFLowU64x64
                    .mul(ONE_64x64.sub(args.utilization64x64))
                    .add(convFHighU64x64.mul(args.utilization64x64))
                    .mul(args.timeIntervalsElapsed64x64)
                    .neg()
                    .exp()
            )
            .add(
                args.cConvergenceULowerBound64x64.mul(convFLowU64x64).add(
                    args.cConvergenceUUpperBound64x64.mul(convFHighU64x64)
                )
            );
    }

    /**
     * @notice calculate the exponential decay coefficient for a given interval
     * @param oldTimestamp timestamp of previous update
     * @param newTimestamp current timestamp
     * @return 64x64 fixed point representation of exponential decay coefficient
     */
    function _decay(uint256 oldTimestamp, uint256 newTimestamp)
        internal
        pure
        returns (int128)
    {
        return
            ONE_64x64.sub(
                (-ABDKMath64x64.divu(newTimestamp - oldTimestamp, 7 days)).exp()
            );
    }

    /**
     * @notice calculate Choudhury’s approximation of the Black-Scholes CDF
     * @param input64x64 64x64 fixed point representation of random variable
     * @return 64x64 fixed point representation of the approximated CDF of x
     */
    function _N(int128 input64x64) internal pure returns (int128) {
        // squaring via mul is cheaper than via pow
        int128 inputSquared64x64 = input64x64.mul(input64x64);

        int128 value64x64 = (-inputSquared64x64 >> 1).exp().div(
            CDF_CONST_0.add(CDF_CONST_1.mul(input64x64.abs())).add(
                CDF_CONST_2.mul(inputSquared64x64.add(THREE_64x64).sqrt())
            )
        );

        return input64x64 > 0 ? ONE_64x64.sub(value64x64) : value64x64;
    }

    /**
     * @notice calculate the price of an option using the Black-Scholes model
     * @param varianceAnnualized64x64 64x64 fixed point representation of annualized variance
     * @param strike64x64 64x64 fixed point representation of strike price
     * @param spot64x64 64x64 fixed point representation of spot price
     * @param timeToMaturity64x64 64x64 fixed point representation of duration of option contract (in years)
     * @param isCall whether to price "call" or "put" option
     * @return 64x64 fixed point representation of Black-Scholes option price
     */
    function _blackScholesPrice(
        int128 varianceAnnualized64x64,
        int128 strike64x64,
        int128 spot64x64,
        int128 timeToMaturity64x64,
        bool isCall
    ) internal pure returns (int128) {
        int128 cumulativeVariance64x64 = timeToMaturity64x64.mul(
            varianceAnnualized64x64
        );
        int128 cumulativeVarianceSqrt64x64 = cumulativeVariance64x64.sqrt();

        int128 d1_64x64 = spot64x64
            .div(strike64x64)
            .ln()
            .add(cumulativeVariance64x64 >> 1)
            .div(cumulativeVarianceSqrt64x64);
        int128 d2_64x64 = d1_64x64.sub(cumulativeVarianceSqrt64x64);

        if (isCall) {
            return
                spot64x64.mul(_N(d1_64x64)).sub(strike64x64.mul(_N(d2_64x64)));
        } else {
            return
                -spot64x64.mul(_N(-d1_64x64)).sub(
                    strike64x64.mul(_N(-d2_64x64))
                );
        }
    }
}

File 20 of 38 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC20 metadata interface
 */
interface IERC20Metadata {
    /**
     * @notice return token name
     * @return token name
     */
    function name() external view returns (string memory);

    /**
     * @notice return token symbol
     * @return token symbol
     */
    function symbol() external view returns (string memory);

    /**
     * @notice return token decimals, generally used only for display purposes
     * @return token decimals
     */
    function decimals() external view returns (uint8);
}

File 21 of 38 : IERC173.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Contract ownership standard interface
 * @dev see https://eips.ethereum.org/EIPS/eip-173
 */
interface IERC173 {
    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    /**
     * @notice get the ERC173 contract owner
     * @return conract owner
     */
    function owner() external view returns (address);

    /**
     * @notice transfer contract ownership to new account
     * @param account address of new owner
     */
    function transferOwnership(address account) external;
}

File 22 of 38 : OwnableStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library OwnableStorage {
    struct Layout {
        address owner;
    }

    bytes32 internal constant STORAGE_SLOT =
        keccak256('solidstate.contracts.storage.Ownable');

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }

    function setOwner(Layout storage l, address owner) internal {
        l.owner = owner;
    }
}

File 23 of 38 : ERC1155Enumerable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { EnumerableSet } from '../../../utils/EnumerableSet.sol';
import { ERC1155Base, ERC1155BaseInternal } from '../base/ERC1155Base.sol';
import { IERC1155Enumerable } from './IERC1155Enumerable.sol';
import { ERC1155EnumerableInternal, ERC1155EnumerableStorage } from './ERC1155EnumerableInternal.sol';

/**
 * @title ERC1155 implementation including enumerable and aggregate functions
 */
abstract contract ERC1155Enumerable is
    IERC1155Enumerable,
    ERC1155Base,
    ERC1155EnumerableInternal
{
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;

    /**
     * @inheritdoc IERC1155Enumerable
     */
    function totalSupply(uint256 id)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return ERC1155EnumerableStorage.layout().totalSupply[id];
    }

    /**
     * @inheritdoc IERC1155Enumerable
     */
    function totalHolders(uint256 id)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return ERC1155EnumerableStorage.layout().accountsByToken[id].length();
    }

    /**
     * @inheritdoc IERC1155Enumerable
     */
    function accountsByToken(uint256 id)
        public
        view
        virtual
        override
        returns (address[] memory)
    {
        EnumerableSet.AddressSet storage accounts = ERC1155EnumerableStorage
            .layout()
            .accountsByToken[id];

        address[] memory addresses = new address[](accounts.length());

        for (uint256 i; i < accounts.length(); i++) {
            addresses[i] = accounts.at(i);
        }

        return addresses;
    }

    /**
     * @inheritdoc IERC1155Enumerable
     */
    function tokensByAccount(address account)
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        EnumerableSet.UintSet storage tokens = ERC1155EnumerableStorage
            .layout()
            .tokensByAccount[account];

        uint256[] memory ids = new uint256[](tokens.length());

        for (uint256 i; i < tokens.length(); i++) {
            ids[i] = tokens.at(i);
        }

        return ids;
    }

    /**
     * @notice ERC1155 hook: update aggregate values
     * @inheritdoc ERC1155EnumerableInternal
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    )
        internal
        virtual
        override(ERC1155BaseInternal, ERC1155EnumerableInternal)
    {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
    }
}

File 24 of 38 : IFeeDiscount.sol
// SPDX-License-Identifier: LGPL-3.0-or-later

pragma solidity ^0.8.0;

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

interface IFeeDiscount {
    event Staked(
        address indexed user,
        uint256 amount,
        uint256 stakePeriod,
        uint256 lockedUntil
    );
    event Unstaked(address indexed user, uint256 amount);

    struct StakeLevel {
        uint256 amount; // Amount to stake
        uint256 discount; // Discount when amount is reached
    }

    /**
     * @notice Stake using IERC2612 permit
     * @param amount The amount of xPremia to stake
     * @param period The lockup period (in seconds)
     * @param deadline Deadline after which permit will fail
     * @param v V
     * @param r R
     * @param s S
     */
    function stakeWithPermit(
        uint256 amount,
        uint256 period,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Lockup xPremia for protocol fee discounts
     *          Longer period of locking will apply a multiplier on the amount staked, in the fee discount calculation
     * @param amount The amount of xPremia to stake
     * @param period The lockup period (in seconds)
     */
    function stake(uint256 amount, uint256 period) external;

    /**
     * @notice Unstake xPremia (If lockup period has ended)
     * @param amount The amount of xPremia to unstake
     */
    function unstake(uint256 amount) external;

    //////////
    // View //
    //////////

    /**
     * Calculate the stake amount of a user, after applying the bonus from the lockup period chosen
     * @param user The user from which to query the stake amount
     * @return The user stake amount after applying the bonus
     */
    function getStakeAmountWithBonus(address user)
        external
        view
        returns (uint256);

    /**
     * @notice Calculate the % of fee discount for user, based on his stake
     * @param user The _user for which the discount is for
     * @return Percentage of protocol fee discount (in basis point)
     *         Ex : 1000 = 10% fee discount
     */
    function getDiscount(address user) external view returns (uint256);

    /**
     * @notice Get stake levels
     * @return Stake levels
     *         Ex : 2500 = -25%
     */
    function getStakeLevels() external returns (StakeLevel[] memory);

    /**
     * @notice Get stake period multiplier
     * @param period The duration (in seconds) for which tokens are locked
     * @return The multiplier for this staking period
     *         Ex : 20000 = x2
     */
    function getStakePeriodMultiplier(uint256 period)
        external
        returns (uint256);

    /**
     * @notice Get staking infos of a user
     * @param user The user address for which to get staking infos
     * @return The staking infos of the user
     */
    function getUserInfo(address user)
        external
        view
        returns (FeeDiscountStorage.UserInfo memory);
}

File 25 of 38 : IPoolEvents.sol
// SPDX-License-Identifier: LGPL-3.0-or-later

pragma solidity ^0.8.0;

interface IPoolEvents {
    event Purchase(
        address indexed user,
        uint256 longTokenId,
        uint256 contractSize,
        uint256 baseCost,
        uint256 feeCost,
        int128 spot64x64
    );

    event Exercise(
        address indexed user,
        uint256 longTokenId,
        uint256 contractSize,
        uint256 exerciseValue,
        uint256 fee
    );

    event Underwrite(
        address indexed underwriter,
        address indexed longReceiver,
        uint256 shortTokenId,
        uint256 intervalContractSize,
        uint256 intervalPremium,
        bool isManualUnderwrite
    );

    event AssignExercise(
        address indexed underwriter,
        uint256 shortTokenId,
        uint256 freedAmount,
        uint256 intervalContractSize,
        uint256 fee
    );

    event Deposit(address indexed user, bool isCallPool, uint256 amount);

    event Withdrawal(
        address indexed user,
        bool isCallPool,
        uint256 depositedAt,
        uint256 amount
    );

    event FeeWithdrawal(bool indexed isCallPool, uint256 amount);

    event Annihilate(uint256 shortTokenId, uint256 amount);

    event UpdateCLevel(
        bool indexed isCall,
        int128 cLevel64x64,
        int128 oldLiquidity64x64,
        int128 newLiquidity64x64
    );

    event UpdateSteepness(int128 steepness64x64, bool isCallPool);
}

File 26 of 38 : IPremiaMining.sol
// SPDX-License-Identifier: LGPL-3.0-or-later

pragma solidity ^0.8.0;

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

interface IPremiaMining {
    function addPremiaRewards(uint256 _amount) external;

    function premiaRewardsAvailable() external view returns (uint256);

    function getTotalAllocationPoints() external view returns (uint256);

    function getPoolInfo(address pool, bool isCallPool)
        external
        view
        returns (PremiaMiningStorage.PoolInfo memory);

    function getPremiaPerYear() external view returns (uint256);

    function addPool(address _pool, uint256 _allocPoints) external;

    function setPoolAllocPoints(
        address[] memory _pools,
        uint256[] memory _allocPoints
    ) external;

    function pendingPremia(
        address _pool,
        bool _isCallPool,
        address _user
    ) external view returns (uint256);

    function updatePool(
        address _pool,
        bool _isCallPool,
        uint256 _totalTVL
    ) external;

    function allocatePending(
        address _user,
        address _pool,
        bool _isCallPool,
        uint256 _userTVLOld,
        uint256 _userTVLNew,
        uint256 _totalTVL
    ) external;

    function claim(
        address _user,
        address _pool,
        bool _isCallPool,
        uint256 _userTVLOld,
        uint256 _userTVLNew,
        uint256 _totalTVL
    ) external;
}

File 27 of 38 : IVolatilitySurfaceOracle.sol
// SPDX-License-Identifier: LGPL-3.0-or-later

pragma solidity ^0.8.0;

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

interface IVolatilitySurfaceOracle {
    function getWhitelistedRelayers() external view returns (address[] memory);

    function getVolatilitySurface(address baseToken, address underlyingToken)
        external
        view
        returns (VolatilitySurfaceOracleStorage.Update memory);

    function getVolatilitySurfaceCoefficientsUnpacked(
        address baseToken,
        address underlyingToken,
        bool isCall
    ) external view returns (int256[] memory);

    function getTimeToMaturity64x64(uint64 maturity)
        external
        view
        returns (int128);

    function getAnnualizedVolatility64x64(
        address baseToken,
        address underlyingToken,
        int128 spot64x64,
        int128 strike64x64,
        int128 timeToMaturity64x64,
        bool isCall
    ) external view returns (int128);

    function getBlackScholesPrice64x64(
        address baseToken,
        address underlyingToken,
        int128 strike64x64,
        int128 spot64x64,
        int128 timeToMaturity64x64,
        bool isCall
    ) external view returns (int128);

    function getBlackScholesPrice(
        address baseToken,
        address underlyingToken,
        int128 strike64x64,
        int128 spot64x64,
        int128 timeToMaturity64x64,
        bool isCall
    ) external view returns (uint256);
}

File 28 of 38 : ERC1155Base.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC1155 } from '../IERC1155.sol';
import { IERC1155Receiver } from '../IERC1155Receiver.sol';
import { ERC1155BaseInternal, ERC1155BaseStorage } from './ERC1155BaseInternal.sol';

/**
 * @title Base ERC1155 contract
 * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
 */
abstract contract ERC1155Base is IERC1155, ERC1155BaseInternal {
    /**
     * @inheritdoc IERC1155
     */
    function balanceOf(address account, uint256 id)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _balanceOf(account, id);
    }

    /**
     * @inheritdoc IERC1155
     */
    function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        require(
            accounts.length == ids.length,
            'ERC1155: accounts and ids length mismatch'
        );

        mapping(uint256 => mapping(address => uint256))
            storage balances = ERC1155BaseStorage.layout().balances;

        uint256[] memory batchBalances = new uint256[](accounts.length);

        unchecked {
            for (uint256 i; i < accounts.length; i++) {
                require(
                    accounts[i] != address(0),
                    'ERC1155: batch balance query for the zero address'
                );
                batchBalances[i] = balances[ids[i]][accounts[i]];
            }
        }

        return batchBalances;
    }

    /**
     * @inheritdoc IERC1155
     */
    function isApprovedForAll(address account, address operator)
        public
        view
        virtual
        override
        returns (bool)
    {
        return ERC1155BaseStorage.layout().operatorApprovals[account][operator];
    }

    /**
     * @inheritdoc IERC1155
     */
    function setApprovalForAll(address operator, bool status)
        public
        virtual
        override
    {
        require(
            msg.sender != operator,
            'ERC1155: setting approval status for self'
        );
        ERC1155BaseStorage.layout().operatorApprovals[msg.sender][
            operator
        ] = status;
        emit ApprovalForAll(msg.sender, operator, status);
    }

    /**
     * @inheritdoc IERC1155
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == msg.sender || isApprovedForAll(from, msg.sender),
            'ERC1155: caller is not owner nor approved'
        );
        _safeTransfer(msg.sender, from, to, id, amount, data);
    }

    /**
     * @inheritdoc IERC1155
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == msg.sender || isApprovedForAll(from, msg.sender),
            'ERC1155: caller is not owner nor approved'
        );
        _safeTransferBatch(msg.sender, from, to, ids, amounts, data);
    }
}

File 29 of 38 : IERC1155Enumerable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC1155 enumerable and aggregate function interface
 */
interface IERC1155Enumerable {
    /**
     * @notice query total minted supply of given token
     * @param id token id to query
     * @return token supply
     */
    function totalSupply(uint256 id) external view returns (uint256);

    /**
     * @notice query total number of holders for given token
     * @param id token id to query
     * @return quantity of holders
     */
    function totalHolders(uint256 id) external view returns (uint256);

    /**
     * @notice query holders of given token
     * @param id token id to query
     * @return list of holder addresses
     */
    function accountsByToken(uint256 id)
        external
        view
        returns (address[] memory);

    /**
     * @notice query tokens held by given address
     * @param account address to query
     * @return list of token ids
     */
    function tokensByAccount(address account)
        external
        view
        returns (uint256[] memory);
}

File 30 of 38 : ERC1155EnumerableInternal.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { EnumerableSet } from '../../../utils/EnumerableSet.sol';
import { ERC1155BaseInternal, ERC1155BaseStorage } from '../base/ERC1155BaseInternal.sol';
import { ERC1155EnumerableStorage } from './ERC1155EnumerableStorage.sol';

/**
 * @title ERC1155Enumerable internal functions
 */
abstract contract ERC1155EnumerableInternal is ERC1155BaseInternal {
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;

    /**
     * @notice ERC1155 hook: update aggregate values
     * @inheritdoc ERC1155BaseInternal
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual override {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);

        if (from != to) {
            ERC1155EnumerableStorage.Layout storage l = ERC1155EnumerableStorage
                .layout();
            mapping(uint256 => EnumerableSet.AddressSet)
                storage tokenAccounts = l.accountsByToken;
            EnumerableSet.UintSet storage fromTokens = l.tokensByAccount[from];
            EnumerableSet.UintSet storage toTokens = l.tokensByAccount[to];

            for (uint256 i; i < ids.length; i++) {
                uint256 amount = amounts[i];

                if (amount > 0) {
                    uint256 id = ids[i];

                    if (from == address(0)) {
                        l.totalSupply[id] += amount;
                    } else if (_balanceOf(from, id) == amount) {
                        tokenAccounts[id].remove(from);
                        fromTokens.remove(id);
                    }

                    if (to == address(0)) {
                        l.totalSupply[id] -= amount;
                    } else if (_balanceOf(to, id) == 0) {
                        tokenAccounts[id].add(to);
                        toTokens.add(id);
                    }
                }
            }
        }
    }
}

File 31 of 38 : IERC1155.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC1155Internal } from './IERC1155Internal.sol';
import { IERC165 } from '../../introspection/IERC165.sol';

/**
 * @notice ERC1155 interface
 * @dev see https://github.com/ethereum/EIPs/issues/1155
 */
interface IERC1155 is IERC1155Internal, IERC165 {
    /**
     * @notice query the balance of given token held by given address
     * @param account address to query
     * @param id token to query
     * @return token balance
     */
    function balanceOf(address account, uint256 id)
        external
        view
        returns (uint256);

    /**
     * @notice query the balances of given tokens held by given addresses
     * @param accounts addresss to query
     * @param ids tokens to query
     * @return token balances
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @notice query approval status of given operator with respect to given address
     * @param account address to query for approval granted
     * @param operator address to query for approval received
     * @return whether operator is approved to spend tokens held by account
     */
    function isApprovedForAll(address account, address operator)
        external
        view
        returns (bool);

    /**
     * @notice grant approval to or revoke approval from given operator to spend held tokens
     * @param operator address whose approval status to update
     * @param status whether operator should be considered approved
     */
    function setApprovalForAll(address operator, bool status) external;

    /**
     * @notice transfer tokens between given addresses, checking for ERC1155Receiver implementation if applicable
     * @param from sender of tokens
     * @param to receiver of tokens
     * @param id token ID
     * @param amount quantity of tokens to transfer
     * @param data data payload
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @notice transfer batch of tokens between given addresses, checking for ERC1155Receiver implementation if applicable
     * @param from sender of tokens
     * @param to receiver of tokens
     * @param ids list of token IDs
     * @param amounts list of quantities of tokens to transfer
     * @param data data payload
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

File 32 of 38 : IERC1155Receiver.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC165 } from '../../introspection/IERC165.sol';

/**
 * @title ERC1155 transfer receiver interface
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @notice validate receipt of ERC1155 transfer
     * @param operator executor of transfer
     * @param from sender of tokens
     * @param id token ID received
     * @param value quantity of tokens received
     * @param data data payload
     * @return function's own selector if transfer is accepted
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @notice validate receipt of ERC1155 batch transfer
     * @param operator executor of transfer
     * @param from sender of tokens
     * @param ids token IDs received
     * @param values quantities of tokens received
     * @param data data payload
     * @return function's own selector if transfer is accepted
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

File 33 of 38 : ERC1155BaseInternal.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { AddressUtils } from '../../../utils/AddressUtils.sol';
import { IERC1155Internal } from '../IERC1155Internal.sol';
import { IERC1155Receiver } from '../IERC1155Receiver.sol';
import { ERC1155BaseStorage } from './ERC1155BaseStorage.sol';

/**
 * @title Base ERC1155 internal functions
 * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
 */
abstract contract ERC1155BaseInternal is IERC1155Internal {
    using AddressUtils for address;

    /**
     * @notice query the balance of given token held by given address
     * @param account address to query
     * @param id token to query
     * @return token balance
     */
    function _balanceOf(address account, uint256 id)
        internal
        view
        virtual
        returns (uint256)
    {
        require(
            account != address(0),
            'ERC1155: balance query for the zero address'
        );
        return ERC1155BaseStorage.layout().balances[id][account];
    }

    /**
     * @notice mint given quantity of tokens for given address
     * @dev ERC1155Receiver implementation is not checked
     * @param account beneficiary of minting
     * @param id token ID
     * @param amount quantity of tokens to mint
     * @param data data payload
     */
    function _mint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(account != address(0), 'ERC1155: mint to the zero address');

        _beforeTokenTransfer(
            msg.sender,
            address(0),
            account,
            _asSingletonArray(id),
            _asSingletonArray(amount),
            data
        );

        mapping(address => uint256) storage balances = ERC1155BaseStorage
            .layout()
            .balances[id];
        balances[account] += amount;

        emit TransferSingle(msg.sender, address(0), account, id, amount);
    }

    /**
     * @notice mint given quantity of tokens for given address
     * @param account beneficiary of minting
     * @param id token ID
     * @param amount quantity of tokens to mint
     * @param data data payload
     */
    function _safeMint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        _mint(account, id, amount, data);

        _doSafeTransferAcceptanceCheck(
            msg.sender,
            address(0),
            account,
            id,
            amount,
            data
        );
    }

    /**
     * @notice mint batch of tokens for given address
     * @dev ERC1155Receiver implementation is not checked
     * @param account beneficiary of minting
     * @param ids list of token IDs
     * @param amounts list of quantities of tokens to mint
     * @param data data payload
     */
    function _mintBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(account != address(0), 'ERC1155: mint to the zero address');
        require(
            ids.length == amounts.length,
            'ERC1155: ids and amounts length mismatch'
        );

        _beforeTokenTransfer(
            msg.sender,
            address(0),
            account,
            ids,
            amounts,
            data
        );

        mapping(uint256 => mapping(address => uint256))
            storage balances = ERC1155BaseStorage.layout().balances;

        for (uint256 i; i < ids.length; i++) {
            balances[ids[i]][account] += amounts[i];
        }

        emit TransferBatch(msg.sender, address(0), account, ids, amounts);
    }

    /**
     * @notice mint batch of tokens for given address
     * @param account beneficiary of minting
     * @param ids list of token IDs
     * @param amounts list of quantities of tokens to mint
     * @param data data payload
     */
    function _safeMintBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        _mintBatch(account, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(
            msg.sender,
            address(0),
            account,
            ids,
            amounts,
            data
        );
    }

    /**
     * @notice burn given quantity of tokens held by given address
     * @param account holder of tokens to burn
     * @param id token ID
     * @param amount quantity of tokens to burn
     */
    function _burn(
        address account,
        uint256 id,
        uint256 amount
    ) internal virtual {
        require(account != address(0), 'ERC1155: burn from the zero address');

        _beforeTokenTransfer(
            msg.sender,
            account,
            address(0),
            _asSingletonArray(id),
            _asSingletonArray(amount),
            ''
        );

        mapping(address => uint256) storage balances = ERC1155BaseStorage
            .layout()
            .balances[id];

        unchecked {
            require(
                balances[account] >= amount,
                'ERC1155: burn amount exceeds balances'
            );
            balances[account] -= amount;
        }

        emit TransferSingle(msg.sender, account, address(0), id, amount);
    }

    /**
     * @notice burn given batch of tokens held by given address
     * @param account holder of tokens to burn
     * @param ids token IDs
     * @param amounts quantities of tokens to burn
     */
    function _burnBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        require(account != address(0), 'ERC1155: burn from the zero address');
        require(
            ids.length == amounts.length,
            'ERC1155: ids and amounts length mismatch'
        );

        _beforeTokenTransfer(msg.sender, account, address(0), ids, amounts, '');

        mapping(uint256 => mapping(address => uint256))
            storage balances = ERC1155BaseStorage.layout().balances;

        unchecked {
            for (uint256 i; i < ids.length; i++) {
                uint256 id = ids[i];
                require(
                    balances[id][account] >= amounts[i],
                    'ERC1155: burn amount exceeds balance'
                );
                balances[id][account] -= amounts[i];
            }
        }

        emit TransferBatch(msg.sender, account, address(0), ids, amounts);
    }

    /**
     * @notice transfer tokens between given addresses
     * @dev ERC1155Receiver implementation is not checked
     * @param operator executor of transfer
     * @param sender sender of tokens
     * @param recipient receiver of tokens
     * @param id token ID
     * @param amount quantity of tokens to transfer
     * @param data data payload
     */
    function _transfer(
        address operator,
        address sender,
        address recipient,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(
            recipient != address(0),
            'ERC1155: transfer to the zero address'
        );

        _beforeTokenTransfer(
            operator,
            sender,
            recipient,
            _asSingletonArray(id),
            _asSingletonArray(amount),
            data
        );

        mapping(uint256 => mapping(address => uint256))
            storage balances = ERC1155BaseStorage.layout().balances;

        unchecked {
            uint256 senderBalance = balances[id][sender];
            require(
                senderBalance >= amount,
                'ERC1155: insufficient balances for transfer'
            );
            balances[id][sender] = senderBalance - amount;
        }

        balances[id][recipient] += amount;

        emit TransferSingle(operator, sender, recipient, id, amount);
    }

    /**
     * @notice transfer tokens between given addresses
     * @param operator executor of transfer
     * @param sender sender of tokens
     * @param recipient receiver of tokens
     * @param id token ID
     * @param amount quantity of tokens to transfer
     * @param data data payload
     */
    function _safeTransfer(
        address operator,
        address sender,
        address recipient,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        _transfer(operator, sender, recipient, id, amount, data);

        _doSafeTransferAcceptanceCheck(
            operator,
            sender,
            recipient,
            id,
            amount,
            data
        );
    }

    /**
     * @notice transfer batch of tokens between given addresses
     * @dev ERC1155Receiver implementation is not checked
     * @param operator executor of transfer
     * @param sender sender of tokens
     * @param recipient receiver of tokens
     * @param ids token IDs
     * @param amounts quantities of tokens to transfer
     * @param data data payload
     */
    function _transferBatch(
        address operator,
        address sender,
        address recipient,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(
            recipient != address(0),
            'ERC1155: transfer to the zero address'
        );
        require(
            ids.length == amounts.length,
            'ERC1155: ids and amounts length mismatch'
        );

        _beforeTokenTransfer(operator, sender, recipient, ids, amounts, data);

        mapping(uint256 => mapping(address => uint256))
            storage balances = ERC1155BaseStorage.layout().balances;

        for (uint256 i; i < ids.length; i++) {
            uint256 token = ids[i];
            uint256 amount = amounts[i];

            unchecked {
                uint256 senderBalance = balances[token][sender];
                require(
                    senderBalance >= amount,
                    'ERC1155: insufficient balances for transfer'
                );
                balances[token][sender] = senderBalance - amount;
            }

            balances[token][recipient] += amount;
        }

        emit TransferBatch(operator, sender, recipient, ids, amounts);
    }

    /**
     * @notice transfer batch of tokens between given addresses
     * @param operator executor of transfer
     * @param sender sender of tokens
     * @param recipient receiver of tokens
     * @param ids token IDs
     * @param amounts quantities of tokens to transfer
     * @param data data payload
     */
    function _safeTransferBatch(
        address operator,
        address sender,
        address recipient,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        _transferBatch(operator, sender, recipient, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(
            operator,
            sender,
            recipient,
            ids,
            amounts,
            data
        );
    }

    /**
     * @notice wrap given element in array of length 1
     * @param element element to wrap
     * @return singleton array
     */
    function _asSingletonArray(uint256 element)
        private
        pure
        returns (uint256[] memory)
    {
        uint256[] memory array = new uint256[](1);
        array[0] = element;
        return array;
    }

    /**
     * @notice revert if applicable transfer recipient is not valid ERC1155Receiver
     * @param operator executor of transfer
     * @param from sender of tokens
     * @param to receiver of tokens
     * @param id token ID
     * @param amount quantity of tokens to transfer
     * @param data data payload
     */
    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try
                IERC1155Receiver(to).onERC1155Received(
                    operator,
                    from,
                    id,
                    amount,
                    data
                )
            returns (bytes4 response) {
                require(
                    response == IERC1155Receiver.onERC1155Received.selector,
                    'ERC1155: ERC1155Receiver rejected tokens'
                );
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert('ERC1155: transfer to non ERC1155Receiver implementer');
            }
        }
    }

    /**
     * @notice revert if applicable transfer recipient is not valid ERC1155Receiver
     * @param operator executor of transfer
     * @param from sender of tokens
     * @param to receiver of tokens
     * @param ids token IDs
     * @param amounts quantities of tokens to transfer
     * @param data data payload
     */
    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try
                IERC1155Receiver(to).onERC1155BatchReceived(
                    operator,
                    from,
                    ids,
                    amounts,
                    data
                )
            returns (bytes4 response) {
                require(
                    response ==
                        IERC1155Receiver.onERC1155BatchReceived.selector,
                    'ERC1155: ERC1155Receiver rejected tokens'
                );
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert('ERC1155: transfer to non ERC1155Receiver implementer');
            }
        }
    }

    /**
     * @notice ERC1155 hook, called before all transfers including mint and burn
     * @dev function should be overridden and new implementation must call super
     * @dev called for both single and batch transfers
     * @param operator executor of transfer
     * @param from sender of tokens
     * @param to receiver of tokens
     * @param ids token IDs
     * @param amounts quantities of tokens to transfer
     * @param data data payload
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}
}

File 34 of 38 : IERC1155Internal.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC165 } from '../../introspection/IERC165.sol';

/**
 * @notice Partial ERC1155 interface needed by internal functions
 */
interface IERC1155Internal {
    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 value
    );

    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    event ApprovalForAll(
        address indexed account,
        address indexed operator,
        bool approved
    );
}

File 35 of 38 : IERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC165 interface registration interface
 * @dev see https://eips.ethereum.org/EIPS/eip-165
 */
interface IERC165 {
    /**
     * @notice query whether contract has registered support for given interface
     * @param interfaceId interface id
     * @return bool whether interface is supported
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 36 of 38 : FeeDiscountStorage.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

library FeeDiscountStorage {
    bytes32 internal constant STORAGE_SLOT =
        keccak256("premia.contracts.staking.PremiaFeeDiscount");

    struct UserInfo {
        uint256 balance; // Balance staked by user
        uint64 stakePeriod; // Stake period selected by user
        uint64 lockedUntil; // Timestamp at which the lock ends
    }

    struct Layout {
        // User data with xPREMIA balance staked and date at which lock ends
        mapping(address => UserInfo) userInfo;
    }

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }
}

File 37 of 38 : PremiaMiningStorage.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

library PremiaMiningStorage {
    bytes32 internal constant STORAGE_SLOT =
        keccak256("premia.contracts.storage.PremiaMining");

    // Info of each pool.
    struct PoolInfo {
        uint256 allocPoint; // How many allocation points assigned to this pool. PREMIA to distribute per block.
        uint256 lastRewardTimestamp; // Last timestamp that PREMIA distribution occurs
        uint256 accPremiaPerShare; // Accumulated PREMIA per share, times 1e12. See below.
    }

    // Info of each user.
    struct UserInfo {
        uint256 reward; // Total allocated unclaimed reward
        uint256 rewardDebt; // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of PREMIA
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accPremiaPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accPremiaPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    struct Layout {
        // Total PREMIA left to distribute
        uint256 premiaAvailable;
        // Amount of premia distributed per year
        uint256 premiaPerYear;
        // pool -> isCallPool -> PoolInfo
        mapping(address => mapping(bool => PoolInfo)) poolInfo;
        // pool -> isCallPool -> user -> UserInfo
        mapping(address => mapping(bool => mapping(address => UserInfo))) userInfo;
        // Total allocation points. Must be the sum of all allocation points in all pools.
        uint256 totalAllocPoint;
    }

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }
}

File 38 of 38 : VolatilitySurfaceOracleStorage.sol
// SPDX-License-Identifier: BUSL-1.1
// For further clarification please see https://license.premia.legal

pragma solidity ^0.8.0;

import {EnumerableSet} from "@solidstate/contracts/utils/EnumerableSet.sol";

library VolatilitySurfaceOracleStorage {
    bytes32 internal constant STORAGE_SLOT =
        keccak256("premia.contracts.storage.VolatilitySurfaceOracle");

    uint256 internal constant COEFF_BITS = 51;
    uint256 internal constant COEFF_BITS_MINUS_ONE = 50;
    uint256 internal constant COEFF_AMOUNT = 5;
    // START_BIT = COEFF_BITS * (COEFF_AMOUNT - 1)
    uint256 internal constant START_BIT = 204;

    struct Update {
        uint256 updatedAt;
        bytes32 callCoefficients;
        bytes32 putCoefficients;
    }

    struct Layout {
        // Base token -> Underlying token -> Update
        mapping(address => mapping(address => Update)) volatilitySurfaces;
        // Relayer addresses which can be trusted to provide accurate option trades
        EnumerableSet.AddressSet whitelistedRelayers;
    }

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }

    function getCoefficients(
        Layout storage l,
        address baseToken,
        address underlyingToken,
        bool isCall
    ) internal view returns (bytes32) {
        Update storage u = l.volatilitySurfaces[baseToken][underlyingToken];
        return isCall ? u.callCoefficients : u.putCoefficients;
    }

    function parseVolatilitySurfaceCoefficients(bytes32 input)
        internal
        pure
        returns (int256[] memory coefficients)
    {
        coefficients = new int256[](COEFF_AMOUNT);

        // Value to add to negative numbers to cast them to int256
        int256 toAdd = (int256(-1) >> COEFF_BITS) << COEFF_BITS;

        assembly {
            let i := 0
            // Value equal to -1
            let mid := shl(COEFF_BITS_MINUS_ONE, 1)

            for {

            } lt(i, COEFF_AMOUNT) {

            } {
                let offset := sub(START_BIT, mul(COEFF_BITS, i))
                let coeff := shr(
                    offset,
                    sub(
                        input,
                        shl(
                            add(offset, COEFF_BITS),
                            shr(add(offset, COEFF_BITS), input)
                        )
                    )
                )

                // Check if value is a negative number and needs casting
                if or(eq(coeff, mid), gt(coeff, mid)) {
                    coeff := add(coeff, toAdd)
                }

                // Store result in the coefficients array
                mstore(add(coefficients, add(0x20, mul(0x20, i))), coeff)

                i := add(i, 1)
            }
        }
    }

    function formatVolatilitySurfaceCoefficients(int256[5] memory coefficients)
        internal
        pure
        returns (bytes32 result)
    {
        for (uint256 i = 0; i < COEFF_AMOUNT; i++) {
            int256 max = int256(1 << COEFF_BITS_MINUS_ONE);
            require(
                coefficients[i] < max && coefficients[i] > -max,
                "Out of bounds"
            );
        }

        assembly {
            let i := 0

            for {

            } lt(i, COEFF_AMOUNT) {

            } {
                let offset := sub(START_BIT, mul(COEFF_BITS, i))
                let coeff := mload(add(coefficients, mul(0x20, i)))

                result := add(
                    result,
                    shl(
                        offset,
                        sub(coeff, shl(COEFF_BITS, shr(COEFF_BITS, coeff)))
                    )
                )

                i := add(i, 1)
            }
        }
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/libraries/OptionMath.sol": {
      "OptionMath": "0xc7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f5"
    }
  }
}

Contract ABI

[{"inputs":[{"internalType":"address","name":"ivolOracle","type":"address"},{"internalType":"address","name":"weth","type":"address"},{"internalType":"address","name":"premiaMining","type":"address"},{"internalType":"address","name":"feeReceiver","type":"address"},{"internalType":"address","name":"feeDiscountAddress","type":"address"},{"internalType":"int128","name":"fee64x64","type":"int128"},{"internalType":"address","name":"uniswapV2Factory","type":"address"},{"internalType":"address","name":"sushiswapFactory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"shortTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Annihilate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"underwriter","type":"address"},{"indexed":false,"internalType":"uint256","name":"shortTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"freedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"intervalContractSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"AssignExercise","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"bool","name":"isCallPool","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"longTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"contractSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exerciseValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Exercise","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCallPool","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"longTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"contractSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseCost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeCost","type":"uint256"},{"indexed":false,"internalType":"int128","name":"spot64x64","type":"int128"}],"name":"Purchase","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"underwriter","type":"address"},{"indexed":true,"internalType":"address","name":"longReceiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"shortTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"intervalContractSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"intervalPremium","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isManualUnderwrite","type":"bool"}],"name":"Underwrite","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCall","type":"bool"},{"indexed":false,"internalType":"int128","name":"cLevel64x64","type":"int128"},{"indexed":false,"internalType":"int128","name":"oldLiquidity64x64","type":"int128"},{"indexed":false,"internalType":"int128","name":"newLiquidity64x64","type":"int128"}],"name":"UpdateCLevel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int128","name":"steepness64x64","type":"int128"},{"indexed":false,"internalType":"bool","name":"isCallPool","type":"bool"}],"name":"UpdateSteepness","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"bool","name":"isCallPool","type":"bool"},{"indexed":false,"internalType":"uint256","name":"depositedAt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[{"internalType":"uint64","name":"maturity","type":"uint64"},{"internalType":"int128","name":"strike64x64","type":"int128"},{"internalType":"uint256","name":"contractSize","type":"uint256"},{"internalType":"bool","name":"isCall","type":"bool"},{"internalType":"uint256","name":"maxCost","type":"uint256"}],"name":"purchase","outputs":[{"internalType":"uint256","name":"baseCost","type":"uint256"},{"internalType":"uint256","name":"feeCost","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"feePayer","type":"address"},{"internalType":"uint64","name":"maturity","type":"uint64"},{"internalType":"int128","name":"strike64x64","type":"int128"},{"internalType":"uint256","name":"contractSize","type":"uint256"},{"internalType":"bool","name":"isCall","type":"bool"}],"name":"quote","outputs":[{"internalType":"int128","name":"baseCost64x64","type":"int128"},{"internalType":"int128","name":"feeCost64x64","type":"int128"},{"internalType":"int128","name":"cLevel64x64","type":"int128"},{"internalType":"int128","name":"slippageCoefficient64x64","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"maturity","type":"uint64"},{"internalType":"int128","name":"strike64x64","type":"int128"},{"internalType":"uint256","name":"contractSize","type":"uint256"},{"internalType":"bool","name":"isCall","type":"bool"},{"internalType":"uint256","name":"maxCost","type":"uint256"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bool","name":"isSushi","type":"bool"}],"name":"swapAndPurchase","outputs":[{"internalType":"uint256","name":"baseCost","type":"uint256"},{"internalType":"uint256","name":"feeCost","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"update","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"underwriter","type":"address"},{"internalType":"address","name":"longReceiver","type":"address"},{"internalType":"uint64","name":"maturity","type":"uint64"},{"internalType":"int128","name":"strike64x64","type":"int128"},{"internalType":"uint256","name":"contractSize","type":"uint256"},{"internalType":"bool","name":"isCall","type":"bool"}],"name":"writeFrom","outputs":[{"internalType":"uint256","name":"longTokenId","type":"uint256"},{"internalType":"uint256","name":"shortTokenId","type":"uint256"}],"stateMutability":"payable","type":"function"}]

6102006040523480156200001257600080fd5b50604051620052833803806200528383398101604081905262000035916200018e565b6001600160a01b038881166101005287811660805286811660a05285811660c052841660e052600f83900b6101205287878787878787878787878787876200008c6000808062000125602090811b620004fd17901c565b6101408181525050620000ae60016000806200012560201b620004fd1760201c565b6101608181525050620000d060026000806200012560201b620004fd1760201c565b6101808181525050620000f260036000806200012560201b620004fd1760201c565b6101a0525050506001600160a01b039485166101c052505050166101e052506200027a9c50505050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200015057620001506200023d565b6200015d92911b62000253565b62000169919062000253565b949350505050565b80516001600160a01b03811681146200018957600080fd5b919050565b600080600080600080600080610100898b031215620001ac57600080fd5b620001b78962000171565b9750620001c760208a0162000171565b9650620001d760408a0162000171565b9550620001e760608a0162000171565b9450620001f760808a0162000171565b935060a089015180600f0b81146200020e57600080fd5b92506200021e60c08a0162000171565b91506200022e60e08a0162000171565b90509295985092959890939650565b634e487b7160e01b600052602160045260246000fd5b600082198211156200027557634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e051614ec8620003bb600039600081816113e40152818161156901528181611822015281816125a2015261261f0152600081816113be01528181611543015281816117fc015281816118c80152818161257c01526125f9015260008181612d4201526134a0015260008181612d1801528181612dbb01526134c6015260008181612cee01528181612e7f0152613132015260008181612cc501528181612d9201528181612e5601528181612ea8015261315801526000610a9c0152600061086b015260008181611c4b0152611c9701526000612060015260008181613c680152613da8015260008181610c3001528181610c7801528181610d990152818161130e0152818161147e015261150c0152614ec86000f3fe60806040526004361061004a5760003560e01c8063405370491461004f5780635ec50709146100a1578063677956f1146100c9578063a2e62045146100dc578063a43e5b6d146100f3575b600080fd5b34801561005b57600080fd5b5061006f61006a366004614501565b610106565b60408051600f95860b815293850b602085015291840b9183019190915290910b60608201526080015b60405180910390f35b6100b46100af366004614565565b6101bf565b60408051928352602083019190915201610098565b6100b46100d73660046145d8565b61034c565b3480156100e857600080fd5b506100f161036c565b005b6100b4610101366004614631565b610386565b60008080808080600080516020614e7383398151915290504281600c0154146101395761013281610542565b9150610146565b6101438142610665565b91505b60006101976040518060c001604052808e6001600160a01b031681526020018d6001600160401b031681526020018c600f0b815260200185600f0b81526020018b81526020018a1515815250610695565b805160208201516040830151606090930151919f909e50919c509a5098505050505050505050565b600080336001600160a01b038916148061021b57506001600160a01b03881660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff165b61025b5760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b600061026684610b31565b905060008461029a57610295610280600f89900b88610b86565b600080516020614e7383398151915290610bee565b61029c565b855b90506102ab8a83836000610c29565b6102c06102b9866001610f2a565b89896104fd565b93506102d06102b9866000610f2a565b92506102dd898588610f5e565b6102e88a8488610f5e565b60408051848152602081018890526000818301526001606082015290516001600160a01b038b811692908d16917fa30eb1f1bb0892af5e3389941c42bc7f44a8bf7b6071ecfe3e964673c908f2b29181900360800190a35050965096945050505050565b60008061035e87878787876000610f7e565b915091509550959350505050565b610383600080516020614e7383398151915261122d565b50565b600080341580610394575085155b6103e05760405162461bcd60e51b815260206004820152601c60248201527f76616c756520616e6420616d6f756e74496e4d617820706173736564000000006044820152606401610252565b866104b457600080516020614e7383398151915260006103ff8261122d565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a08101919091529091503381526001600160401b038f166020820152600f8e810b604083015282900b6060820152608081018d90528b151560a0820152600061047282610695565b90506104ad61049582602001518360000151600f0b61128d90919063ffffffff16565b6104a88f876112c190919063ffffffff16565b6112ef565b9a50505050505b34156104cc576104c68786868661130a565b506104db565b6104d987878787876117f0565b505b6104ea8c8c8c8c8c6001610f7e565b915091509a509a98505050505050505050565b600081600f0b6080846001600160401b0316901b60f886600781111561052557610525614714565b61053092911b614740565b61053a9190614740565b949350505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561059557600080fd5b505afa1580156105a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105cd9190614758565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561062157600080fd5b505afa158015610635573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106599190614758565b905061053a828261198a565b60006011830181610678610e1085614787565b8152602081019190915260400160002054600f0b90505b92915050565b60408051608081018252600080825260208201819052918101829052606081019190915260008260400151600f0b1380156106d7575060008260600151600f0b135b80156106f05750600082602001516001600160401b0316115b61072b5760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964206172677360a01b6044820152606401610252565b60808201517fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ee54600080516020614e73833981519152916000916107799190600160a01b900460ff16611a25565b60a08501519091506000806107a46107918685611a3b565b61079b8786611a54565b87919086611ac7565b91509150600081600f0b136107e45760405162461bcd60e51b81526020600482015260066024820152656e6f206c697160d01b6044820152606401610252565b600061080c4289602001516001600160401b0316610802919061479b565b6301e13380611b4f565b8654600188015460608b01516040808d0151905163c4afd30d60e01b81526001600160a01b0394851660048201529284166024840152600f91820b6044840152810b606483015283900b608482015286151560a48201529192506000917f00000000000000000000000000000000000000000000000000000000000000009091169063c4afd30d9060c40160206040518083038186803b1580156108af57600080fd5b505afa1580156108c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e791906147b2565b9050600081600f0b136109265760405162461bcd60e51b81526020600482015260076024820152660766f6c203d20360cc1b6044820152606401610252565b600080600073c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f5630313686c6040518061014001604052806109678889600f0b611b8690919063ffffffff16565b600f0b81526020018f60400151600f0b81526020018f60600151600f0b815260200188600f0b81526020018a600f0b815260200189600f0b81526020016109ba8d8b600f0b611bbc90919063ffffffff16565b600f0b8152602001600160401b600f0b8152602001674ccccccccccccccd600f0b81526020018b15158152506040518263ffffffff1660e01b8152600401610a0291906147cf565b60606040518083038186803b158015610a1a57600080fd5b505af4158015610a2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a529190614898565b92509250925087610a7057610a6b600f84900b8a611b86565b610a90565b60608c0151610a9090610a87600f86900b8c611b86565b600f0b90611bef565b600f0b808c52610ac0907f0000000000000000000000000000000000000000000000000000000000000000611b86565b600f90810b60208d015282810b60408d015281900b60608c01528b51600090610af490610aec90611c47565b612710611b4f565b60208d0151909150610b0990600f0b82611b86565b8c602001818151610b1a91906148e5565b600f0b9052509a9c9b505050505050505050505050565b600081610b5657600080516020614e73833981519152546001600160a01b031661068f565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b600081610b955750600061068f565b600083600f0b1215610ba657600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b03811115610bd557600080fd5b60401b8119811115610be657600080fd5b019392505050565b600080610c0c838560030160149054906101000a900460ff16611a25565b905061053a818560030160159054906101000a900460ff166112ef565b80610e52577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161415610e11573415610e0c5781341115610d92577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015610cd157600080fd5b505af1158015610ce5573d6000803e3d6000fd5b50505050506000336001600160a01b03168334610d02919061479b565b604051600081818185875af1925050503d8060008114610d3e576040519150601f19603f3d011682016040523d82523d6000602084013e610d43565b606091505b5050905080610d885760405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606401610252565b6000925050610e52565b34820391507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610df257600080fd5b505af1158015610e06573d6000803e3d6000fd5b50505050505b610e52565b3415610e525760405162461bcd60e51b815260206004820152601060248201526f1b9bdd0815d155120819195c1bdcda5d60821b6044820152606401610252565b8115610f24576040516323b872dd60e01b81526001600160a01b038581166004830152306024830152604482018490528416906323b872dd90606401602060405180830381600087803b158015610ea857600080fd5b505af1158015610ebc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee09190614935565b610f245760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b6044820152606401610252565b50505050565b60008215610f485781610f3e576005610f41565b60045b905061068f565b81610f54576007610f57565b60065b9392505050565b610f7983838360405180602001604052806000815250611d11565b505050565b600080600080516020614e73833981519152610f9d4262015180614740565b896001600160401b03161015610fe35760405162461bcd60e51b815260206004820152600b60248201526a657870203c20312064617960a81b6044820152606401610252565b610ff0426277f880614740565b896001600160401b0316106110375760405162461bcd60e51b815260206004820152600d60248201526c657870203e203930206461797360981b6044820152606401610252565b6110436170808a614952565b6001600160401b0316156110995760405162461bcd60e51b815260206004820152601c60248201527f657870206d75737420626520382d686f757220696e6372656d656e74000000006044820152606401610252565b60006110a48261122d565b9050861561112c57600181600f0b901b600f0b89600f0b131580156110e55750600a6110d1826008614978565b6110db9190614a0d565b600f0b89600f0b12155b6111275760405162461bcd60e51b8152602060048201526013602482015272737472696b65206f7574206f662072616e676560681b6044820152606401610252565b6111a7565b600a61113982600c614978565b6111439190614a0d565b600f0b89600f0b131580156111655750600181600f0b901d600f0b89600f0b12155b6111a75760405162461bcd60e51b8152602060048201526013602482015272737472696b65206f7574206f662072616e676560681b6044820152606401610252565b6111b682338c8c8b8d87611e38565b9094509250856111c68486614740565b11156112035760405162461bcd60e51b815260206004820152600c60248201526b065786365737320736c6970760a41b6044820152606401610252565b6112203361121089610b31565b61121a8688614740565b88610c29565b5050965096945050505050565b60004282600c015414156112455761068f8242610665565b61124e82610542565b905061125a8242610665565b600f0b61126c5761126c8242836120fc565b42600c83015561127d82600161217a565b61128882600061217a565b919050565b6000600f83810b9083900b0160016001607f1b031981128015906112b8575060016001607f1b038113155b610f5757600080fd5b6000816112dc576003830154600160a81b900460ff16610f57565b505060030154600160a01b900460ff1690565b6000610f576112ff83600a614b2f565b600f85900b90610b86565b60607f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168484600081811061134957611349614b3e565b905060200201602081019061135e9190614b54565b6001600160a01b0316146113b45760405162461bcd60e51b815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f504154480000006044820152606401610252565b611440826113e2577f0000000000000000000000000000000000000000000000000000000000000000611404565b7f00000000000000000000000000000000000000000000000000000000000000005b86868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250889250612205915050565b9050348160008151811061145657611456614b3e565b6020026020010151111561147c5760405162461bcd60e51b815260040161025290614b6f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0826000815181106114be576114be614b3e565b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b1580156114f157600080fd5b505af1158015611505573d6000803e3d6000fd5b50505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663a9059cbb6115df84611567577f0000000000000000000000000000000000000000000000000000000000000000611589565b7f00000000000000000000000000000000000000000000000000000000000000005b8787600081811061159c5761159c614b3e565b90506020020160208101906115b19190614b54565b888860018181106115c4576115c4614b3e565b90506020020160208101906115d99190614b54565b87612393565b836000815181106115f2576115f2614b3e565b60200260200101516040518363ffffffff1660e01b815260040161162b9291906001600160a01b03929092168252602082015260400190565b602060405180830381600087803b15801561164557600080fd5b505af1158015611659573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061167d9190614935565b61168957611689614bb6565b6116ca818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152503392508791506124949050565b806000815181106116dd576116dd614b3e565b602002602001015134111561053a576000336001600160a01b03168260008151811061170b5761170b614b3e565b60200260200101513461171e919061479b565b6040805160008152602081019182905261173791614c0e565b60006040518083038185875af1925050503d8060008114611774576040519150601f19603f3d011682016040523d82523d6000602084013e611779565b606091505b50509050806117e75760405162461bcd60e51b815260206004820152603460248201527f5472616e7366657248656c7065723a3a736166655472616e736665724554483a60448201527308115512081d1c985b9cd9995c8819985a5b195960621b6064820152608401610252565b50949350505050565b606061187e82611820577f0000000000000000000000000000000000000000000000000000000000000000611842565b7f00000000000000000000000000000000000000000000000000000000000000005b87868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250889250612205915050565b9050848160008151811061189457611894614b3e565b602002602001015111156118ba5760405162461bcd60e51b815260040161025290614b6f565b611940336118ec84611567577f0000000000000000000000000000000000000000000000000000000000000000611589565b836000815181106118ff576118ff614b3e565b60200260200101518787600081811061191a5761191a614b3e565b905060200201602081019061192f9190614b54565b6001600160a01b03169291906126f7565b611981818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152503392508791506124949050565b95945050505050565b60008161199657600080fd5b6000808412156119ab57836000039350600190505b60008312156119bd5760009290920391155b60006119c98585612751565b905081156119f7576001607f1b816001600160801b031611156119eb57600080fd5b600003915061068f9050565b60016001607f1b03816001600160801b03161115611a1457600080fd5b915061068f9050565b505092915050565b6000610f5783611a3684600a614b2f565b611b4f565b6000611a4783836128b3565b9050610f578382846128da565b600080611a7183611a66576001611a69565b60005b6000806104fd565b831515600090815260138601602052604090206001015490915061053a90600080516020614e5383398151915260008481526020919091526040902054611ab8919061479b565b611ac286866112c1565b611a25565b801515600090815260138501602052604081206001810154829190829015801590611af25750815415155b8015611aff575081544210155b15611b3d576001820154611b1790611ac28a886112c1565b9050611b27600f87900b8261128d565b9250611b368888888689612a9c565b9350611b44565b8693508592505b505094509492505050565b600081611b5b57600080fd5b6000611b678484612751565b905060016001607f1b036001600160801b0382161115610f5757600080fd5b6000600f83810b9083900b0260401d60016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b6000600f82810b9084900b0360016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b600081600f0b60001415611c0257600080fd5b600082600f0b604085600f0b901b81611c1d57611c1d614771565b05905060016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615611288576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b158015611cd957600080fd5b505afa158015611ced573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068f9190614758565b6001600160a01b038416611d715760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610252565b611d9033600086611d8187612ba2565b611d8a87612ba2565b86612bed565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611de3908490614740565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b60008042876001600160401b031611611e7d5760405162461bcd60e51b8152602060048201526007602482015266195e1c1a5c995960ca1b6044820152606401610252565b8860050154841015611ebd5760405162461bcd60e51b81526020600482015260096024820152681d1bdbc81cdb585b1b60ba1b6044820152606401610252565b600085611ee157611edc611ed5600f89900b87610b86565b8b90610bee565b611ee3565b845b861515600090815260138c016020526040902060010154909150600080516020614e538339815191526000611f1789613129565b815260200190815260200160002054611f30919061479b565b811115611f6b5760405162461bcd60e51b8152602060048201526009602482015268696e737566206c697160b81b6044820152606401610252565b506000611fbd6040518060c001604052808b6001600160a01b031681526020018a6001600160401b0316815260200189600f0b815260200186600f0b8152602001878152602001881515815250610695565b8051909150611fd0906104a88c896112c1565b6020820151909350611fe6906104a88c896112c1565b91506000611fff611ff8886001610f2a565b8a8a6104fd565b90506000612018612011896000610f2a565b8b8b6104fd565b90506120258b8389610f5e565b60006120318d8a611a54565b90506120418d8d8a89868e61317d565b600061204d8e8b611a54565b905061205b8e83838d61341a565b61208e7f00000000000000000000000000000000000000000000000000000000000000006120888c613497565b88610f5e565b60408051858152602081018b905290810188905260608101879052600f89900b60808201526001600160a01b038e16907f4719d073a940c087132bffc1aac4f3837ca2c820d95574727828ce38f7fbc8c79060a00160405180910390a2505050505097509795505050505050565b600061210a610e1084614787565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061214360ff8083169061479b565b6001901b846012016000600884901c8152602001908152602001600020600082825461216f9190614740565b909155505050505050565b801515600090815260138301602052604090208054158061219b5750805442105b156121a557505050565b60006121b18484611a54565b90506121e784826121e16121d68660010154611ac2898b6112c190919063ffffffff16565b600f86900b9061128d565b8661341a565b50501515600090815260139091016020526040812081815560010155565b60606002835110156122595760405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f5041544800006044820152606401610252565b82516001600160401b0381111561227257612272614bcc565b60405190808252806020026020018201604052801561229b578160200160208202803683370190505b5090508381600183516122ae919061479b565b815181106122be576122be614b3e565b6020026020010181815250506000600184516122da919061479b565b90505b80156117e75760008061232e88876122f660018761479b565b8151811061230657612306614b3e565b602002602001015188868151811061232057612320614b3e565b6020026020010151886134eb565b9150915061235684848151811061234757612347614b3e565b602002602001015183836135c6565b8461236260018661479b565b8151811061237257612372614b3e565b6020026020010181815250505050808061238b90614c2a565b9150506122dd565b60008060006123a286866136ec565b6040516bffffffffffffffffffffffff19606084811b8216602084015283901b166034820152919350915087906048016040516020818303038152906040528051906020012085612428576040518060400160405280602081526020017f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f81525061245f565b6040518060400160405280602081526020017fe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c63038152505b60405160200161247193929190614c41565b60408051601f198184030181529190528051602090910120979650505050505050565b60005b600184516124a5919061479b565b8110156126f0576000808583815181106124c1576124c1614b3e565b6020026020010151868460016124d79190614740565b815181106124e7576124e7614b3e565b60200260200101519150915060006124ff83836136ec565b509050600088612510866001614740565b8151811061252057612520614b3e565b60200260200101519050600080836001600160a01b0316866001600160a01b03161461254e57826000612552565b6000835b91509150600060028b51612566919061479b565b881061257257896125ed565b6125ed896125a0577f00000000000000000000000000000000000000000000000000000000000000006125c2565b7f00000000000000000000000000000000000000000000000000000000000000005b878d6125cf8c6002614740565b815181106125df576125df614b3e565b60200260200101518c612393565b90506126478961261d577f000000000000000000000000000000000000000000000000000000000000000061263f565b7f00000000000000000000000000000000000000000000000000000000000000005b88888c612393565b6001600160a01b031663022c0d9f84848460006040519080825280601f01601f191660200182016040528015612684576020820181803683370190505b506040518563ffffffff1660e01b81526004016126a49493929190614cb6565b600060405180830381600087803b1580156126be57600080fd5b505af11580156126d2573d6000803e3d6000fd5b505050505050505050505080806126e890614ce3565b915050612497565b5050505050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610f249085906137e4565b60008161275d57600080fd5b60006001600160c01b0384116127885782604085901b8161278057612780614771565b04905061289f565b60c084811c64010000000081106127a1576020918201911c5b6201000081106127b3576010918201911c5b61010081106127c4576008918201911c5b601081106127d4576004918201911c5b600481106127e4576002918201911c5b600281106127f3576001820191505b60bf820360018603901c6001018260ff0387901b8161281457612814614771565b0492506001600160801b0383111561282b57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b82811015612857576001820391505b608084901b9290038281101561286e576001820391505b829003608084901c821461288457612884614bb6565b88818161289357612893614771565b04870196505050505050505b6001600160801b03811115610f5757600080fd5b6000816128ce576008830154600160801b9004600f0b610f57565b505060090154600f0b90565b600080826128ec5784600a01546128f2565b84600b01545b6128fc904261479b565b905061a8c081111561291b5761291461a8c08261479b565b9050612924565b83915050610f57565b600061293282613840611b4f565b9050600061294585611a66576001611a69565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020614e5383398151915290935290832054939450926129a3916129939161479b565b61299d908461479b565b83611b4f565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e66666666666666660808201819052600160401b60a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b815291925073c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f591634916d70d91612a3f91600401614cfe565b60206040518083038186803b158015612a5757600080fd5b505af4158015612a6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8f91906147b2565b9998505050505050505050565b60008082612ab1576019870154600f0b612ac1565b6019870154600160801b9004600f0b5b905080600f0b60001415612ad957506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b606482015273c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f59063e101a89b9060840160206040518083038186803b158015612b3f57600080fd5b505af4158015612b53573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b7791906147b2565b915067b33333333333333382600f0b1215612b985767b33333333333333391505b5095945050505050565b60408051600180825281830190925260609160009190602080830190803683370190505090508281600081518110612bdc57612bdc614b3e565b602090810291909101015292915050565b612bfb8686868686866138b6565b600080516020614e7383398151915260005b845181101561311f576000858281518110612c2a57612c2a614b3e565b602002602001015190506000858381518110612c4857612c48614b3e565b602002602001015190508060001415612c6257505061310d565b6001600160a01b038916612c7f57612c7d6015850183613a9a565b505b6001600160a01b038816158015612caf57506000828152600080516020614e538339815191526020526040902054155b15612cc357612cc16015850183613aa6565b505b7f0000000000000000000000000000000000000000000000000000000000000000821480612d1057507f000000000000000000000000000000000000000000000000000000000000000082145b80612d3a57507f000000000000000000000000000000000000000000000000000000000000000082145b80612d6457507f000000000000000000000000000000000000000000000000000000000000000082145b15612e54576001600160a01b03891615801590612d8957506001600160a01b03881615155b15612e545760007f0000000000000000000000000000000000000000000000000000000000000000831480612ddd57507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d87016020908152604080832084151584529091529020549091504290612e179062015180614740565b10612e525760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b6044820152606401610252565b505b7f0000000000000000000000000000000000000000000000000000000000000000821480612ea157507f000000000000000000000000000000000000000000000000000000000000000082145b15613049577f000000000000000000000000000000000000000000000000000000000000000082146000612ed58683613ab2565b90506001600160a01b038b1615612fff576000612ef28c86613acc565b90508181118015612f0c5750612f088285614740565b8111155b15612fa4576001600160a01b038c166000908152601488016020908152604080832086151580855260138c0184528285205485529083528184209084529091529020548490612f5b908361479b565b1015612f995760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b6044820152606401610252565b612fa4878d85613b7f565b6001600160a01b038b1615612ffd57612fd8878d858c8a81518110612fcb57612fcb614b3e565b6020026020010151613c30565b612ffd878c858c8a81518110612ff057612ff0614b3e565b6020026020010151613d70565b505b6001600160a01b038a161561304657600061301a8b86613acc565b90508181111580156130345750816130328583614740565b115b1561304457613044878c85613e94565b505b50505b60f882901c826001600160a01b038b161580159061306f57506001600160a01b038a1615155b80156130a75750600582600781111561308a5761308a614714565b14806130a7575060078260078111156130a5576130a5614714565b145b1561310857600060058360078111156130c2576130c2614714565b1490506000816130e9576130e46130dd600f85900b87610b86565b8990610bee565b6130eb565b845b90506130f9888e8484613c30565b613105888d8484613d70565b50505b505050505b8061311781614ce3565b915050612c0d565b5050505050505050565b600081613156577f000000000000000000000000000000000000000000000000000000000000000061068f565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b600061318882613129565b9050826000836131af576131aa6131a3600f84900b89610b86565b8a90610bee565b6131b1565b865b90505b801561340f578315156000908152600f8a01602090815260408083208380529091528120546001600160a01b0316906131ed8286613acc565b90506131f98b87613ab2565b8110156132125761320b8b8388613b7f565b50506131b4565b61321d8b8388613f3c565b61324b5761322c828683613f76565b61323f8261323988613497565b83610f5e565b61320b8b838884613c30565b6000836132588a82614740565b8d6014016000866001600160a01b03166001600160a01b0316815260200190815260200160002060008f60130160008c15151515815260200190815260200160002060000154815260200190815260200160002060008a15151515815260200190815260200160002054846132cd919061479b565b6132d79190614dae565b6132e19190614787565b9050806132f0575050506131b4565b838111156132fb5750825b600084613308838c614dae565b6133129190614787565b905061331e818b61479b565b995061332a828661479b565b94506133388d858a84613d70565b61334c8488613347848661479b565b613f76565b876133775761337461336d8361336489600f0b614108565b600f0b90610b86565b8e9061415e565b91505b61338e848a87156133885784610f5e565b8d610f5e565b8b6001600160a01b0316846001600160a01b03167fa30eb1f1bb0892af5e3389941c42bc7f44a8bf7b6071ecfe3e964673c908f2b28b886000146133d257856133d4565b8e5b60408051928352602083019190915281018590526000606082015260800160405180910390a3613404828c61479b565b9a50505050506131b4565b505050505050505050565b60006134268583611a3b565b905060006134378683878787612a9c565b9050613444868285614199565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b6000816134c4577f000000000000000000000000000000000000000000000000000000000000000061068f565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008060006134fa86866136ec565b50905060008061350c89898989612393565b6001600160a01b0316630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561354457600080fd5b505afa158015613558573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061357c9190614de4565b506001600160701b031691506001600160701b03169150826001600160a01b0316886001600160a01b0316146135b35780826135b6565b81815b909a909950975050505050505050565b600080841161362c5760405162461bcd60e51b815260206004820152602c60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4f60448201526b155514155517d05353d5539560a21b6064820152608401610252565b60008311801561363c5750600082115b6136995760405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b6064820152608401610252565b60006136a58585614dae565b6136b1906103e8614dae565b905060006136bf868561479b565b6136cb906103e5614dae565b90506136d78183614787565b6136e2906001614740565b9695505050505050565b600080826001600160a01b0316846001600160a01b0316141561375f5760405162461bcd60e51b815260206004820152602560248201527f556e697377617056324c6962726172793a204944454e544943414c5f41444452604482015264455353455360d81b6064820152608401610252565b826001600160a01b0316846001600160a01b03161061377f578284613782565b83835b90925090506001600160a01b0382166137dd5760405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f4144445245535300006044820152606401610252565b9250929050565b6000613839826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166141f09092919063ffffffff16565b805190915015610f7957808060200190518101906138579190614935565b610f795760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610252565b836001600160a01b0316856001600160a01b031614613a92576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020614e53833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b8751811015613a8c57600087828151811061396257613962614b3e565b602002602001015190506000811115613a7957600089838151811061398957613989614b3e565b6020026020010151905060006001600160a01b03168c6001600160a01b031614156139d757600081815260208890526040812080548492906139cc908490614740565b90915550613a0d9050565b816139e28d83613acc565b1415613a0d576000818152602087905260409020613a00908d6141ff565b50613a0b8582613aa6565b505b6001600160a01b038b16613a445760008181526020889052604081208054849290613a3990849061479b565b90915550613a779050565b613a4e8b82613acc565b613a77576000818152602087905260409020613a6a908c614214565b50613a758482613a9a565b505b505b5080613a8481614ce3565b915050613945565b50505050505b505050505050565b6000610f578383614225565b6000610f578383614274565b600081613ac3578260040154610f57565b50506005015490565b60006001600160a01b038316613b385760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b6064820152608401610252565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6001600160a01b038216613b9257600080fd5b8015156000908152600f84016020908152604080832060108701909252909120613bbd84838361435f565b613bc8575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b86308786613c9b898261479b565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b158015613cfe57600080fd5b505af1158015613d12573d6000803e3d6000fd5b505050508282613d22919061479b565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055613d54838261479b565b9315156000908152601890960160205250506040909320555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b86308786613ddb8982614740565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b158015613e3e57600080fd5b505af1158015613e52573d6000803e3d6000fd5b505050508282613e629190614740565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055613d548382614740565b6001600160a01b038216613ea757600080fd5b8015156000908152600f84016020908152604080832060108701909252909120613ed284838361435f565b15613ede575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061198157504210949350505050565b6001600160a01b038316613fd85760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b6064820152608401610252565b61400633846000613fe886612ba2565b613ff186612ba2565b60405180602001604052806000815250612bed565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156140a75760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b6064820152608401610252565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600081600f0b6000141561411b57600080fd5b600082600f0b600160801b8161413357614133614771565b05905060016001607f1b03198112801590614155575060016001607f1b038113155b61068f57600080fd5b60008061417c838560030160159054906101000a900460ff16611a25565b905061053a818560030160149054906101000a900460ff166112ef565b80156141c9576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b606061053a84846000856143ab565b6000610f57836001600160a01b038416614274565b6000610f57836001600160a01b0384165b600081815260018301602052604081205461426c5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561068f565b50600061068f565b6000818152600183016020526040812054801561435557600061429860018361479b565b855490915060009086906142ae9060019061479b565b815481106142be576142be614b3e565b90600052602060002001549050808660000183815481106142e1576142e1614b3e565b6000918252602090912001556142f8826001614740565b6000828152600188016020526040902055855486908061431a5761431a614e29565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061068f565b600091505061068f565b6001600160a01b0383811660009081526020849052604081205490911615158061053a57506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6060843b61440f5760405162461bcd60e51b815260206004820152602b60248201527f416464726573735574696c733a2066756e6374696f6e2063616c6c20746f206e60448201526a1bdb8b58dbdb9d1c9858dd60aa1b6064820152608401610252565b600080866001600160a01b0316858760405161442b9190614c0e565b60006040518083038185875af1925050503d8060008114614468576040519150601f19603f3d011682016040523d82523d6000602084013e61446d565b606091505b5091509150811561448157915061053a9050565b8051156144915780518082602001fd5b8360405162461bcd60e51b81526004016102529190614e3f565b80356001600160a01b038116811461128857600080fd5b80356001600160401b038116811461128857600080fd5b80600f0b811461038357600080fd5b801515811461038357600080fd5b8035611288816144e8565b600080600080600060a0868803121561451957600080fd5b614522866144ab565b9450614530602087016144c2565b93506040860135614540816144d9565b9250606086013591506080860135614557816144e8565b809150509295509295909350565b60008060008060008060c0878903121561457e57600080fd5b614587876144ab565b9550614595602088016144ab565b94506145a3604088016144c2565b935060608701356145b3816144d9565b92506080870135915060a08701356145ca816144e8565b809150509295509295509295565b600080600080600060a086880312156145f057600080fd5b6145f9866144c2565b94506020860135614609816144d9565b9350604086013592506060860135614620816144e8565b949793965091946080013592915050565b6000806000806000806000806000806101208b8d03121561465157600080fd5b61465a8b6144c2565b995060208b013561466a816144d9565b985060408b0135975060608b0135614681816144e8565b965060808b0135955060a08b0135945060c08b0135935060e08b01356001600160401b03808211156146b257600080fd5b818d0191508d601f8301126146c657600080fd5b8135818111156146d557600080fd5b8e60208260051b85010111156146ea57600080fd5b6020830195508094505050506147036101008c016144f6565b90509295989b9194979a5092959850565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600082198211156147535761475361472a565b500190565b60006020828403121561476a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261479657614796614771565b500490565b6000828210156147ad576147ad61472a565b500390565b6000602082840312156147c457600080fd5b8151610f57816144d9565b8151600f0b8152610140810160208301516147ef6020840182600f0b9052565b5060408301516148046040840182600f0b9052565b5060608301516148196060840182600f0b9052565b50608083015161482e6080840182600f0b9052565b5060a083015161484360a0840182600f0b9052565b5060c083015161485860c0840182600f0b9052565b5060e083015161486d60e0840182600f0b9052565b506101008084015161488382850182600f0b9052565b50506101208381015180151584830152611a1d565b6000806000606084860312156148ad57600080fd5b83516148b8816144d9565b60208501519093506148c9816144d9565b60408501519092506148da816144d9565b809150509250925092565b600081600f0b83600f0b600081128160016001607f1b0319018312811516156149105761491061472a565b8160016001607f1b0301831381161561492b5761492b61472a565b5090039392505050565b60006020828403121561494757600080fd5b8151610f57816144e8565b60006001600160401b038084168061496c5761496c614771565b92169190910692915050565b600081600f0b83600f0b60016001607f1b036000821360008413838304851182821616156149a8576149a861472a565b60016001607f1b031960008512828116878305871216156149cb576149cb61472a565b600087129250858205871284841616156149e7576149e761472a565b858505871281841616156149fd576149fd61472a565b5050509290910295945050505050565b600081600f0b83600f0b80614a2457614a24614771565b60016001607f1b0319821460001982141615614a4257614a4261472a565b90059392505050565b600181815b80851115614a86578160001904821115614a6c57614a6c61472a565b80851615614a7957918102915b93841c9390800290614a50565b509250929050565b600082614a9d5750600161068f565b81614aaa5750600061068f565b8160018114614ac05760028114614aca57614ae6565b600191505061068f565b60ff841115614adb57614adb61472a565b50506001821b61068f565b5060208310610133831016604e8410600b8410161715614b09575081810a61068f565b614b138383614a4b565b8060001904821115614b2757614b2761472a565b029392505050565b6000610f5760ff841683614a8e565b634e487b7160e01b600052603260045260246000fd5b600060208284031215614b6657600080fd5b610f57826144ab565b60208082526027908201527f556e69737761705632526f757465723a204558434553534956455f494e50555460408201526617d05353d5539560ca1b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60005b83811015614bfd578181015183820152602001614be5565b83811115610f245750506000910152565b60008251614c20818460208701614be2565b9190910192915050565b600081614c3957614c3961472a565b506000190190565b60ff60f81b81526bffffffffffffffffffffffff198460601b16600182015282601582015260008251614c7b816035850160208701614be2565b91909101603501949350505050565b60008151808452614ca2816020860160208601614be2565b601f01601f19169290920160200192915050565b84815283602082015260018060a01b03831660408201526080606082015260006136e26080830184614c8a565b6000600019821415614cf757614cf761472a565b5060010190565b6000610120820190508251600f0b82526020830151600f0b60208301526040830151614d2f6040840182600f0b9052565b506060830151614d446060840182600f0b9052565b506080830151614d596080840182600f0b9052565b5060a0830151614d6e60a0840182600f0b9052565b5060c0830151614d8360c0840182600f0b9052565b5060e0830151614d9860e0840182600f0b9052565b5061010080840151611a1d82850182600f0b9052565b6000816000190483118215151615614dc857614dc861472a565b500290565b80516001600160701b038116811461128857600080fd5b600080600060608486031215614df957600080fd5b614e0284614dcd565b9250614e1060208501614dcd565b9150604084015163ffffffff811681146148da57600080fd5b634e487b7160e01b600052603160045260246000fd5b602081526000610f576020830184614c8a56feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eba2646970667358221220ca8cb79aecc7b503570a1cbece532f77847522a0ba99ab18bfc87bc3f6deff0264736f6c63430008090033000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000bc3c01d954282eed8433da4359c1ac1443a7d09a0000000000000000000000007bf2392bd078c8353069cffeacc67c094079be230000000000000000000000007fa86681a7c19416950bae6c04a5116f3b07116d00000000000000000000000000000000000000000000000007ae147ae147ae140000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c4

Deployed Bytecode

0x60806040526004361061004a5760003560e01c8063405370491461004f5780635ec50709146100a1578063677956f1146100c9578063a2e62045146100dc578063a43e5b6d146100f3575b600080fd5b34801561005b57600080fd5b5061006f61006a366004614501565b610106565b60408051600f95860b815293850b602085015291840b9183019190915290910b60608201526080015b60405180910390f35b6100b46100af366004614565565b6101bf565b60408051928352602083019190915201610098565b6100b46100d73660046145d8565b61034c565b3480156100e857600080fd5b506100f161036c565b005b6100b4610101366004614631565b610386565b60008080808080600080516020614e7383398151915290504281600c0154146101395761013281610542565b9150610146565b6101438142610665565b91505b60006101976040518060c001604052808e6001600160a01b031681526020018d6001600160401b031681526020018c600f0b815260200185600f0b81526020018b81526020018a1515815250610695565b805160208201516040830151606090930151919f909e50919c509a5098505050505050505050565b600080336001600160a01b038916148061021b57506001600160a01b03881660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff165b61025b5760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b600061026684610b31565b905060008461029a57610295610280600f89900b88610b86565b600080516020614e7383398151915290610bee565b61029c565b855b90506102ab8a83836000610c29565b6102c06102b9866001610f2a565b89896104fd565b93506102d06102b9866000610f2a565b92506102dd898588610f5e565b6102e88a8488610f5e565b60408051848152602081018890526000818301526001606082015290516001600160a01b038b811692908d16917fa30eb1f1bb0892af5e3389941c42bc7f44a8bf7b6071ecfe3e964673c908f2b29181900360800190a35050965096945050505050565b60008061035e87878787876000610f7e565b915091509550959350505050565b610383600080516020614e7383398151915261122d565b50565b600080341580610394575085155b6103e05760405162461bcd60e51b815260206004820152601c60248201527f76616c756520616e6420616d6f756e74496e4d617820706173736564000000006044820152606401610252565b866104b457600080516020614e7383398151915260006103ff8261122d565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a08101919091529091503381526001600160401b038f166020820152600f8e810b604083015282900b6060820152608081018d90528b151560a0820152600061047282610695565b90506104ad61049582602001518360000151600f0b61128d90919063ffffffff16565b6104a88f876112c190919063ffffffff16565b6112ef565b9a50505050505b34156104cc576104c68786868661130a565b506104db565b6104d987878787876117f0565b505b6104ea8c8c8c8c8c6001610f7e565b915091509a509a98505050505050505050565b600081600f0b6080846001600160401b0316901b60f886600781111561052557610525614714565b61053092911b614740565b61053a9190614740565b949350505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561059557600080fd5b505afa1580156105a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105cd9190614758565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561062157600080fd5b505afa158015610635573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106599190614758565b905061053a828261198a565b60006011830181610678610e1085614787565b8152602081019190915260400160002054600f0b90505b92915050565b60408051608081018252600080825260208201819052918101829052606081019190915260008260400151600f0b1380156106d7575060008260600151600f0b135b80156106f05750600082602001516001600160401b0316115b61072b5760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964206172677360a01b6044820152606401610252565b60808201517fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ee54600080516020614e73833981519152916000916107799190600160a01b900460ff16611a25565b60a08501519091506000806107a46107918685611a3b565b61079b8786611a54565b87919086611ac7565b91509150600081600f0b136107e45760405162461bcd60e51b81526020600482015260066024820152656e6f206c697160d01b6044820152606401610252565b600061080c4289602001516001600160401b0316610802919061479b565b6301e13380611b4f565b8654600188015460608b01516040808d0151905163c4afd30d60e01b81526001600160a01b0394851660048201529284166024840152600f91820b6044840152810b606483015283900b608482015286151560a48201529192506000917f000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf9091169063c4afd30d9060c40160206040518083038186803b1580156108af57600080fd5b505afa1580156108c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e791906147b2565b9050600081600f0b136109265760405162461bcd60e51b81526020600482015260076024820152660766f6c203d20360cc1b6044820152606401610252565b600080600073c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f5630313686c6040518061014001604052806109678889600f0b611b8690919063ffffffff16565b600f0b81526020018f60400151600f0b81526020018f60600151600f0b815260200188600f0b81526020018a600f0b815260200189600f0b81526020016109ba8d8b600f0b611bbc90919063ffffffff16565b600f0b8152602001600160401b600f0b8152602001674ccccccccccccccd600f0b81526020018b15158152506040518263ffffffff1660e01b8152600401610a0291906147cf565b60606040518083038186803b158015610a1a57600080fd5b505af4158015610a2e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a529190614898565b92509250925087610a7057610a6b600f84900b8a611b86565b610a90565b60608c0151610a9090610a87600f86900b8c611b86565b600f0b90611bef565b600f0b808c52610ac0907f00000000000000000000000000000000000000000000000007ae147ae147ae14611b86565b600f90810b60208d015282810b60408d015281900b60608c01528b51600090610af490610aec90611c47565b612710611b4f565b60208d0151909150610b0990600f0b82611b86565b8c602001818151610b1a91906148e5565b600f0b9052509a9c9b505050505050505050505050565b600081610b5657600080516020614e73833981519152546001600160a01b031661068f565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b600081610b955750600061068f565b600083600f0b1215610ba657600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b03811115610bd557600080fd5b60401b8119811115610be657600080fd5b019392505050565b600080610c0c838560030160149054906101000a900460ff16611a25565b905061053a818560030160159054906101000a900460ff166112ef565b80610e52577f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b0316836001600160a01b03161415610e11573415610e0c5781341115610d92577f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015610cd157600080fd5b505af1158015610ce5573d6000803e3d6000fd5b50505050506000336001600160a01b03168334610d02919061479b565b604051600081818185875af1925050503d8060008114610d3e576040519150601f19603f3d011682016040523d82523d6000602084013e610d43565b606091505b5050905080610d885760405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606401610252565b6000925050610e52565b34820391507f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610df257600080fd5b505af1158015610e06573d6000803e3d6000fd5b50505050505b610e52565b3415610e525760405162461bcd60e51b815260206004820152601060248201526f1b9bdd0815d155120819195c1bdcda5d60821b6044820152606401610252565b8115610f24576040516323b872dd60e01b81526001600160a01b038581166004830152306024830152604482018490528416906323b872dd90606401602060405180830381600087803b158015610ea857600080fd5b505af1158015610ebc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee09190614935565b610f245760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b6044820152606401610252565b50505050565b60008215610f485781610f3e576005610f41565b60045b905061068f565b81610f54576007610f57565b60065b9392505050565b610f7983838360405180602001604052806000815250611d11565b505050565b600080600080516020614e73833981519152610f9d4262015180614740565b896001600160401b03161015610fe35760405162461bcd60e51b815260206004820152600b60248201526a657870203c20312064617960a81b6044820152606401610252565b610ff0426277f880614740565b896001600160401b0316106110375760405162461bcd60e51b815260206004820152600d60248201526c657870203e203930206461797360981b6044820152606401610252565b6110436170808a614952565b6001600160401b0316156110995760405162461bcd60e51b815260206004820152601c60248201527f657870206d75737420626520382d686f757220696e6372656d656e74000000006044820152606401610252565b60006110a48261122d565b9050861561112c57600181600f0b901b600f0b89600f0b131580156110e55750600a6110d1826008614978565b6110db9190614a0d565b600f0b89600f0b12155b6111275760405162461bcd60e51b8152602060048201526013602482015272737472696b65206f7574206f662072616e676560681b6044820152606401610252565b6111a7565b600a61113982600c614978565b6111439190614a0d565b600f0b89600f0b131580156111655750600181600f0b901d600f0b89600f0b12155b6111a75760405162461bcd60e51b8152602060048201526013602482015272737472696b65206f7574206f662072616e676560681b6044820152606401610252565b6111b682338c8c8b8d87611e38565b9094509250856111c68486614740565b11156112035760405162461bcd60e51b815260206004820152600c60248201526b065786365737320736c6970760a41b6044820152606401610252565b6112203361121089610b31565b61121a8688614740565b88610c29565b5050965096945050505050565b60004282600c015414156112455761068f8242610665565b61124e82610542565b905061125a8242610665565b600f0b61126c5761126c8242836120fc565b42600c83015561127d82600161217a565b61128882600061217a565b919050565b6000600f83810b9083900b0160016001607f1b031981128015906112b8575060016001607f1b038113155b610f5757600080fd5b6000816112dc576003830154600160a81b900460ff16610f57565b505060030154600160a01b900460ff1690565b6000610f576112ff83600a614b2f565b600f85900b90610b86565b60607f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b03168484600081811061134957611349614b3e565b905060200201602081019061135e9190614b54565b6001600160a01b0316146113b45760405162461bcd60e51b815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f504154480000006044820152606401610252565b611440826113e2577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f611404565b7f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c45b86868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250889250612205915050565b9050348160008151811061145657611456614b3e565b6020026020010151111561147c5760405162461bcd60e51b815260040161025290614b6f565b7f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663d0e30db0826000815181106114be576114be614b3e565b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b1580156114f157600080fd5b505af1158015611505573d6000803e3d6000fd5b50505050507f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663a9059cbb6115df84611567577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f611589565b7f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c45b8787600081811061159c5761159c614b3e565b90506020020160208101906115b19190614b54565b888860018181106115c4576115c4614b3e565b90506020020160208101906115d99190614b54565b87612393565b836000815181106115f2576115f2614b3e565b60200260200101516040518363ffffffff1660e01b815260040161162b9291906001600160a01b03929092168252602082015260400190565b602060405180830381600087803b15801561164557600080fd5b505af1158015611659573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061167d9190614935565b61168957611689614bb6565b6116ca818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152503392508791506124949050565b806000815181106116dd576116dd614b3e565b602002602001015134111561053a576000336001600160a01b03168260008151811061170b5761170b614b3e565b60200260200101513461171e919061479b565b6040805160008152602081019182905261173791614c0e565b60006040518083038185875af1925050503d8060008114611774576040519150601f19603f3d011682016040523d82523d6000602084013e611779565b606091505b50509050806117e75760405162461bcd60e51b815260206004820152603460248201527f5472616e7366657248656c7065723a3a736166655472616e736665724554483a60448201527308115512081d1c985b9cd9995c8819985a5b195960621b6064820152608401610252565b50949350505050565b606061187e82611820577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f611842565b7f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c45b87868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250889250612205915050565b9050848160008151811061189457611894614b3e565b602002602001015111156118ba5760405162461bcd60e51b815260040161025290614b6f565b611940336118ec84611567577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f611589565b836000815181106118ff576118ff614b3e565b60200260200101518787600081811061191a5761191a614b3e565b905060200201602081019061192f9190614b54565b6001600160a01b03169291906126f7565b611981818585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152503392508791506124949050565b95945050505050565b60008161199657600080fd5b6000808412156119ab57836000039350600190505b60008312156119bd5760009290920391155b60006119c98585612751565b905081156119f7576001607f1b816001600160801b031611156119eb57600080fd5b600003915061068f9050565b60016001607f1b03816001600160801b03161115611a1457600080fd5b915061068f9050565b505092915050565b6000610f5783611a3684600a614b2f565b611b4f565b6000611a4783836128b3565b9050610f578382846128da565b600080611a7183611a66576001611a69565b60005b6000806104fd565b831515600090815260138601602052604090206001015490915061053a90600080516020614e5383398151915260008481526020919091526040902054611ab8919061479b565b611ac286866112c1565b611a25565b801515600090815260138501602052604081206001810154829190829015801590611af25750815415155b8015611aff575081544210155b15611b3d576001820154611b1790611ac28a886112c1565b9050611b27600f87900b8261128d565b9250611b368888888689612a9c565b9350611b44565b8693508592505b505094509492505050565b600081611b5b57600080fd5b6000611b678484612751565b905060016001607f1b036001600160801b0382161115610f5757600080fd5b6000600f83810b9083900b0260401d60016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b6000600f82810b9084900b0360016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b600081600f0b60001415611c0257600080fd5b600082600f0b604085600f0b901b81611c1d57611c1d614771565b05905060016001607f1b031981128015906112b8575060016001607f1b03811315610f5757600080fd5b60007f0000000000000000000000007fa86681a7c19416950bae6c04a5116f3b07116d6001600160a01b031615611288576040516303793c8d60e11b81526001600160a01b0383811660048301527f0000000000000000000000007fa86681a7c19416950bae6c04a5116f3b07116d16906306f2791a9060240160206040518083038186803b158015611cd957600080fd5b505afa158015611ced573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068f9190614758565b6001600160a01b038416611d715760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610252565b611d9033600086611d8187612ba2565b611d8a87612ba2565b86612bed565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611de3908490614740565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b60008042876001600160401b031611611e7d5760405162461bcd60e51b8152602060048201526007602482015266195e1c1a5c995960ca1b6044820152606401610252565b8860050154841015611ebd5760405162461bcd60e51b81526020600482015260096024820152681d1bdbc81cdb585b1b60ba1b6044820152606401610252565b600085611ee157611edc611ed5600f89900b87610b86565b8b90610bee565b611ee3565b845b861515600090815260138c016020526040902060010154909150600080516020614e538339815191526000611f1789613129565b815260200190815260200160002054611f30919061479b565b811115611f6b5760405162461bcd60e51b8152602060048201526009602482015268696e737566206c697160b81b6044820152606401610252565b506000611fbd6040518060c001604052808b6001600160a01b031681526020018a6001600160401b0316815260200189600f0b815260200186600f0b8152602001878152602001881515815250610695565b8051909150611fd0906104a88c896112c1565b6020820151909350611fe6906104a88c896112c1565b91506000611fff611ff8886001610f2a565b8a8a6104fd565b90506000612018612011896000610f2a565b8b8b6104fd565b90506120258b8389610f5e565b60006120318d8a611a54565b90506120418d8d8a89868e61317d565b600061204d8e8b611a54565b905061205b8e83838d61341a565b61208e7f0000000000000000000000007bf2392bd078c8353069cffeacc67c094079be236120888c613497565b88610f5e565b60408051858152602081018b905290810188905260608101879052600f89900b60808201526001600160a01b038e16907f4719d073a940c087132bffc1aac4f3837ca2c820d95574727828ce38f7fbc8c79060a00160405180910390a2505050505097509795505050505050565b600061210a610e1084614787565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061214360ff8083169061479b565b6001901b846012016000600884901c8152602001908152602001600020600082825461216f9190614740565b909155505050505050565b801515600090815260138301602052604090208054158061219b5750805442105b156121a557505050565b60006121b18484611a54565b90506121e784826121e16121d68660010154611ac2898b6112c190919063ffffffff16565b600f86900b9061128d565b8661341a565b50501515600090815260139091016020526040812081815560010155565b60606002835110156122595760405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f5041544800006044820152606401610252565b82516001600160401b0381111561227257612272614bcc565b60405190808252806020026020018201604052801561229b578160200160208202803683370190505b5090508381600183516122ae919061479b565b815181106122be576122be614b3e565b6020026020010181815250506000600184516122da919061479b565b90505b80156117e75760008061232e88876122f660018761479b565b8151811061230657612306614b3e565b602002602001015188868151811061232057612320614b3e565b6020026020010151886134eb565b9150915061235684848151811061234757612347614b3e565b602002602001015183836135c6565b8461236260018661479b565b8151811061237257612372614b3e565b6020026020010181815250505050808061238b90614c2a565b9150506122dd565b60008060006123a286866136ec565b6040516bffffffffffffffffffffffff19606084811b8216602084015283901b166034820152919350915087906048016040516020818303038152906040528051906020012085612428576040518060400160405280602081526020017f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f81525061245f565b6040518060400160405280602081526020017fe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c63038152505b60405160200161247193929190614c41565b60408051601f198184030181529190528051602090910120979650505050505050565b60005b600184516124a5919061479b565b8110156126f0576000808583815181106124c1576124c1614b3e565b6020026020010151868460016124d79190614740565b815181106124e7576124e7614b3e565b60200260200101519150915060006124ff83836136ec565b509050600088612510866001614740565b8151811061252057612520614b3e565b60200260200101519050600080836001600160a01b0316866001600160a01b03161461254e57826000612552565b6000835b91509150600060028b51612566919061479b565b881061257257896125ed565b6125ed896125a0577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f6125c2565b7f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c45b878d6125cf8c6002614740565b815181106125df576125df614b3e565b60200260200101518c612393565b90506126478961261d577f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f61263f565b7f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c45b88888c612393565b6001600160a01b031663022c0d9f84848460006040519080825280601f01601f191660200182016040528015612684576020820181803683370190505b506040518563ffffffff1660e01b81526004016126a49493929190614cb6565b600060405180830381600087803b1580156126be57600080fd5b505af11580156126d2573d6000803e3d6000fd5b505050505050505050505080806126e890614ce3565b915050612497565b5050505050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610f249085906137e4565b60008161275d57600080fd5b60006001600160c01b0384116127885782604085901b8161278057612780614771565b04905061289f565b60c084811c64010000000081106127a1576020918201911c5b6201000081106127b3576010918201911c5b61010081106127c4576008918201911c5b601081106127d4576004918201911c5b600481106127e4576002918201911c5b600281106127f3576001820191505b60bf820360018603901c6001018260ff0387901b8161281457612814614771565b0492506001600160801b0383111561282b57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b82811015612857576001820391505b608084901b9290038281101561286e576001820391505b829003608084901c821461288457612884614bb6565b88818161289357612893614771565b04870196505050505050505b6001600160801b03811115610f5757600080fd5b6000816128ce576008830154600160801b9004600f0b610f57565b505060090154600f0b90565b600080826128ec5784600a01546128f2565b84600b01545b6128fc904261479b565b905061a8c081111561291b5761291461a8c08261479b565b9050612924565b83915050610f57565b600061293282613840611b4f565b9050600061294585611a66576001611a69565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020614e5383398151915290935290832054939450926129a3916129939161479b565b61299d908461479b565b83611b4f565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e66666666666666660808201819052600160401b60a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b815291925073c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f591634916d70d91612a3f91600401614cfe565b60206040518083038186803b158015612a5757600080fd5b505af4158015612a6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8f91906147b2565b9998505050505050505050565b60008082612ab1576019870154600f0b612ac1565b6019870154600160801b9004600f0b5b905080600f0b60001415612ad957506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b606482015273c7a7275bc25a7bf07c6d0c2f8784c5450cb9b8f59063e101a89b9060840160206040518083038186803b158015612b3f57600080fd5b505af4158015612b53573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b7791906147b2565b915067b33333333333333382600f0b1215612b985767b33333333333333391505b5095945050505050565b60408051600180825281830190925260609160009190602080830190803683370190505090508281600081518110612bdc57612bdc614b3e565b602090810291909101015292915050565b612bfb8686868686866138b6565b600080516020614e7383398151915260005b845181101561311f576000858281518110612c2a57612c2a614b3e565b602002602001015190506000858381518110612c4857612c48614b3e565b602002602001015190508060001415612c6257505061310d565b6001600160a01b038916612c7f57612c7d6015850183613a9a565b505b6001600160a01b038816158015612caf57506000828152600080516020614e538339815191526020526040902054155b15612cc357612cc16015850183613aa6565b505b7f0000000000000000000000000000000000000000000000000000000000000000821480612d1057507f010000000000000000000000000000000000000000000000000000000000000082145b80612d3a57507f020000000000000000000000000000000000000000000000000000000000000082145b80612d6457507f030000000000000000000000000000000000000000000000000000000000000082145b15612e54576001600160a01b03891615801590612d8957506001600160a01b03881615155b15612e545760007f0000000000000000000000000000000000000000000000000000000000000000831480612ddd57507f020000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d87016020908152604080832084151584529091529020549091504290612e179062015180614740565b10612e525760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b6044820152606401610252565b505b7f0000000000000000000000000000000000000000000000000000000000000000821480612ea157507f010000000000000000000000000000000000000000000000000000000000000082145b15613049577f000000000000000000000000000000000000000000000000000000000000000082146000612ed58683613ab2565b90506001600160a01b038b1615612fff576000612ef28c86613acc565b90508181118015612f0c5750612f088285614740565b8111155b15612fa4576001600160a01b038c166000908152601488016020908152604080832086151580855260138c0184528285205485529083528184209084529091529020548490612f5b908361479b565b1015612f995760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b6044820152606401610252565b612fa4878d85613b7f565b6001600160a01b038b1615612ffd57612fd8878d858c8a81518110612fcb57612fcb614b3e565b6020026020010151613c30565b612ffd878c858c8a81518110612ff057612ff0614b3e565b6020026020010151613d70565b505b6001600160a01b038a161561304657600061301a8b86613acc565b90508181111580156130345750816130328583614740565b115b1561304457613044878c85613e94565b505b50505b60f882901c826001600160a01b038b161580159061306f57506001600160a01b038a1615155b80156130a75750600582600781111561308a5761308a614714565b14806130a7575060078260078111156130a5576130a5614714565b145b1561310857600060058360078111156130c2576130c2614714565b1490506000816130e9576130e46130dd600f85900b87610b86565b8990610bee565b6130eb565b845b90506130f9888e8484613c30565b613105888d8484613d70565b50505b505050505b8061311781614ce3565b915050612c0d565b5050505050505050565b600081613156577f010000000000000000000000000000000000000000000000000000000000000061068f565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b600061318882613129565b9050826000836131af576131aa6131a3600f84900b89610b86565b8a90610bee565b6131b1565b865b90505b801561340f578315156000908152600f8a01602090815260408083208380529091528120546001600160a01b0316906131ed8286613acc565b90506131f98b87613ab2565b8110156132125761320b8b8388613b7f565b50506131b4565b61321d8b8388613f3c565b61324b5761322c828683613f76565b61323f8261323988613497565b83610f5e565b61320b8b838884613c30565b6000836132588a82614740565b8d6014016000866001600160a01b03166001600160a01b0316815260200190815260200160002060008f60130160008c15151515815260200190815260200160002060000154815260200190815260200160002060008a15151515815260200190815260200160002054846132cd919061479b565b6132d79190614dae565b6132e19190614787565b9050806132f0575050506131b4565b838111156132fb5750825b600084613308838c614dae565b6133129190614787565b905061331e818b61479b565b995061332a828661479b565b94506133388d858a84613d70565b61334c8488613347848661479b565b613f76565b876133775761337461336d8361336489600f0b614108565b600f0b90610b86565b8e9061415e565b91505b61338e848a87156133885784610f5e565b8d610f5e565b8b6001600160a01b0316846001600160a01b03167fa30eb1f1bb0892af5e3389941c42bc7f44a8bf7b6071ecfe3e964673c908f2b28b886000146133d257856133d4565b8e5b60408051928352602083019190915281018590526000606082015260800160405180910390a3613404828c61479b565b9a50505050506131b4565b505050505050505050565b60006134268583611a3b565b905060006134378683878787612a9c565b9050613444868285614199565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b6000816134c4577f030000000000000000000000000000000000000000000000000000000000000061068f565b7f020000000000000000000000000000000000000000000000000000000000000092915050565b60008060006134fa86866136ec565b50905060008061350c89898989612393565b6001600160a01b0316630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561354457600080fd5b505afa158015613558573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061357c9190614de4565b506001600160701b031691506001600160701b03169150826001600160a01b0316886001600160a01b0316146135b35780826135b6565b81815b909a909950975050505050505050565b600080841161362c5760405162461bcd60e51b815260206004820152602c60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4f60448201526b155514155517d05353d5539560a21b6064820152608401610252565b60008311801561363c5750600082115b6136995760405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b6064820152608401610252565b60006136a58585614dae565b6136b1906103e8614dae565b905060006136bf868561479b565b6136cb906103e5614dae565b90506136d78183614787565b6136e2906001614740565b9695505050505050565b600080826001600160a01b0316846001600160a01b0316141561375f5760405162461bcd60e51b815260206004820152602560248201527f556e697377617056324c6962726172793a204944454e544943414c5f41444452604482015264455353455360d81b6064820152608401610252565b826001600160a01b0316846001600160a01b03161061377f578284613782565b83835b90925090506001600160a01b0382166137dd5760405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f4144445245535300006044820152606401610252565b9250929050565b6000613839826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166141f09092919063ffffffff16565b805190915015610f7957808060200190518101906138579190614935565b610f795760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610252565b836001600160a01b0316856001600160a01b031614613a92576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020614e53833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b8751811015613a8c57600087828151811061396257613962614b3e565b602002602001015190506000811115613a7957600089838151811061398957613989614b3e565b6020026020010151905060006001600160a01b03168c6001600160a01b031614156139d757600081815260208890526040812080548492906139cc908490614740565b90915550613a0d9050565b816139e28d83613acc565b1415613a0d576000818152602087905260409020613a00908d6141ff565b50613a0b8582613aa6565b505b6001600160a01b038b16613a445760008181526020889052604081208054849290613a3990849061479b565b90915550613a779050565b613a4e8b82613acc565b613a77576000818152602087905260409020613a6a908c614214565b50613a758482613a9a565b505b505b5080613a8481614ce3565b915050613945565b50505050505b505050505050565b6000610f578383614225565b6000610f578383614274565b600081613ac3578260040154610f57565b50506005015490565b60006001600160a01b038316613b385760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b6064820152608401610252565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6001600160a01b038216613b9257600080fd5b8015156000908152600f84016020908152604080832060108701909252909120613bbd84838361435f565b613bc8575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f000000000000000000000000bc3c01d954282eed8433da4359c1ac1443a7d09a1663edaf7d5b86308786613c9b898261479b565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b158015613cfe57600080fd5b505af1158015613d12573d6000803e3d6000fd5b505050508282613d22919061479b565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055613d54838261479b565b9315156000908152601890960160205250506040909320555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f000000000000000000000000bc3c01d954282eed8433da4359c1ac1443a7d09a1663edaf7d5b86308786613ddb8982614740565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b158015613e3e57600080fd5b505af1158015613e52573d6000803e3d6000fd5b505050508282613e629190614740565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055613d548382614740565b6001600160a01b038216613ea757600080fd5b8015156000908152600f84016020908152604080832060108701909252909120613ed284838361435f565b15613ede575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061198157504210949350505050565b6001600160a01b038316613fd85760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b6064820152608401610252565b61400633846000613fe886612ba2565b613ff186612ba2565b60405180602001604052806000815250612bed565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156140a75760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b6064820152608401610252565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600081600f0b6000141561411b57600080fd5b600082600f0b600160801b8161413357614133614771565b05905060016001607f1b03198112801590614155575060016001607f1b038113155b61068f57600080fd5b60008061417c838560030160159054906101000a900460ff16611a25565b905061053a818560030160149054906101000a900460ff166112ef565b80156141c9576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b606061053a84846000856143ab565b6000610f57836001600160a01b038416614274565b6000610f57836001600160a01b0384165b600081815260018301602052604081205461426c5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561068f565b50600061068f565b6000818152600183016020526040812054801561435557600061429860018361479b565b855490915060009086906142ae9060019061479b565b815481106142be576142be614b3e565b90600052602060002001549050808660000183815481106142e1576142e1614b3e565b6000918252602090912001556142f8826001614740565b6000828152600188016020526040902055855486908061431a5761431a614e29565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061068f565b600091505061068f565b6001600160a01b0383811660009081526020849052604081205490911615158061053a57506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6060843b61440f5760405162461bcd60e51b815260206004820152602b60248201527f416464726573735574696c733a2066756e6374696f6e2063616c6c20746f206e60448201526a1bdb8b58dbdb9d1c9858dd60aa1b6064820152608401610252565b600080866001600160a01b0316858760405161442b9190614c0e565b60006040518083038185875af1925050503d8060008114614468576040519150601f19603f3d011682016040523d82523d6000602084013e61446d565b606091505b5091509150811561448157915061053a9050565b8051156144915780518082602001fd5b8360405162461bcd60e51b81526004016102529190614e3f565b80356001600160a01b038116811461128857600080fd5b80356001600160401b038116811461128857600080fd5b80600f0b811461038357600080fd5b801515811461038357600080fd5b8035611288816144e8565b600080600080600060a0868803121561451957600080fd5b614522866144ab565b9450614530602087016144c2565b93506040860135614540816144d9565b9250606086013591506080860135614557816144e8565b809150509295509295909350565b60008060008060008060c0878903121561457e57600080fd5b614587876144ab565b9550614595602088016144ab565b94506145a3604088016144c2565b935060608701356145b3816144d9565b92506080870135915060a08701356145ca816144e8565b809150509295509295509295565b600080600080600060a086880312156145f057600080fd5b6145f9866144c2565b94506020860135614609816144d9565b9350604086013592506060860135614620816144e8565b949793965091946080013592915050565b6000806000806000806000806000806101208b8d03121561465157600080fd5b61465a8b6144c2565b995060208b013561466a816144d9565b985060408b0135975060608b0135614681816144e8565b965060808b0135955060a08b0135945060c08b0135935060e08b01356001600160401b03808211156146b257600080fd5b818d0191508d601f8301126146c657600080fd5b8135818111156146d557600080fd5b8e60208260051b85010111156146ea57600080fd5b6020830195508094505050506147036101008c016144f6565b90509295989b9194979a5092959850565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600082198211156147535761475361472a565b500190565b60006020828403121561476a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261479657614796614771565b500490565b6000828210156147ad576147ad61472a565b500390565b6000602082840312156147c457600080fd5b8151610f57816144d9565b8151600f0b8152610140810160208301516147ef6020840182600f0b9052565b5060408301516148046040840182600f0b9052565b5060608301516148196060840182600f0b9052565b50608083015161482e6080840182600f0b9052565b5060a083015161484360a0840182600f0b9052565b5060c083015161485860c0840182600f0b9052565b5060e083015161486d60e0840182600f0b9052565b506101008084015161488382850182600f0b9052565b50506101208381015180151584830152611a1d565b6000806000606084860312156148ad57600080fd5b83516148b8816144d9565b60208501519093506148c9816144d9565b60408501519092506148da816144d9565b809150509250925092565b600081600f0b83600f0b600081128160016001607f1b0319018312811516156149105761491061472a565b8160016001607f1b0301831381161561492b5761492b61472a565b5090039392505050565b60006020828403121561494757600080fd5b8151610f57816144e8565b60006001600160401b038084168061496c5761496c614771565b92169190910692915050565b600081600f0b83600f0b60016001607f1b036000821360008413838304851182821616156149a8576149a861472a565b60016001607f1b031960008512828116878305871216156149cb576149cb61472a565b600087129250858205871284841616156149e7576149e761472a565b858505871281841616156149fd576149fd61472a565b5050509290910295945050505050565b600081600f0b83600f0b80614a2457614a24614771565b60016001607f1b0319821460001982141615614a4257614a4261472a565b90059392505050565b600181815b80851115614a86578160001904821115614a6c57614a6c61472a565b80851615614a7957918102915b93841c9390800290614a50565b509250929050565b600082614a9d5750600161068f565b81614aaa5750600061068f565b8160018114614ac05760028114614aca57614ae6565b600191505061068f565b60ff841115614adb57614adb61472a565b50506001821b61068f565b5060208310610133831016604e8410600b8410161715614b09575081810a61068f565b614b138383614a4b565b8060001904821115614b2757614b2761472a565b029392505050565b6000610f5760ff841683614a8e565b634e487b7160e01b600052603260045260246000fd5b600060208284031215614b6657600080fd5b610f57826144ab565b60208082526027908201527f556e69737761705632526f757465723a204558434553534956455f494e50555460408201526617d05353d5539560ca1b606082015260800190565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60005b83811015614bfd578181015183820152602001614be5565b83811115610f245750506000910152565b60008251614c20818460208701614be2565b9190910192915050565b600081614c3957614c3961472a565b506000190190565b60ff60f81b81526bffffffffffffffffffffffff198460601b16600182015282601582015260008251614c7b816035850160208701614be2565b91909101603501949350505050565b60008151808452614ca2816020860160208601614be2565b601f01601f19169290920160200192915050565b84815283602082015260018060a01b03831660408201526080606082015260006136e26080830184614c8a565b6000600019821415614cf757614cf761472a565b5060010190565b6000610120820190508251600f0b82526020830151600f0b60208301526040830151614d2f6040840182600f0b9052565b506060830151614d446060840182600f0b9052565b506080830151614d596080840182600f0b9052565b5060a0830151614d6e60a0840182600f0b9052565b5060c0830151614d8360c0840182600f0b9052565b5060e0830151614d9860e0840182600f0b9052565b5061010080840151611a1d82850182600f0b9052565b6000816000190483118215151615614dc857614dc861472a565b500290565b80516001600160701b038116811461128857600080fd5b600080600060608486031215614df957600080fd5b614e0284614dcd565b9250614e1060208501614dcd565b9150604084015163ffffffff811681146148da57600080fd5b634e487b7160e01b600052603160045260246000fd5b602081526000610f576020830184614c8a56feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eba2646970667358221220ca8cb79aecc7b503570a1cbece532f77847522a0ba99ab18bfc87bc3f6deff0264736f6c63430008090033

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

000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000bc3c01d954282eed8433da4359c1ac1443a7d09a0000000000000000000000007bf2392bd078c8353069cffeacc67c094079be230000000000000000000000007fa86681a7c19416950bae6c04a5116f3b07116d00000000000000000000000000000000000000000000000007ae147ae147ae140000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c4

-----Decoded View---------------
Arg [0] : ivolOracle (address): 0xC4B2C51f969e0713E799De73b7f130Fb7Bb604CF
Arg [1] : weth (address): 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
Arg [2] : premiaMining (address): 0xbC3c01D954282eEd8433da4359C1ac1443a7d09A
Arg [3] : feeReceiver (address): 0x7bf2392bd078C8353069CffeAcc67c094079be23
Arg [4] : feeDiscountAddress (address): 0x7Fa86681A7c19416950bAE6c04A5116f3b07116D
Arg [5] : fee64x64 (int128): 553402322211286548
Arg [6] : uniswapV2Factory (address): 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
Arg [7] : sushiswapFactory (address): 0xc35DADB65012eC5796536bD9864eD8773aBc74C4

-----Encoded View---------------
8 Constructor Arguments found :
Arg [0] : 000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf
Arg [1] : 00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1
Arg [2] : 000000000000000000000000bc3c01d954282eed8433da4359c1ac1443a7d09a
Arg [3] : 0000000000000000000000007bf2392bd078c8353069cffeacc67c094079be23
Arg [4] : 0000000000000000000000007fa86681a7c19416950bae6c04a5116f3b07116d
Arg [5] : 00000000000000000000000000000000000000000000000007ae147ae147ae14
Arg [6] : 0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
Arg [7] : 000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c4


Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.