ETH Price: $3,755.55 (-2.46%)

Contract

0x0458ea5F4CD00E873264Be2031Ceb8f9d9b3116c

Overview

ETH Balance

0 ETH

ETH Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Advanced mode:
Parent Transaction Hash Block From To
View All Internal Transactions

Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
CurveTricryptoOptimized

Compiler Version
vyper:0.3.10

Optimization Enabled:
N/A

Other Settings:
default evmVersion, None license

Contract Source Code (Vyper language format)

# pragma version 0.3.10
# pragma optimize gas
# pragma evm-version paris
"""
@title CurveTricryptoOptimized
@author Curve.Fi
@license Copyright (c) Curve.Fi, 2023 - all rights reserved
@notice A Curve AMM pool for 3 unpegged assets (e.g. WETH, BTC, USD).
@dev All prices in the AMM are with respect to the first token in the pool.
"""

from vyper.interfaces import ERC20
implements: ERC20  # <--------------------- AMM contract is also the LP token.

# --------------------------------- Interfaces -------------------------------

interface Math:
    def geometric_mean(_x: uint256[N_COINS]) -> uint256: view
    def wad_exp(_power: int256) -> uint256: view
    def cbrt(x: uint256) -> uint256: view
    def reduction_coefficient(
        x: uint256[N_COINS], fee_gamma: uint256
    ) -> uint256: view
    def newton_D(
        ANN: uint256,
        gamma: uint256,
        x_unsorted: uint256[N_COINS],
        K0_prev: uint256
    ) -> uint256: view
    def get_y(
        ANN: uint256,
        gamma: uint256,
        x: uint256[N_COINS],
        D: uint256,
        i: uint256,
    ) -> uint256[2]: view
    def get_p(
        _xp: uint256[N_COINS], _D: uint256, _A_gamma: uint256[2],
    ) -> uint256[N_COINS-1]: view

interface Factory:
    def admin() -> address: view
    def fee_receiver() -> address: view
    def views_implementation() -> address: view

interface Views:
    def calc_token_amount(
        amounts: uint256[N_COINS], deposit: bool, swap: address
    ) -> uint256: view
    def get_dy(
        i: uint256, j: uint256, dx: uint256, swap: address
    ) -> uint256: view
    def get_dx(
        i: uint256, j: uint256, dy: uint256, swap: address
    ) -> uint256: view


# ------------------------------- Events -------------------------------------

event Transfer:
    sender: indexed(address)
    receiver: indexed(address)
    value: uint256

event Approval:
    owner: indexed(address)
    spender: indexed(address)
    value: uint256

event TokenExchange:
    buyer: indexed(address)
    sold_id: uint256
    tokens_sold: uint256
    bought_id: uint256
    tokens_bought: uint256
    fee: uint256
    packed_price_scale: uint256

event AddLiquidity:
    provider: indexed(address)
    token_amounts: uint256[N_COINS]
    fee: uint256
    token_supply: uint256
    packed_price_scale: uint256

event RemoveLiquidity:
    provider: indexed(address)
    token_amounts: uint256[N_COINS]
    token_supply: uint256

event RemoveLiquidityOne:
    provider: indexed(address)
    token_amount: uint256
    coin_index: uint256
    coin_amount: uint256
    approx_fee: uint256
    packed_price_scale: uint256

event NewParameters:
    mid_fee: uint256
    out_fee: uint256
    fee_gamma: uint256
    allowed_extra_profit: uint256
    adjustment_step: uint256
    ma_time: uint256
    xcp_ma_time: uint256

event RampAgamma:
    initial_A: uint256
    future_A: uint256
    initial_gamma: uint256
    future_gamma: uint256
    initial_time: uint256
    future_time: uint256

event StopRampA:
    current_A: uint256
    current_gamma: uint256
    time: uint256

event ClaimAdminFee:
    admin: indexed(address)
    tokens: uint256[N_COINS]


# ----------------------- Storage/State Variables ----------------------------

N_COINS: constant(uint256) = 3
PRECISION: constant(uint256) = 10**18  # <------- The precision to convert to.
PRECISIONS: immutable(uint256[N_COINS])

MATH: public(immutable(Math))
coins: public(immutable(address[N_COINS]))
factory: public(immutable(Factory))

price_scale_packed: uint256  # <------------------------ Internal price scale.
price_oracle_packed: uint256  # <------- Price target given by moving average.
cached_xcp_oracle: uint256  # <----------- EMA of totalSupply * virtual_price.

last_prices_packed: uint256
last_timestamp: public(uint256)    # idx 0 is for prices, idx 1 is for xcp.
last_xcp: public(uint256)
xcp_ma_time: public(uint256)

initial_A_gamma: public(uint256)
initial_A_gamma_time: public(uint256)

future_A_gamma: public(uint256)
future_A_gamma_time: public(uint256)  # <------ Time when ramping is finished.
#         This value is 0 (default) when pool is first deployed, and only gets
#        populated by block.timestamp + future_time in `ramp_A_gamma` when the
#                      ramping process is initiated. After ramping is finished
#      (i.e. self.future_A_gamma_time < block.timestamp), the variable is left
#                                                            and not set to 0.

balances: public(uint256[N_COINS])
D: public(uint256)
xcp_profit: public(uint256)
xcp_profit_a: public(uint256)  # <--- Full profit at last claim of admin fees.

virtual_price: public(uint256)  # <------ Cached (fast to read) virtual price.
#                          The cached `virtual_price` is also used internally.

# Params that affect how price_scale get adjusted :
packed_rebalancing_params: public(uint256)  # <---------- Contains rebalancing
#               parameters allowed_extra_profit, adjustment_step, and ma_time.

# Fee params that determine dynamic fees:
packed_fee_params: public(uint256)  # <---- Packs mid_fee, out_fee, fee_gamma.

ADMIN_FEE: public(constant(uint256)) = 5 * 10**9  # <----- 50% of earned fees.
MIN_FEE: constant(uint256) = 5 * 10**5  # <-------------------------- 0.5 BPS.
MAX_FEE: constant(uint256) = 10 * 10**9
NOISE_FEE: constant(uint256) = 10**5  # <---------------------------- 0.1 BPS.

# ----------------------- Admin params ---------------------------------------

last_admin_fee_claim_timestamp: uint256
admin_lp_virtual_balance: uint256

MIN_RAMP_TIME: constant(uint256) = 86400
MIN_ADMIN_FEE_CLAIM_INTERVAL: constant(uint256) = 86400

A_MULTIPLIER: constant(uint256) = 10000
MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 100
MAX_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER * 1000
MAX_A_CHANGE: constant(uint256) = 10
MIN_GAMMA: constant(uint256) = 10**10
MAX_GAMMA: constant(uint256) = 5 * 10**16

PRICE_SIZE: constant(uint128) = 256 / (N_COINS - 1)
PRICE_MASK: constant(uint256) = 2**PRICE_SIZE - 1

# ----------------------- ERC20 Specific vars --------------------------------

name: public(immutable(String[64]))
symbol: public(immutable(String[32]))
decimals: public(constant(uint8)) = 18
version: public(constant(String[8])) = "v2.0.0"

balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])
totalSupply: public(uint256)
nonces: public(HashMap[address, uint256])

EIP712_TYPEHASH: constant(bytes32) = keccak256(
    "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"
)
EIP2612_TYPEHASH: constant(bytes32) = keccak256(
    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
)
VERSION_HASH: constant(bytes32) = keccak256(version)
NAME_HASH: immutable(bytes32)
CACHED_CHAIN_ID: immutable(uint256)
salt: public(immutable(bytes32))
CACHED_DOMAIN_SEPARATOR: immutable(bytes32)


# ----------------------- Contract -------------------------------------------

@external
def __init__(
    _name: String[64],
    _symbol: String[32],
    _coins: address[N_COINS],
    _math: address,
    _weth: address,  # unused but factory has it.
    _salt: bytes32,
    __packed_precisions: uint256,
    packed_A_gamma: uint256,
    packed_fee_params: uint256,
    packed_rebalancing_params: uint256,
    packed_prices: uint256,
):
    MATH = Math(_math)
    factory = Factory(msg.sender)
    name = _name
    symbol = _symbol
    coins = _coins

    PRECISIONS = self._unpack_3(__packed_precisions)  # <------- Precisions of
    #                      coins are calculated as 10**(18 - coin.decimals()).

    self.initial_A_gamma = packed_A_gamma  # <------------------- A and gamma.
    self.future_A_gamma = packed_A_gamma

    self.packed_rebalancing_params = packed_rebalancing_params  # <-- Contains
    #               rebalancing params: allowed_extra_profit, adjustment_step,
    #                                                         and ma_exp_time.

    self.packed_fee_params = packed_fee_params  # <-------------- Contains Fee
    #                                  params: mid_fee, out_fee and fee_gamma.

    self.price_scale_packed = packed_prices
    self.price_oracle_packed = packed_prices
    self.last_prices_packed = packed_prices
    self.last_timestamp = self._pack_2(block.timestamp, block.timestamp)
    self.xcp_profit_a = 10**18
    self.xcp_ma_time = 62324  # <--------- 12 hours default on contract start.

    #         Cache DOMAIN_SEPARATOR. If chain.id is not CACHED_CHAIN_ID, then
    #     DOMAIN_SEPARATOR will be re-calculated each time `permit` is called.
    #                   Otherwise, it will always use CACHED_DOMAIN_SEPARATOR.
    #                       see: `_domain_separator()` for its implementation.
    NAME_HASH = keccak256(name)
    salt = _salt
    CACHED_CHAIN_ID = chain.id
    CACHED_DOMAIN_SEPARATOR = keccak256(
        _abi_encode(
            EIP712_TYPEHASH,
            NAME_HASH,
            VERSION_HASH,
            chain.id,
            self,
            salt,
        )
    )

    log Transfer(empty(address), self, 0)  # <------- Fire empty transfer from
    #                                       0x0 to self for indexers to catch.


# ------------------- Token transfers in and out of the AMM ------------------


@internal
def _transfer_in(
    _coin_idx: uint256,
    _dx: uint256,
    sender: address,
    expect_optimistic_transfer: bool,
) -> uint256:
    """
    @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig`
            if it is not empty.
    @params _coin_idx uint256 Index of the coin to transfer in.
    @params dx amount of `_coin` to transfer into the pool.
    @params sender address to transfer `_coin` from.
    @params expect_optimistic_transfer bool True if pool expects user to transfer.
            This is only enabled for exchange_received.
    @return The amount of tokens received.
    """
    coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self)

    if expect_optimistic_transfer:  # Only enabled in exchange_received:
        # it expects the caller of exchange_received to have sent tokens to
        # the pool before calling this method.

        # If someone donates extra tokens to the contract: do not acknowledge.
        # We only want to know if there are dx amount of tokens. Anything extra,
        # we ignore. This is why we need to check if received_amounts (which
        # accounts for coin balances of the contract) is atleast dx.
        # If we checked for received_amounts == dx, an extra transfer without a
        # call to exchange_received will break the method.
        dx: uint256 = coin_balance - self.balances[_coin_idx]
        assert dx >= _dx  # dev: user didn't give us coins

        # Adjust balances
        self.balances[_coin_idx] += dx

        return dx

    # ----------------------------------------------- ERC20 transferFrom flow.

    # EXTERNAL CALL
    assert ERC20(coins[_coin_idx]).transferFrom(
        sender,
        self,
        _dx,
        default_return_value=True
    )

    dx: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) - coin_balance
    self.balances[_coin_idx] += dx
    return dx


@internal
def _transfer_out(_coin_idx: uint256, _amount: uint256, receiver: address):
    """
    @notice Transfer a single token from the pool to receiver.
    @dev This function is called by `remove_liquidity` and
         `remove_liquidity_one`, `_claim_admin_fees` and `_exchange` methods.
    @params _coin_idx uint256 Index of the token to transfer out
    @params _amount Amount of token to transfer out
    @params receiver Address to send the tokens to
    """

    # Adjust balances before handling transfers:
    self.balances[_coin_idx] -= _amount

    # EXTERNAL CALL
    assert ERC20(coins[_coin_idx]).transfer(
        receiver,
        _amount,
        default_return_value=True
    )


# -------------------------- AMM Main Functions ------------------------------


@external
@nonreentrant("lock")
def exchange(
    i: uint256,
    j: uint256,
    dx: uint256,
    min_dy: uint256,
    receiver: address = msg.sender
) -> uint256:
    """
    @notice Exchange using wrapped native token by default
    @param i Index value for the input coin
    @param j Index value for the output coin
    @param dx Amount of input coin being swapped in
    @param min_dy Minimum amount of output coin to receive
    @param receiver Address to send the output coin to. Default is msg.sender
    @return uint256 Amount of tokens at index j received by the `receiver
    """
    # _transfer_in updates self.balances here:
    dx_received: uint256 = self._transfer_in(
        i,
        dx,
        msg.sender,
        False
    )

    # No ERC20 token transfers occur here:
    out: uint256[3] = self._exchange(
        i,
        j,
        dx_received,
        min_dy,
    )

    # _transfer_out updates self.balances here. Update to state occurs before
    # external calls:
    self._transfer_out(j, out[0], receiver)

    # log:
    log TokenExchange(msg.sender, i, dx_received, j, out[0], out[1], out[2])

    return out[0]


@external
@nonreentrant('lock')
def exchange_received(
    i: uint256,
    j: uint256,
    dx: uint256,
    min_dy: uint256,
    receiver: address = msg.sender,
) -> uint256:
    """
    @notice Exchange: but user must transfer dx amount of coin[i] tokens to pool first.
            Pool will not call transferFrom and will only check if a surplus of
            coins[i] is greater than or equal to `dx`.
    @dev Use-case is to reduce the number of redundant ERC20 token
         transfers in zaps. Primarily for dex-aggregators/arbitrageurs/searchers.
         Note for users: please transfer + exchange_received in 1 tx.
    @param i Index value for the input coin
    @param j Index value for the output coin
    @param dx Amount of input coin being swapped in
    @param min_dy Minimum amount of output coin to receive
    @param receiver Address to send the output coin to
    @return uint256 Amount of tokens at index j received by the `receiver`
    """
    # _transfer_in updates self.balances here:
    dx_received: uint256 = self._transfer_in(
        i,
        dx,
        msg.sender,
        True  # <---- expect_optimistic_transfer is set to True here.
    )

    # No ERC20 token transfers occur here:
    out: uint256[3] = self._exchange(
        i,
        j,
        dx_received,
        min_dy,
    )

    # _transfer_out updates self.balances here. Update to state occurs before
    # external calls:
    self._transfer_out(j, out[0], receiver)

    # log:
    log TokenExchange(msg.sender, i, dx_received, j, out[0], out[1], out[2])

    return out[0]


@external
@nonreentrant("lock")
def add_liquidity(
    amounts: uint256[N_COINS],
    min_mint_amount: uint256,
    receiver: address = msg.sender
) -> uint256:
    """
    @notice Adds liquidity into the pool.
    @param amounts Amounts of each coin to add.
    @param min_mint_amount Minimum amount of LP to mint.
    @param receiver Address to send the LP tokens to. Default is msg.sender
    @return uint256 Amount of LP tokens received by the `receiver
    """

    A_gamma: uint256[2] = self._A_gamma()
    xp: uint256[N_COINS] = self.balances
    amountsp: uint256[N_COINS] = empty(uint256[N_COINS])
    d_token: uint256 = 0
    d_token_fee: uint256 = 0
    old_D: uint256 = 0

    assert amounts[0] + amounts[1] + amounts[2] > 0  # dev: no coins to add

    # --------------------- Get prices, balances -----------------------------

    packed_price_scale: uint256 = self.price_scale_packed
    price_scale: uint256[N_COINS-1] = self._unpack_prices(packed_price_scale)

    # -------------------------------------- Update balances and calculate xp.
    xp_old: uint256[N_COINS] = xp
    amounts_received: uint256[N_COINS] = empty(uint256[N_COINS])

    ########################## TRANSFER IN <-------

    for i in range(N_COINS):
        if amounts[i] > 0:
            # Updates self.balances here:
            amounts_received[i] = self._transfer_in(
                i,
                amounts[i],
                msg.sender,
                False,  # <--------------------- Disable optimistic transfers.
            )
            xp[i] = xp[i] + amounts_received[i]

    xp[0] *= PRECISIONS[0]
    xp_old[0] *= PRECISIONS[0]
    for i in range(N_COINS):

        if i >= 1:
            xp[i] = unsafe_div(xp[i] * price_scale[i-1] * PRECISIONS[i], PRECISION)
            xp_old[i] = unsafe_div(
                xp_old[i] * unsafe_mul(price_scale[i-1], PRECISIONS[i]),
                PRECISION
            )

        if amounts_received[i] > 0:
            amountsp[i] = xp[i] - xp_old[i]

    # -------------------- Calculate LP tokens to mint -----------------------

    if self.future_A_gamma_time > block.timestamp:  # <--- A_gamma is ramping.

        # ----- Recalculate the invariant if A or gamma are undergoing a ramp.
        old_D = MATH.newton_D(A_gamma[0], A_gamma[1], xp_old, 0)

    else:

        old_D = self.D

    D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)

    token_supply: uint256 = self.totalSupply
    if old_D > 0:
        d_token = token_supply * D / old_D - token_supply
    else:
        d_token = self.get_xcp(D, packed_price_scale)  # <----- Making initial
        #                                            virtual price equal to 1.

    assert d_token > 0  # dev: nothing minted

    if old_D > 0:

        d_token_fee = (
            self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1
        )

        d_token -= d_token_fee
        token_supply += d_token
        self.mint(receiver, d_token)
        self.admin_lp_virtual_balance += unsafe_div(ADMIN_FEE * d_token_fee, 10**10)

        packed_price_scale = self.tweak_price(A_gamma, xp, D, 0)

    else:

        # (re)instatiating an empty pool:

        self.D = D
        self.virtual_price = 10**18
        self.xcp_profit = 10**18
        self.xcp_profit_a = 10**18

        # Initialise xcp oracle here:
        self.cached_xcp_oracle = d_token  # <--- virtual_price * totalSupply / 10**18

        self.mint(receiver, d_token)

    assert d_token >= min_mint_amount, "Slippage"

    # ---------------------------------------------- Log and claim admin fees.

    log AddLiquidity(
        receiver, amounts_received, d_token_fee, token_supply, packed_price_scale
    )

    return d_token


@external
@nonreentrant("lock")
def remove_liquidity(
    _amount: uint256,
    min_amounts: uint256[N_COINS],
    receiver: address = msg.sender,
) -> uint256[N_COINS]:
    """
    @notice This withdrawal method is very safe, does no complex math since
            tokens are withdrawn in balanced proportions. No fees are charged.
    @param _amount Amount of LP tokens to burn
    @param min_amounts Minimum amounts of tokens to withdraw
    @param receiver Address to send the withdrawn tokens to
    @return uint256[3] Amount of pool tokens received by the `receiver`
    """
    amount: uint256 = _amount
    balances: uint256[N_COINS] = self.balances
    withdraw_amounts: uint256[N_COINS] = empty(uint256[N_COINS])

    # -------------------------------------------------------- Burn LP tokens.

    total_supply: uint256 = self.totalSupply  # <------ Get totalSupply before
    self.burnFrom(msg.sender, _amount)  # ---- reducing it with self.burnFrom.

    # There are two cases for withdrawing tokens from the pool.
    #   Case 1. Withdrawal does not empty the pool.
    #           In this situation, D is adjusted proportional to the amount of
    #           LP tokens burnt. ERC20 tokens transferred is proportional
    #           to : (AMM balance * LP tokens in) / LP token total supply
    #   Case 2. Withdrawal empties the pool.
    #           In this situation, all tokens are withdrawn and the invariant
    #           is reset.

    if amount == total_supply:  # <----------------------------------- Case 2.

        for i in range(N_COINS):

            withdraw_amounts[i] = balances[i]

    else:  # <-------------------------------------------------------- Case 1.

        amount -= 1  # <---- To prevent rounding errors, favor LPs a tiny bit.

        for i in range(N_COINS):

            withdraw_amounts[i] = balances[i] * amount / total_supply
            assert withdraw_amounts[i] >= min_amounts[i]

    D: uint256 = self.D
    self.D = D - unsafe_div(D * amount, total_supply)  # <----------- Reduce D
    #      proportional to the amount of tokens leaving. Since withdrawals are
    #       balanced, this is a simple subtraction. If amount == total_supply,
    #                                                             D will be 0.

    # ---------------------------------- Transfers ---------------------------

    for i in range(N_COINS):
        # _transfer_out updates self.balances here. Update to state occurs
        # before external calls:
        self._transfer_out(i, withdraw_amounts[i], receiver)

    log RemoveLiquidity(msg.sender, withdraw_amounts, total_supply - _amount)

    # --------------------------- Upkeep xcp oracle --------------------------

    # Update xcp since liquidity was removed:
    xp: uint256[N_COINS] = self.xp(self.balances, self.price_scale_packed)
    last_xcp: uint256 = MATH.geometric_mean(xp)  # <----------- Cache it for now.

    last_timestamp: uint256[2] = self._unpack_2(self.last_timestamp)
    if last_timestamp[1] < block.timestamp:

        cached_xcp_oracle: uint256 = self.cached_xcp_oracle
        alpha: uint256 = self._alpha(last_timestamp[1], self.xcp_ma_time)

        self.cached_xcp_oracle = unsafe_div(
            last_xcp * (10**18 - alpha) + cached_xcp_oracle * alpha,
            10**18
        )
        last_timestamp[1] = block.timestamp

        # Pack and store timestamps:
        self.last_timestamp = self._pack_2(last_timestamp[0], last_timestamp[1])

    # Store last xcp
    self.last_xcp = last_xcp

    return withdraw_amounts


@external
@nonreentrant("lock")
def remove_liquidity_one_coin(
    token_amount: uint256,
    i: uint256,
    min_amount: uint256,
    receiver: address = msg.sender
) -> uint256:
    """
    @notice Withdraw liquidity in a single token.
            Involves fees (lower than swap fees).
    @dev This operation also involves an admin fee claim.
    @param token_amount Amount of LP tokens to burn
    @param i Index of the token to withdraw
    @param min_amount Minimum amount of token to withdraw.
    @param receiver Address to send the withdrawn tokens to
    @return Amount of tokens at index i received by the `receiver`
    """

    self._claim_admin_fees()  # <--------- Auto-claim admin fees occasionally.

    A_gamma: uint256[2] = self._A_gamma()

    dy: uint256 = 0
    D: uint256 = 0
    p: uint256 = 0
    xp: uint256[N_COINS] = empty(uint256[N_COINS])
    approx_fee: uint256 = 0

    # ------------------------------------------------------------------------

    dy, D, xp, approx_fee = self._calc_withdraw_one_coin(
        A_gamma,
        token_amount,
        i,
        (self.future_A_gamma_time > block.timestamp),  # <------- During ramps
    )  #                                                  we need to update D.

    assert dy >= min_amount, "Slippage"

    # ---------------------------- State Updates -----------------------------

    # Burn user's tokens:
    self.burnFrom(msg.sender, token_amount)

    packed_price_scale: uint256 = self.tweak_price(A_gamma, xp, D, 0)
    #        Safe to use D from _calc_withdraw_one_coin here ---^

    # ------------------------- Transfers ------------------------------------

    # _transfer_out updates self.balances here. Update to state occurs before
    # external calls:
    self._transfer_out(i, dy, receiver)

    log RemoveLiquidityOne(
        msg.sender, token_amount, i, dy, approx_fee, packed_price_scale
    )

    return dy


# -------------------------- Packing functions -------------------------------


@internal
@pure
def _pack_3(x: uint256[3]) -> uint256:
    """
    @notice Packs 3 integers with values <= 10**18 into a uint256
    @param x The uint256[3] to pack
    @return uint256 Integer with packed values
    """
    return (x[0] << 128) | (x[1] << 64) | x[2]


@internal
@pure
def _unpack_3(_packed: uint256) -> uint256[3]:
    """
    @notice Unpacks a uint256 into 3 integers (values must be <= 10**18)
    @param val The uint256 to unpack
    @return uint256[3] A list of length 3 with unpacked integers
    """
    return [
        (_packed >> 128) & 18446744073709551615,
        (_packed >> 64) & 18446744073709551615,
        _packed & 18446744073709551615,
    ]



@pure
@internal
def _pack_2(p1: uint256, p2: uint256) -> uint256:
    return p1 | (p2 << 128)


@pure
@internal
def _unpack_2(packed: uint256) -> uint256[2]:
    return [packed & (2**128 - 1), packed >> 128]


@internal
@pure
def _pack_prices(prices_to_pack: uint256[N_COINS-1]) -> uint256:
    """
    @notice Packs N_COINS-1 prices into a uint256.
    @param prices_to_pack The prices to pack
    @return uint256 An integer that packs prices
    """
    packed_prices: uint256 = 0
    p: uint256 = 0
    for k in range(N_COINS - 1):
        packed_prices = packed_prices << PRICE_SIZE
        p = prices_to_pack[N_COINS - 2 - k]
        assert p < PRICE_MASK
        packed_prices = p | packed_prices
    return packed_prices


@internal
@pure
def _unpack_prices(_packed_prices: uint256) -> uint256[2]:
    """
    @notice Unpacks N_COINS-1 prices from a uint256.
    @param _packed_prices The packed prices
    @return uint256[2] Unpacked prices
    """
    unpacked_prices: uint256[N_COINS-1] = empty(uint256[N_COINS-1])
    packed_prices: uint256 = _packed_prices
    for k in range(N_COINS - 1):
        unpacked_prices[k] = packed_prices & PRICE_MASK
        packed_prices = packed_prices >> PRICE_SIZE

    return unpacked_prices


# ---------------------- AMM Internal Functions -------------------------------


@internal
def _exchange(
    i: uint256,
    j: uint256,
    dx_received: uint256,
    min_dy: uint256,
) -> uint256[3]:

    assert i != j  # dev: coin index out of range
    assert dx_received > 0  # dev: do not exchange 0 coins

    A_gamma: uint256[2] = self._A_gamma()
    xp: uint256[N_COINS] = self.balances  # <------- Has dx added to balances.
    dy: uint256 = 0

    y: uint256 = xp[j]  # <----------------- if j > N_COINS, this will revert.
    x0: uint256 = xp[i] - dx_received  # old xp[i]

    packed_price_scale: uint256 = self.price_scale_packed
    price_scale: uint256[N_COINS - 1] = self._unpack_prices(
        packed_price_scale
    )

    xp[0] *= PRECISIONS[0]
    for k in range(1, N_COINS):
        xp[k] = unsafe_div(
            xp[k] * price_scale[k - 1] * PRECISIONS[k],
            PRECISION
        )  # <-------- Safu to do unsafe_div here since PRECISION is not zero.

    prec_i: uint256 = PRECISIONS[i]

    # ----------- Update invariant if A, gamma are undergoing ramps ---------

    t: uint256 = self.future_A_gamma_time
    if t > block.timestamp:

        x0 *= prec_i

        if i > 0:
            x0 = unsafe_div(x0 * price_scale[i - 1], PRECISION)

        x1: uint256 = xp[i]  # <------------------ Back up old value in xp ...
        xp[i] = x0                                                         # |
        self.D = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)              # |
        xp[i] = x1  # <-------------------------------------- ... and restore.

    # ----------------------- Calculate dy and fees --------------------------

    D: uint256 = self.D
    y_out: uint256[2] = MATH.get_y(A_gamma[0], A_gamma[1], xp, D, j)
    dy = xp[j] - y_out[0]
    xp[j] -= dy
    dy -= 1

    if j > 0:
        dy = dy * PRECISION / price_scale[j - 1]
    dy /= PRECISIONS[j]

    fee: uint256 = unsafe_div(self._fee(xp) * dy, 10**10)
    dy -= fee  # <--------------------- Subtract fee from the outgoing amount.
    assert dy >= min_dy, "Slippage"

    y -= dy

    y *= PRECISIONS[j]
    if j > 0:
        y = unsafe_div(y * price_scale[j - 1], PRECISION)
    xp[j] = y  # <------------------------------------------------- Update xp.

    # ------ Tweak price_scale with good initial guess for newton_D ----------

    packed_price_scale = self.tweak_price(A_gamma, xp, 0, y_out[1])

    return [dy, fee, packed_price_scale]


@internal
def tweak_price(
    A_gamma: uint256[2],
    _xp: uint256[N_COINS],
    new_D: uint256,
    K0_prev: uint256 = 0,
) -> uint256:
    """
    @notice Updates price_oracle, last_price and conditionally adjusts
            price_scale. This is called whenever there is an unbalanced
            liquidity operation: _exchange, add_liquidity, or
            remove_liquidity_one_coin.
    @dev Contains main liquidity rebalancing logic, by tweaking `price_scale`.
    @param A_gamma Array of A and gamma parameters.
    @param _xp Array of current balances.
    @param new_D New D value.
    @param K0_prev Initial guess for `newton_D`.
    """

    # ---------------------------- Read storage ------------------------------

    price_oracle: uint256[N_COINS - 1] = self._unpack_prices(self.price_oracle_packed)
    last_prices: uint256[N_COINS - 1] = self._unpack_prices(self.last_prices_packed)
    packed_price_scale: uint256 = self.price_scale_packed
    price_scale: uint256[N_COINS - 1] = self._unpack_prices(packed_price_scale)
    rebalancing_params: uint256[3] = self._unpack_3(self.packed_rebalancing_params)
    # Contains: allowed_extra_profit, adjustment_step, ma_time. -----^

    total_supply: uint256 = self.totalSupply
    old_xcp_profit: uint256 = self.xcp_profit
    old_virtual_price: uint256 = self.virtual_price

    # ----------------------- Update Oracles if needed -----------------------

    last_timestamp: uint256[2] = self._unpack_2(self.last_timestamp)
    alpha: uint256 = 0
    if last_timestamp[0] < block.timestamp:  # 0th index is for price_oracle.

        #   The moving average price oracle is calculated using the last_price
        #      of the trade at the previous block, and the price oracle logged
        #              before that trade. This can happen only once per block.

        # ------------------ Calculate moving average params -----------------

        alpha = self._alpha(last_timestamp[0], rebalancing_params[2])
        for k in range(N_COINS - 1):

            # ----------------- We cap state price that goes into the EMA with
            #                                                 2 x price_scale.
            price_oracle[k] = unsafe_div(
                min(last_prices[k], 2 * price_scale[k]) * (10**18 - alpha) +
                price_oracle[k] * alpha,  # ^-------- Cap spot price into EMA.
                10**18
            )

        self.price_oracle_packed = self._pack_prices(price_oracle)
        last_timestamp[0] = block.timestamp

    # ----------------------------------------------------- Update xcp oracle.

    if last_timestamp[1] < block.timestamp:

        cached_xcp_oracle: uint256 = self.cached_xcp_oracle
        alpha = self._alpha(last_timestamp[1], self.xcp_ma_time)
        self.cached_xcp_oracle = unsafe_div(
            self.last_xcp * (10**18 - alpha) + cached_xcp_oracle * alpha,
            10**18
        )

        # Pack and store timestamps:
        last_timestamp[1] = block.timestamp

    self.last_timestamp = self._pack_2(last_timestamp[0], last_timestamp[1])

    #  `price_oracle` is used further on to calculate its vector distance from
    # price_scale. This distance is used to calculate the amount of adjustment
    # to be done to the price_scale.
    # ------------------------------------------------------------------------

    # ------------------ If new_D is set to 0, calculate it ------------------

    D_unadjusted: uint256 = new_D
    if new_D == 0:  #  <--------------------------- _exchange sets new_D to 0.
        D_unadjusted = MATH.newton_D(A_gamma[0], A_gamma[1], _xp, K0_prev)

    # ----------------------- Calculate last_prices --------------------------

    last_prices = MATH.get_p(_xp, D_unadjusted, A_gamma)
    for k in range(N_COINS - 1):
        last_prices[k] = unsafe_div(last_prices[k] * price_scale[k], 10**18)
    self.last_prices_packed = self._pack_prices(last_prices)

    # ---------- Update profit numbers without price adjustment first --------

    xp: uint256[N_COINS] = empty(uint256[N_COINS])
    xp[0] = unsafe_div(D_unadjusted, N_COINS)
    for k in range(N_COINS - 1):
        xp[k + 1] = D_unadjusted * 10**18 / (N_COINS * price_scale[k])

    # ------------------------- Update xcp_profit ----------------------------

    xcp_profit: uint256 = 10**18
    virtual_price: uint256 = 10**18

    if old_virtual_price > 0:

        xcp: uint256 = MATH.geometric_mean(xp)
        virtual_price = 10**18 * xcp / total_supply

        xcp_profit = unsafe_div(
            old_xcp_profit * virtual_price,
            old_virtual_price
        )  # <---------------- Safu to do unsafe_div as old_virtual_price > 0.

        #       If A and gamma are not undergoing ramps (t < block.timestamp),
        #         ensure new virtual_price is not less than old virtual_price,
        #                                        else the pool suffers a loss.
        if self.future_A_gamma_time < block.timestamp:
            assert virtual_price > old_virtual_price, "Loss"

        # -------------------------- Cache last_xcp --------------------------

        self.last_xcp = xcp  # geometric_mean(D * price_scale)

    self.xcp_profit = xcp_profit

    # ------------ Rebalance liquidity if there's enough profits to adjust it:
    if virtual_price * 2 - 10**18 > xcp_profit + 2 * rebalancing_params[0]:
        #                          allowed_extra_profit --------^

        # ------------------- Get adjustment step ----------------------------

        #                Calculate the vector distance between price_scale and
        #                                                        price_oracle.
        norm: uint256 = 0
        ratio: uint256 = 0
        for k in range(N_COINS - 1):

            ratio = unsafe_div(price_oracle[k] * 10**18, price_scale[k])
            # unsafe_div because we did safediv before ----^

            if ratio > 10**18:
                ratio = unsafe_sub(ratio, 10**18)
            else:
                ratio = unsafe_sub(10**18, ratio)
            norm = unsafe_add(norm, ratio**2)

        norm = isqrt(norm)  # <-------------------- isqrt is not in base 1e18.
        adjustment_step: uint256 = max(
            rebalancing_params[1], unsafe_div(norm, 5)
        )  #           ^------------------------------------- adjustment_step.

        if norm > adjustment_step:  # <---------- We only adjust prices if the
            #          vector distance between price_oracle and price_scale is
            #             large enough. This check ensures that no rebalancing
            #           occurs if the distance is low i.e. the pool prices are
            #                                     pegged to the oracle prices.

            # ------------------------------------- Calculate new price scale.

            p_new: uint256[N_COINS - 1] = empty(uint256[N_COINS - 1])
            for k in range(N_COINS - 1):
                p_new[k] = unsafe_div(
                    price_scale[k] * unsafe_sub(norm, adjustment_step)
                    + adjustment_step * price_oracle[k],
                    norm
                )  # <- norm is non-zero and gt adjustment_step; unsafe = safe

            # ---------------- Update stale xp (using price_scale) with p_new.
            xp = _xp
            for k in range(N_COINS - 1):
                xp[k + 1] = unsafe_div(_xp[k + 1] * p_new[k], price_scale[k])
                # unsafe_div because we did safediv before ----^

            # ------------------------------------------ Update D with new xp.
            D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)
            assert D > 0  # dev: unsafe D
            # Check if calculated p_new is safu:
            for k in range(N_COINS):
                frac: uint256 = unsafe_div(xp[k] * 10**18, D)
                assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  # dev: unsafe p_new

            xp[0] = unsafe_div(D, N_COINS)
            for k in range(N_COINS - 1):
                xp[k + 1] = D * 10**18 / (N_COINS * p_new[k])  # <---- Convert
                #                                           xp to real prices.

            # ---------- Calculate new virtual_price using new xp and D. Reuse
            #              `old_virtual_price` (but it has new virtual_price).
            old_virtual_price = unsafe_div(
                10**18 * MATH.geometric_mean(xp), total_supply
            )  # <----- unsafe_div because we did safediv before (if vp>1e18)

            # ---------------------------- Proceed if we've got enough profit.
            if (
                old_virtual_price > 10**18 and
                2 * old_virtual_price - 10**18 > xcp_profit
            ):

                packed_price_scale = self._pack_prices(p_new)

                self.D = D
                self.virtual_price = old_virtual_price
                self.price_scale_packed = packed_price_scale

                return packed_price_scale

    # --------- price_scale was not adjusted. Update the profit counter and D.
    self.D = D_unadjusted
    self.virtual_price = virtual_price

    return packed_price_scale


@internal
def _claim_admin_fees():
    """
    @notice Claims admin fees and sends it to fee_receiver set in the factory.
    @dev Functionally similar to:
         1. Calculating admin's share of fees,
         2. minting LP tokens,
         3. admin claims underlying tokens via remove_liquidity.
    """

    # --------------------- Check if fees can be claimed ---------------------

    # Disable fee claiming if:
    # 1. If time passed since last fee claim is less than
    #    MIN_ADMIN_FEE_CLAIM_INTERVAL.
    # 2. Pool parameters are being ramped.

    last_claim_time: uint256 = self.last_admin_fee_claim_timestamp
    if (
        unsafe_sub(block.timestamp, last_claim_time) < MIN_ADMIN_FEE_CLAIM_INTERVAL or
        self.future_A_gamma_time > block.timestamp
    ):
        return

    xcp_profit: uint256 = self.xcp_profit  # <---------- Current pool profits.
    xcp_profit_a: uint256 = self.xcp_profit_a  # <- Profits at previous claim.
    current_lp_token_supply: uint256 = self.totalSupply

    # Do not claim admin fees if:
    # 1. insufficient profits accrued since last claim, and
    # 2. there are less than 10**18 (or 1 unit of) lp tokens, else it can lead
    #    to manipulated virtual prices.

    if xcp_profit <= xcp_profit_a or current_lp_token_supply < 10**18:
        return

    # ---------- Conditions met to claim admin fees: compute state. ----------

    A_gamma: uint256[2] = self._A_gamma()
    D: uint256 = self.D
    vprice: uint256 = self.virtual_price
    packed_price_scale: uint256 = self.price_scale_packed
    fee_receiver: address = factory.fee_receiver()
    balances: uint256[N_COINS] = self.balances

    #  Admin fees are calculated as follows.
    #      1. Calculate accrued profit since last claim. `xcp_profit`
    #         is the current profits. `xcp_profit_a` is the profits
    #         at the previous claim.
    #      2. Take out admin's share, which is hardcoded at 5 * 10**9.
    #         (50% => half of 100% => 10**10 / 2 => 5 * 10**9).
    #      3. Since half of the profits go to rebalancing the pool, we
    #         are left with half; so divide by 2.

    fees: uint256 = unsafe_div(
        unsafe_sub(xcp_profit, xcp_profit_a) * ADMIN_FEE, 2 * 10**10
    )

    # ------------------------------ Claim admin fees by minting admin's share
    #                                                of the pool in LP tokens.

    # This is the admin fee tokens claimed in self.add_liquidity. We add it to
    # the LP token share that the admin needs to claim:
    admin_share: uint256 = self.admin_lp_virtual_balance
    frac: uint256 = 0
    if fee_receiver != empty(address) and fees > 0:

        # -------------------------------- Calculate admin share to be minted.
        frac = vprice * 10**18 / (vprice - fees) - 10**18
        admin_share += current_lp_token_supply * frac / 10**18

        # ------ Subtract fees from profits that will be used for rebalancing.
        xcp_profit -= fees * 2

    # ------------------- Recalculate virtual_price following admin fee claim.
    total_supply_including_admin_share: uint256 = (
        current_lp_token_supply + admin_share
    )
    vprice = (
        10**18 * self.get_xcp(D, packed_price_scale) /
        total_supply_including_admin_share
    )

    # Do not claim fees if doing so causes virtual price to drop below 10**18.
    if vprice < 10**18:
        return

    # ---------------------------- Update State ------------------------------

    # Set admin virtual LP balances to zero because we claimed:
    self.admin_lp_virtual_balance = 0

    self.xcp_profit = xcp_profit
    self.last_admin_fee_claim_timestamp = block.timestamp

    # Since we reduce balances: virtual price goes down
    self.virtual_price = vprice

    # Adjust D after admin seemingly removes liquidity
    self.D = D - unsafe_div(D * admin_share, total_supply_including_admin_share)

    if xcp_profit > xcp_profit_a:
        self.xcp_profit_a = xcp_profit  # <-------- Cache last claimed profit.

    # --------------------------- Handle Transfers ---------------------------

    admin_tokens: uint256[N_COINS] = empty(uint256[N_COINS])
    if admin_share > 0:

        for i in range(N_COINS):

            admin_tokens[i] = (
                balances[i] * admin_share /
                total_supply_including_admin_share
            )

            # _transfer_out tokens to admin and update self.balances. State
            # update to self.balances occurs before external contract calls:
            self._transfer_out(i, admin_tokens[i], fee_receiver)

        log ClaimAdminFee(fee_receiver, admin_tokens)


@internal
@pure
def xp(
    balances: uint256[N_COINS],
    price_scale_packed: uint256,
) -> uint256[N_COINS]:

    result: uint256[N_COINS] = balances
    result[0] *= PRECISIONS[0]
    packed_prices: uint256 = price_scale_packed
    for i in range(1, N_COINS):
        p: uint256 = (packed_prices & PRICE_MASK) * PRECISIONS[i]
        result[i] = result[i] * p / PRECISION
        packed_prices = packed_prices >> PRICE_SIZE

    return result


@internal
@view
def _alpha(last_timestamp: uint256, ma_exp_time: uint256) -> uint256:

    return MATH.wad_exp(
        -convert(
            unsafe_div(
                (block.timestamp - last_timestamp) * 10**18,
                ma_exp_time
            ),
            int256,
        )
    )


@view
@internal
def _A_gamma() -> uint256[2]:
    t1: uint256 = self.future_A_gamma_time

    A_gamma_1: uint256 = self.future_A_gamma
    gamma1: uint256 = A_gamma_1 & 2**128 - 1
    A1: uint256 = A_gamma_1 >> 128

    if block.timestamp < t1:

        # --------------- Handle ramping up and down of A --------------------

        A_gamma_0: uint256 = self.initial_A_gamma
        t0: uint256 = self.initial_A_gamma_time

        t1 -= t0
        t0 = block.timestamp - t0
        t2: uint256 = t1 - t0

        A1 = ((A_gamma_0 >> 128) * t2 + A1 * t0) / t1
        gamma1 = ((A_gamma_0 & 2**128 - 1) * t2 + gamma1 * t0) / t1

    return [A1, gamma1]


@internal
@view
def _fee(xp: uint256[N_COINS]) -> uint256:

    fee_params: uint256[3] = self._unpack_3(self.packed_fee_params)
    f: uint256 = MATH.reduction_coefficient(xp, fee_params[2])

    return unsafe_div(
        fee_params[0] * f + fee_params[1] * (10**18 - f),
        10**18
    )


@internal
@pure
def get_xcp(D: uint256, price_scale_packed: uint256) -> uint256:

    x: uint256[N_COINS] = empty(uint256[N_COINS])
    x[0] = D / N_COINS
    packed_prices: uint256 = price_scale_packed  # <------ No precisions here
    #                                 because we don't switch to "real" units.

    for i in range(1, N_COINS):
        x[i] = D * 10**18 / (N_COINS * (packed_prices & PRICE_MASK))
        packed_prices = packed_prices >> PRICE_SIZE

    return MATH.geometric_mean(x)


@view
@internal
def _calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256:
    # fee = sum(amounts_i - avg(amounts)) * fee' / sum(amounts)
    fee: uint256 = unsafe_div(
        unsafe_mul(self._fee(xp), N_COINS),
        unsafe_mul(4, unsafe_sub(N_COINS, 1))
    )

    S: uint256 = 0
    for _x in amounts:
        S += _x

    avg: uint256 = unsafe_div(S, N_COINS)
    Sdiff: uint256 = 0

    for _x in amounts:
        if _x > avg:
            Sdiff += unsafe_sub(_x, avg)
        else:
            Sdiff += unsafe_sub(avg, _x)

    return fee * Sdiff / S + NOISE_FEE


@internal
@view
def _calc_withdraw_one_coin(
    A_gamma: uint256[2],
    token_amount: uint256,
    i: uint256,
    update_D: bool,
) -> (uint256, uint256, uint256[N_COINS], uint256):

    token_supply: uint256 = self.totalSupply
    assert token_amount <= token_supply  # dev: token amount more than supply
    assert i < N_COINS  # dev: coin out of range

    xx: uint256[N_COINS] = self.balances
    xp: uint256[N_COINS] = PRECISIONS
    D0: uint256 = 0

    # -------------------------- Calculate D0 and xp -------------------------

    price_scale_i: uint256 = PRECISION * PRECISIONS[0]
    packed_prices: uint256 = self.price_scale_packed
    xp[0] *= xx[0]
    for k in range(1, N_COINS):
        p: uint256 = (packed_prices & PRICE_MASK)
        if i == k:
            price_scale_i = p * xp[i]
        xp[k] = unsafe_div(xp[k] * xx[k] * p, PRECISION)
        packed_prices = packed_prices >> PRICE_SIZE

    if update_D:  # <-------------- D is updated if pool is undergoing a ramp.
        D0 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)
    else:
        D0 = self.D

    D: uint256 = D0

    # -------------------------------- Fee Calc ------------------------------

    # Charge fees on D. Roughly calculate xp[i] after withdrawal and use that
    # to calculate fee. Precision is not paramount here: we just want a
    # behavior where the higher the imbalance caused the more fee the AMM
    # charges.

    # xp is adjusted assuming xp[0] ~= xp[1] ~= x[2], which is usually not the
    #  case. We charge self._fee(xp), where xp is an imprecise adjustment post
    #  withdrawal in one coin. If the withdraw is too large: charge max fee by
    #   default. This is because the fee calculation will otherwise underflow.

    xp_imprecise: uint256[N_COINS] = xp
    xp_correction: uint256 = xp[i] * N_COINS * token_amount / token_supply
    fee: uint256 = self._unpack_3(self.packed_fee_params)[1]  # <- self.out_fee.

    if xp_correction < xp_imprecise[i]:
        xp_imprecise[i] -= xp_correction
        fee = self._fee(xp_imprecise)

    dD: uint256 = unsafe_div(token_amount * D, token_supply)
    D_fee: uint256 = fee * dD / (2 * 10**10) + 1  # <------- Actual fee on D.

    # --------- Calculate `approx_fee` (assuming balanced state) in ith token.
    # -------------------------------- We only need this for fee in the event.
    approx_fee: uint256 = N_COINS * D_fee * xx[i] / D

    # ------------------------------------------------------------------------
    D -= (dD - D_fee)  # <----------------------------------- Charge fee on D.
    # --------------------------------- Calculate `y_out`` with `(D - D_fee)`.
    y: uint256 = MATH.get_y(A_gamma[0], A_gamma[1], xp, D, i)[0]
    dy: uint256 = (xp[i] - y) * PRECISION / price_scale_i
    xp[i] = y

    return dy, D, xp, approx_fee


# ------------------------ ERC20 functions -----------------------------------


@internal
def _approve(_owner: address, _spender: address, _value: uint256):
    self.allowance[_owner][_spender] = _value

    log Approval(_owner, _spender, _value)


@internal
def _transfer(_from: address, _to: address, _value: uint256):
    assert _to not in [self, empty(address)]

    self.balanceOf[_from] -= _value
    self.balanceOf[_to] += _value

    log Transfer(_from, _to, _value)


@view
@internal
def _domain_separator() -> bytes32:
    if chain.id != CACHED_CHAIN_ID:
        return keccak256(
            _abi_encode(
                EIP712_TYPEHASH,
                NAME_HASH,
                VERSION_HASH,
                chain.id,
                self,
                salt,
            )
        )
    return CACHED_DOMAIN_SEPARATOR


@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
    """
    @dev Transfer tokens from one address to another.
    @param _from address The address which you want to send tokens from
    @param _to address The address which you want to transfer to
    @param _value uint256 the amount of tokens to be transferred
    @return bool True on successul transfer. Reverts otherwise.
    """
    _allowance: uint256 = self.allowance[_from][msg.sender]
    if _allowance != max_value(uint256):
        self._approve(_from, msg.sender, _allowance - _value)

    self._transfer(_from, _to, _value)
    return True


@external
def transfer(_to: address, _value: uint256) -> bool:
    """
    @dev Transfer token for a specified address
    @param _to The address to transfer to.
    @param _value The amount to be transferred.
    @return bool True on successful transfer. Reverts otherwise.
    """
    self._transfer(msg.sender, _to, _value)
    return True


@external
def approve(_spender: address, _value: uint256) -> bool:
    """
    @notice Allow `_spender` to transfer up to `_value` amount
            of tokens from the caller's account.
    @param _spender The account permitted to spend up to `_value` amount of
                    caller's funds.
    @param _value The amount of tokens `_spender` is allowed to spend.
    @return bool Success
    """
    self._approve(msg.sender, _spender, _value)
    return True


@external
def permit(
    _owner: address,
    _spender: address,
    _value: uint256,
    _deadline: uint256,
    _v: uint8,
    _r: bytes32,
    _s: bytes32,
) -> bool:
    """
    @notice Permit `_spender` to spend up to `_value` amount of `_owner`'s
            tokens via a signature.
    @dev In the event of a chain fork, replay attacks are prevented as
         domain separator is recalculated. However, this is only if the
         resulting chains update their chainId.
    @param _owner The account which generated the signature and is granting an
                  allowance.
    @param _spender The account which will be granted an allowance.
    @param _value The approval amount.
    @param _deadline The deadline by which the signature must be submitted.
    @param _v The last byte of the ECDSA signature.
    @param _r The first 32 bytes of the ECDSA signature.
    @param _s The second 32 bytes of the ECDSA signature.
    @return bool Success.
    """
    assert _owner != empty(address)  # dev: invalid owner
    assert block.timestamp <= _deadline  # dev: permit expired

    nonce: uint256 = self.nonces[_owner]
    digest: bytes32 = keccak256(
        concat(
            b"\x19\x01",
            self._domain_separator(),
            keccak256(
                _abi_encode(
                    EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline
                )
            ),
        )
    )
    assert ecrecover(digest, _v, _r, _s) == _owner  # dev: invalid signature

    self.nonces[_owner] = unsafe_add(nonce, 1)  # <-- Unsafe add is safe here.
    self._approve(_owner, _spender, _value)
    return True


@internal
def mint(_to: address, _value: uint256) -> bool:
    """
    @dev Mint an amount of the token and assigns it to an account.
         This encapsulates the modification of balances such that the
         proper events are emitted.
    @param _to The account that will receive the created tokens.
    @param _value The amount that will be created.
    @return bool Success.
    """
    self.totalSupply += _value
    self.balanceOf[_to] += _value

    log Transfer(empty(address), _to, _value)
    return True


@internal
def burnFrom(_to: address, _value: uint256) -> bool:
    """
    @dev Burn an amount of the token from a given account.
    @param _to The account whose tokens will be burned.
    @param _value The amount that will be burned.
    @return bool Success.
    """
    self.totalSupply -= _value
    self.balanceOf[_to] -= _value

    log Transfer(_to, empty(address), _value)
    return True


# ------------------------- AMM View Functions -------------------------------


@external
@view
def fee_receiver() -> address:
    """
    @notice Returns the address of the admin fee receiver.
    @return address Fee receiver.
    """
    return factory.fee_receiver()


@external
@view
def admin() -> address:
    """
    @notice Returns the address of the pool's admin.
    @return address Admin.
    """
    return factory.admin()


@external
@view
def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256:
    """
    @notice Calculate LP tokens minted or to be burned for depositing or
            removing `amounts` of coins
    @dev Includes fee.
    @param amounts Amounts of tokens being deposited or withdrawn
    @param deposit True if it is a deposit action, False if withdrawn.
    @return uint256 Amount of LP tokens deposited or withdrawn.
    """
    view_contract: address = factory.views_implementation()
    return Views(view_contract).calc_token_amount(amounts, deposit, self)


@external
@view
def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256:
    """
    @notice Get amount of coin[j] tokens received for swapping in dx amount of coin[i]
    @dev Includes fee.
    @param i index of input token. Check pool.coins(i) to get coin address at ith index
    @param j index of output token
    @param dx amount of input coin[i] tokens
    @return uint256 Exact amount of output j tokens for dx amount of i input tokens.
    """
    view_contract: address = factory.views_implementation()
    return Views(view_contract).get_dy(i, j, dx, self)


@external
@view
def get_dx(i: uint256, j: uint256, dy: uint256) -> uint256:
    """
    @notice Get amount of coin[i] tokens to input for swapping out dy amount
            of coin[j]
    @dev This is an approximate method, and returns estimates close to the input
         amount. Expensive to call on-chain.
    @param i index of input token. Check pool.coins(i) to get coin address at
           ith index
    @param j index of output token
    @param dy amount of input coin[j] tokens received
    @return uint256 Approximate amount of input i tokens to get dy amount of j tokens.
    """
    view_contract: address = factory.views_implementation()
    return Views(view_contract).get_dx(i, j, dy, self)


@external
@view
@nonreentrant("lock")
def lp_price() -> uint256:
    """
    @notice Calculates the current price of the LP token w.r.t coin at the
            0th index
    @return uint256 LP price.
    """

    price_oracle: uint256[N_COINS-1] = self._unpack_prices(self.price_oracle_packed)
    return (
        3 * self.virtual_price * MATH.cbrt(price_oracle[0] * price_oracle[1])
    ) / 10**24


@external
@view
@nonreentrant("lock")
def get_virtual_price() -> uint256:
    """
    @notice Calculates the current virtual price of the pool LP token.
    @dev Not to be confused with `self.virtual_price` which is a cached
         virtual price.
    @return uint256 Virtual Price.
    """
    return (
        10**18 * self.get_xcp(self.D, self.price_scale_packed) /
        self.totalSupply
    )


@external
@view
@nonreentrant("lock")
def price_oracle(k: uint256) -> uint256:
    """
    @notice Returns the oracle price of the coin at index `k` w.r.t the coin
            at index 0.
    @dev The oracle is an exponential moving average, with a periodicity
         determined by `self.ma_time`. The aggregated prices are cached state
         prices (dy/dx) calculated AFTER the latest trade.
         State prices that goes into the EMA are capped at 2 x price_scale.
    @param k The index of the coin.
    @return uint256 Price oracle value of kth coin.
    """
    price_oracle: uint256 = self._unpack_prices(self.price_oracle_packed)[k]
    price_scale: uint256 = self._unpack_prices(self.price_scale_packed)[k]
    last_prices_timestamp: uint256 = self._unpack_2(self.last_timestamp)[0]

    if last_prices_timestamp < block.timestamp:  # <------------ Update moving
        #                                                   average if needed.

        last_prices: uint256 = self._unpack_prices(self.last_prices_packed)[k]
        ma_time: uint256 = self._unpack_3(self.packed_rebalancing_params)[2]
        alpha: uint256 = self._alpha(last_prices_timestamp, ma_time)
        return (
            min(last_prices, 2 * price_scale) * (10**18 - alpha) +
            price_oracle * alpha
        ) / 10**18

    return price_oracle


@external
@view
@nonreentrant("lock")
def xcp_oracle() -> uint256:
    """
    @notice Returns the oracle value for xcp.
    @dev The oracle is an exponential moving average, with a periodicity
         determined by `self.xcp_ma_time`.
         `TVL` is xcp, calculated as either:
            1. virtual_price * total_supply, OR
            2. self.get_xcp(...), OR
            3. MATH.geometric_mean(xp)
    @return uint256 Oracle value of xcp.
    """

    last_xcp_timestamp: uint256 = self._unpack_2(self.last_timestamp)[1]
    cached_xcp_oracle: uint256 = self.cached_xcp_oracle

    if last_xcp_timestamp < block.timestamp:

        alpha: uint256 = self._alpha(last_xcp_timestamp, self.xcp_ma_time)
        return (self.last_xcp * (10**18 - alpha) + cached_xcp_oracle * alpha) / 10**18

    return cached_xcp_oracle


@external
@view
def last_prices(k: uint256) -> uint256:
    """
    @notice Returns last price of the coin at index `k` w.r.t the coin
            at index 0.
    @dev last_prices returns the quote by the AMM for an infinitesimally small swap
         after the last trade. It is not equivalent to the last traded price, and
         is computed by taking the partial differential of `x` w.r.t `y`. The
         derivative is calculated in `get_p` and then multiplied with price_scale
         to give last_prices.
    @param k The index of the coin.
    @return uint256 Last logged price of coin.
    """
    return self._unpack_prices(self.last_prices_packed)[k]


@external
@view
@nonreentrant("lock")
def price_scale(k: uint256) -> uint256:
    """
    @notice Returns the price scale of the coin at index `k` w.r.t the coin
            at index 0.
    @dev Price scale determines the price band around which liquidity is
         concentrated.
    @param k The index of the coin.
    @return uint256 Price scale of coin.
    """
    return self._unpack_prices(self.price_scale_packed)[k]


@external
@view
def fee() -> uint256:
    """
    @notice Returns the fee charged by the pool at current state.
    @dev Not to be confused with the fee charged at liquidity action, since
         there the fee is calculated on `xp` AFTER liquidity is added or
         removed.
    @return uint256 fee bps.
    """
    return self._fee(self.xp(self.balances, self.price_scale_packed))


@view
@external
def calc_withdraw_one_coin(token_amount: uint256, i: uint256) -> uint256:
    """
    @notice Calculates output tokens with fee
    @param token_amount LP Token amount to burn
    @param i token in which liquidity is withdrawn
    @return uint256 Amount of ith tokens received for burning token_amount LP tokens.
    """

    return self._calc_withdraw_one_coin(
        self._A_gamma(),
        token_amount,
        i,
        (self.future_A_gamma_time > block.timestamp)
    )[0]


@external
@view
def calc_token_fee(
    amounts: uint256[N_COINS], xp: uint256[N_COINS]
) -> uint256:
    """
    @notice Returns the fee charged on the given amounts for add_liquidity.
    @param amounts The amounts of coins being added to the pool.
    @param xp The current balances of the pool multiplied by coin precisions.
    @return uint256 Fee charged.
    """
    return self._calc_token_fee(amounts, xp)


@view
@external
def A() -> uint256:
    """
    @notice Returns the current pool amplification parameter.
    @return uint256 A param.
    """
    return self._A_gamma()[0]


@view
@external
def gamma() -> uint256:
    """
    @notice Returns the current pool gamma parameter.
    @return uint256 gamma param.
    """
    return self._A_gamma()[1]


@view
@external
def mid_fee() -> uint256:
    """
    @notice Returns the current mid fee
    @return uint256 mid_fee value.
    """
    return self._unpack_3(self.packed_fee_params)[0]


@view
@external
def out_fee() -> uint256:
    """
    @notice Returns the current out fee
    @return uint256 out_fee value.
    """
    return self._unpack_3(self.packed_fee_params)[1]


@view
@external
def fee_gamma() -> uint256:
    """
    @notice Returns the current fee gamma
    @return uint256 fee_gamma value.
    """
    return self._unpack_3(self.packed_fee_params)[2]


@view
@external
def allowed_extra_profit() -> uint256:
    """
    @notice Returns the current allowed extra profit
    @return uint256 allowed_extra_profit value.
    """
    return self._unpack_3(self.packed_rebalancing_params)[0]


@view
@external
def adjustment_step() -> uint256:
    """
    @notice Returns the current adjustment step
    @return uint256 adjustment_step value.
    """
    return self._unpack_3(self.packed_rebalancing_params)[1]


@view
@external
def ma_time() -> uint256:
    """
    @notice Returns the current moving average time in seconds
    @dev To get time in seconds, the parameter is multipled by ln(2)
         One can expect off-by-one errors here.
    @return uint256 ma_time value.
    """
    return self._unpack_3(self.packed_rebalancing_params)[2] * 694 / 1000


@view
@external
def precisions() -> uint256[N_COINS]:  # <-------------- For by view contract.
    """
    @notice Returns the precisions of each coin in the pool.
    @return uint256[3] precisions of coins.
    """
    return PRECISIONS


@external
@view
def fee_calc(xp: uint256[N_COINS]) -> uint256:  # <----- For by view contract.
    """
    @notice Returns the fee charged by the pool at current state.
    @param xp The current balances of the pool multiplied by coin precisions.
    @return uint256 Fee value.
    """
    return self._fee(xp)


@view
@external
def DOMAIN_SEPARATOR() -> bytes32:
    """
    @notice EIP712 domain separator.
    @return bytes32 Domain Separator set for the current chain.
    """
    return self._domain_separator()


# ------------------------- AMM Admin Functions ------------------------------


@external
def ramp_A_gamma(
    future_A: uint256, future_gamma: uint256, future_time: uint256
):
    """
    @notice Initialise Ramping A and gamma parameter values linearly.
    @dev Only accessible by factory admin, and only
    @param future_A The future A value.
    @param future_gamma The future gamma value.
    @param future_time The timestamp at which the ramping will end.
    """
    assert msg.sender == factory.admin()  # dev: only owner
    assert block.timestamp > self.initial_A_gamma_time + (MIN_RAMP_TIME - 1)  # dev: ramp undergoing
    assert future_time > block.timestamp + MIN_RAMP_TIME - 1  # dev: insufficient time

    A_gamma: uint256[2] = self._A_gamma()
    initial_A_gamma: uint256 = A_gamma[0] << 128
    initial_A_gamma = initial_A_gamma | A_gamma[1]

    assert future_A > MIN_A - 1
    assert future_A < MAX_A + 1
    assert future_gamma > MIN_GAMMA - 1
    assert future_gamma < MAX_GAMMA + 1

    ratio: uint256 = 10**18 * future_A / A_gamma[0]
    assert ratio < 10**18 * MAX_A_CHANGE + 1
    assert ratio > 10**18 / MAX_A_CHANGE - 1

    ratio = 10**18 * future_gamma / A_gamma[1]
    assert ratio < 10**18 * MAX_A_CHANGE + 1
    assert ratio > 10**18 / MAX_A_CHANGE - 1

    self.initial_A_gamma = initial_A_gamma
    self.initial_A_gamma_time = block.timestamp

    future_A_gamma: uint256 = future_A << 128
    future_A_gamma = future_A_gamma | future_gamma
    self.future_A_gamma_time = future_time
    self.future_A_gamma = future_A_gamma

    log RampAgamma(
        A_gamma[0],
        future_A,
        A_gamma[1],
        future_gamma,
        block.timestamp,
        future_time,
    )


@external
def stop_ramp_A_gamma():
    """
    @notice Stop Ramping A and gamma parameters immediately.
    @dev Only accessible by factory admin.
    """
    assert msg.sender == factory.admin()  # dev: only owner

    A_gamma: uint256[2] = self._A_gamma()
    current_A_gamma: uint256 = A_gamma[0] << 128
    current_A_gamma = current_A_gamma | A_gamma[1]
    self.initial_A_gamma = current_A_gamma
    self.future_A_gamma = current_A_gamma
    self.initial_A_gamma_time = block.timestamp
    self.future_A_gamma_time = block.timestamp

    # ------ Now (block.timestamp < t1) is always False, so we return saved A.

    log StopRampA(A_gamma[0], A_gamma[1], block.timestamp)


@external
@nonreentrant('lock')
def apply_new_parameters(
    _new_mid_fee: uint256,
    _new_out_fee: uint256,
    _new_fee_gamma: uint256,
    _new_allowed_extra_profit: uint256,
    _new_adjustment_step: uint256,
    _new_ma_time: uint256,
    _new_xcp_ma_time: uint256,
):
    """
    @notice Commit new parameters.
    @dev Only accessible by factory admin.
    @param _new_mid_fee The new mid fee.
    @param _new_out_fee The new out fee.
    @param _new_fee_gamma The new fee gamma.
    @param _new_allowed_extra_profit The new allowed extra profit.
    @param _new_adjustment_step The new adjustment step.
    @param _new_ma_time The new ma time. ma_time is time_in_seconds/ln(2).
    @param _new_xcp_ma_time The new ma time for xcp oracle.
    """
    assert msg.sender == factory.admin()  # dev: only owner

    # ----------------------------- Set fee params ---------------------------

    new_mid_fee: uint256 = _new_mid_fee
    new_out_fee: uint256 = _new_out_fee
    new_fee_gamma: uint256 = _new_fee_gamma

    current_fee_params: uint256[3] = self._unpack_3(self.packed_fee_params)

    if new_out_fee < MAX_FEE + 1:
        assert new_out_fee > MIN_FEE - 1  # dev: fee is out of range
    else:
        new_out_fee = current_fee_params[1]

    if new_mid_fee > MAX_FEE:
        new_mid_fee = current_fee_params[0]
    assert new_mid_fee <= new_out_fee  # dev: mid-fee is too high

    if new_fee_gamma < 10**18:
        assert new_fee_gamma > 0  # dev: fee_gamma out of range [1 .. 10**18]
    else:
        new_fee_gamma = current_fee_params[2]

    self.packed_fee_params = self._pack_3([new_mid_fee, new_out_fee, new_fee_gamma])

    # ----------------- Set liquidity rebalancing parameters -----------------

    new_allowed_extra_profit: uint256 = _new_allowed_extra_profit
    new_adjustment_step: uint256 = _new_adjustment_step
    new_ma_time: uint256 = _new_ma_time

    current_rebalancing_params: uint256[3] = self._unpack_3(self.packed_rebalancing_params)

    if new_allowed_extra_profit > 10**18:
        new_allowed_extra_profit = current_rebalancing_params[0]

    if new_adjustment_step > 10**18:
        new_adjustment_step = current_rebalancing_params[1]

    if new_ma_time < 872542:  # <----- Calculated as: 7 * 24 * 60 * 60 / ln(2)
        assert new_ma_time > 86  # dev: MA time should be longer than 60/ln(2)
    else:
        new_ma_time = current_rebalancing_params[2]

    self.packed_rebalancing_params = self._pack_3(
        [new_allowed_extra_profit, new_adjustment_step, new_ma_time]
    )

    # Set xcp oracle moving average window time:
    new_xcp_ma_time: uint256 = _new_xcp_ma_time
    if new_xcp_ma_time < 872542:
        assert new_xcp_ma_time > 86  # dev: xcp MA time should be longer than 60/ln(2)
    else:
        new_xcp_ma_time = self.xcp_ma_time
    self.xcp_ma_time = new_xcp_ma_time

    # ---------------------------------- LOG ---------------------------------

    log NewParameters(
        new_mid_fee,
        new_out_fee,
        new_fee_gamma,
        new_allowed_extra_profit,
        new_adjustment_step,
        new_ma_time,
        _new_xcp_ma_time,
    )

Contract Security Audit

Contract ABI

[{"name":"Transfer","inputs":[{"name":"sender","type":"address","indexed":true},{"name":"receiver","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Approval","inputs":[{"name":"owner","type":"address","indexed":true},{"name":"spender","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"TokenExchange","inputs":[{"name":"buyer","type":"address","indexed":true},{"name":"sold_id","type":"uint256","indexed":false},{"name":"tokens_sold","type":"uint256","indexed":false},{"name":"bought_id","type":"uint256","indexed":false},{"name":"tokens_bought","type":"uint256","indexed":false},{"name":"fee","type":"uint256","indexed":false},{"name":"packed_price_scale","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"AddLiquidity","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"token_amounts","type":"uint256[3]","indexed":false},{"name":"fee","type":"uint256","indexed":false},{"name":"token_supply","type":"uint256","indexed":false},{"name":"packed_price_scale","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"RemoveLiquidity","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"token_amounts","type":"uint256[3]","indexed":false},{"name":"token_supply","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"RemoveLiquidityOne","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"token_amount","type":"uint256","indexed":false},{"name":"coin_index","type":"uint256","indexed":false},{"name":"coin_amount","type":"uint256","indexed":false},{"name":"approx_fee","type":"uint256","indexed":false},{"name":"packed_price_scale","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"NewParameters","inputs":[{"name":"mid_fee","type":"uint256","indexed":false},{"name":"out_fee","type":"uint256","indexed":false},{"name":"fee_gamma","type":"uint256","indexed":false},{"name":"allowed_extra_profit","type":"uint256","indexed":false},{"name":"adjustment_step","type":"uint256","indexed":false},{"name":"ma_time","type":"uint256","indexed":false},{"name":"xcp_ma_time","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"RampAgamma","inputs":[{"name":"initial_A","type":"uint256","indexed":false},{"name":"future_A","type":"uint256","indexed":false},{"name":"initial_gamma","type":"uint256","indexed":false},{"name":"future_gamma","type":"uint256","indexed":false},{"name":"initial_time","type":"uint256","indexed":false},{"name":"future_time","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"StopRampA","inputs":[{"name":"current_A","type":"uint256","indexed":false},{"name":"current_gamma","type":"uint256","indexed":false},{"name":"time","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"ClaimAdminFee","inputs":[{"name":"admin","type":"address","indexed":true},{"name":"tokens","type":"uint256[3]","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_coins","type":"address[3]"},{"name":"_math","type":"address"},{"name":"_weth","type":"address"},{"name":"_salt","type":"bytes32"},{"name":"__packed_precisions","type":"uint256"},{"name":"packed_A_gamma","type":"uint256"},{"name":"packed_fee_params","type":"uint256"},{"name":"packed_rebalancing_params","type":"uint256"},{"name":"packed_prices","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"exchange","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dx","type":"uint256"},{"name":"min_dy","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"exchange","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dx","type":"uint256"},{"name":"min_dy","type":"uint256"},{"name":"receiver","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"exchange_received","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dx","type":"uint256"},{"name":"min_dy","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"exchange_received","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dx","type":"uint256"},{"name":"min_dy","type":"uint256"},{"name":"receiver","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"add_liquidity","inputs":[{"name":"amounts","type":"uint256[3]"},{"name":"min_mint_amount","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"add_liquidity","inputs":[{"name":"amounts","type":"uint256[3]"},{"name":"min_mint_amount","type":"uint256"},{"name":"receiver","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity","inputs":[{"name":"_amount","type":"uint256"},{"name":"min_amounts","type":"uint256[3]"}],"outputs":[{"name":"","type":"uint256[3]"}]},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity","inputs":[{"name":"_amount","type":"uint256"},{"name":"min_amounts","type":"uint256[3]"},{"name":"receiver","type":"address"}],"outputs":[{"name":"","type":"uint256[3]"}]},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity_one_coin","inputs":[{"name":"token_amount","type":"uint256"},{"name":"i","type":"uint256"},{"name":"min_amount","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity_one_coin","inputs":[{"name":"token_amount","type":"uint256"},{"name":"i","type":"uint256"},{"name":"min_amount","type":"uint256"},{"name":"receiver","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"transferFrom","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"transfer","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"approve","inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"permit","inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"fee_receiver","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"admin","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"calc_token_amount","inputs":[{"name":"amounts","type":"uint256[3]"},{"name":"deposit","type":"bool"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"get_dy","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dx","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"get_dx","inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"},{"name":"dy","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"lp_price","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"get_virtual_price","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"price_oracle","inputs":[{"name":"k","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"xcp_oracle","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"last_prices","inputs":[{"name":"k","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"price_scale","inputs":[{"name":"k","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"fee","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"calc_withdraw_one_coin","inputs":[{"name":"token_amount","type":"uint256"},{"name":"i","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"calc_token_fee","inputs":[{"name":"amounts","type":"uint256[3]"},{"name":"xp","type":"uint256[3]"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"A","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"gamma","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"mid_fee","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"out_fee","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"fee_gamma","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"allowed_extra_profit","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"adjustment_step","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"ma_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"precisions","inputs":[],"outputs":[{"name":"","type":"uint256[3]"}]},{"stateMutability":"view","type":"function","name":"fee_calc","inputs":[{"name":"xp","type":"uint256[3]"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"DOMAIN_SEPARATOR","inputs":[],"outputs":[{"name":"","type":"bytes32"}]},{"stateMutability":"nonpayable","type":"function","name":"ramp_A_gamma","inputs":[{"name":"future_A","type":"uint256"},{"name":"future_gamma","type":"uint256"},{"name":"future_time","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"stop_ramp_A_gamma","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"apply_new_parameters","inputs":[{"name":"_new_mid_fee","type":"uint256"},{"name":"_new_out_fee","type":"uint256"},{"name":"_new_fee_gamma","type":"uint256"},{"name":"_new_allowed_extra_profit","type":"uint256"},{"name":"_new_adjustment_step","type":"uint256"},{"name":"_new_ma_time","type":"uint256"},{"name":"_new_xcp_ma_time","type":"uint256"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"MATH","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"coins","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"factory","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"last_timestamp","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"last_xcp","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"xcp_ma_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"initial_A_gamma","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"initial_A_gamma_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"future_A_gamma","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"future_A_gamma_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"balances","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"D","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"xcp_profit","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"xcp_profit_a","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"virtual_price","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"packed_rebalancing_params","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"packed_fee_params","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"ADMIN_FEE","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8"}]},{"stateMutability":"view","type":"function","name":"version","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"balanceOf","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"allowance","inputs":[{"name":"arg0","type":"address"},{"name":"arg1","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"nonces","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"salt","inputs":[],"outputs":[{"name":"","type":"bytes32"}]}]

61556d3d81600a3d39f3fe71006153895150346103c557602061556a600039600051604060208261556a01600039600051116103c557602060208261556a0160003960005101808261556a016080395050602061558a600039600051602060208261556a01600039600051116103c557602060208261556a0160003960005101808261556a0160e039505060206155aa6000396000518060a01c6103c5576101205260206155ca6000396000518060a01c6103c5576101405260206155ea6000396000518060a01c6103c55761016052602061560a6000396000518060a01c6103c55761018052602061562a6000396000518060a01c6103c5576101a052610180516151e9523361526952602060805101600081601f0160051c600381116103c557801561013c57905b8060051b608001518160051b61010001615189015260010181811861011c575b505050602060e05101600081601f0160051c600281116103c557801561017e57905b8060051b60e001518160051b61016001615189015260010181811861015e575b505050610120516152095261014051615229526101605161524952602061566a6040396101ac6101c061037a565b6101c080516151895260208101516151a95260408101516151c95250602061568a600039600051600855602061568a600039600051600a5560206156ca60003960005160135560206156aa60003960005160145560206156ea60003960005160015560206156ea60003960005160025560206156ea600039600051600455426040524260605261023d6101c06103b6565b6101c051600555670de0b6b3a764000060115561f37460075560206152895101600081601f0160051c600381116103c557801561029757905b8060051b6101000161518901518160051b6101c00152600101818118610276575b5050506101c080516020820120905061532952602061564a6000396000516153695246615349527fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564726101e05261532951610200527fd61c1033330c368dfc371f5b1e7133f4794e104642e5a3c87aba7a6a3441c8ff6102205246610240523061026052615369516102805260c06101c0526101c0805160208201209050615389523060007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60006101c05260206101c0a36151896103ca610000396153a9610000f35b67ffffffffffffffff60405160801c16815267ffffffffffffffff60405160401c16602082015267ffffffffffffffff60405116604082015250565b60605160801b60405117815250565b600080fd60003560e01c6002603f820660011b61510b01601e39600051565b63ed6c1546811861287257346151065760206151e960403960206040f3612872565b63c6610657811861007157602436103417615106576020600435600281116151065760051b6080016151890160403960206040f35b63556d6e9f81186128725760643610341761510657602061526960003960005163e31593d8606052602060606004607c845afa6100b3573d600060003e3d6000fd5b60203d10615106576060518060a01c6151065760a05260a09050516040526020604051633bb1f8c1606052606060046080373060e052602060606084607c845afa610103573d600060003e3d6000fd5b60203d106151065760609050f3612872565b63c45a01558118610133573461510657602061526960403960206040f35b637ecebe00811460033611161561287257602436103417615106576004358060a01c61510657604052601a60405160205260005260406000205460605260206060f3612872565b634d23bfa0811861287257346151065760055460405260206040f3612872565b63175753e981186101b657346151065760065460405260206040f35b63dd62ed3e811861287257604436103417615106576004358060a01c615106576040526024358060a01c615106576060526018604051602052600052604060002080606051602052600052604060002090505460805260206080f3612872565b6399f6bdda811861023257346151065760075460405260206040f35b63204fe3d5811861024e57346151065760085460405260206040f35b634515cef381186128725760843610341761510657336106205261080a56612872565b63e89876ff811861028d57346151065760095460405260206040f35b635b41b90881186102ac5760843610341761510657336109a052611778565b63a9059cbb811861287257604436103417615106576004358060a01c6151065760c0523360405260c0516060526024356080526102e7614fd5565b600160e052602060e0f3612872565b63f30cfad58118612872573461510657600a5460405260206040f3612872565b63f9ed95978118610332573461510657600b5460405260206040f35b633644e5158118612872573461510657602061034f610120615071565b610120f3612872565b634903b0d1811861038557602436103417615106576004356002811161510657600c015460405260206040f35b636872765381186128725760243610341761510657600054600214615106576002546040526103b5610100612ca6565b610100600435600181116151065760051b810190505160e0526001546040526103df610120612ca6565b610120600435600181116151065760051b81019050516101005260055460405261040a610140612e01565b6101405161012052426101205110156105255760045460405261042e610160612ca6565b610160600435600181116151065760051b810190505161014052601354604052610459610180612878565b610180604081019050516101605261012051604052610160516060526104806101a0612e26565b6101a0516101805261014051610100518060011b818160011c18615106579050808281188284100218905090506101805180670de0b6b3a764000003670de0b6b3a76400008111615106579050808202811583838304141715615106579050905060e0516101805180820281158383830414171561510657905090508082018281106151065790509050670de0b6b3a7640000810490506101a05260206101a061052a565b602060e05bf3612872565b630f529ba2811861054c573461510657600f5460405260206040f35b6372d4f0e28118610578573461510657602060145460405261056e6060612878565b6060604081019050f35b63244c7c2e8118612872573461510657602061526960003960005163f851a440610120526020610120600461013c845afa6105b8573d600060003e3d6000fd5b60203d1061510657610120518060a01c61510657610160526101609050513318615106576105e7610160612b4d565b610160805161012052602081015161014052506101205160801b61016052610140516101605117610160526101605160085561016051600a554260095542600b557f5f0e7fba3d100c9e19446e1c92fe436f0a9a22fe99669360e4fdd6d3de2fc2846101205161018052610140516101a052426101c0526060610180a100612872565b637ba1a74d811861068657346151065760105460405260206040f35b63e361640581186106a257346151065760145460405260206040f35b6323c6afea8118612872573461510657600054600214615106576005546040526106cc60e0612e01565b60e06020810190505160c05260035460e0524260c05110156107825760c0516040526007546060526106ff610120612e26565b61012051610100526006546101005180670de0b6b3a764000003670de0b6b3a76400008111615106579050808202811583838304141715615106579050905060e0516101005180820281158383830414171561510657905090508082018281106151065790509050670de0b6b3a764000081049050610120526020610120610787565b602060e05bf3612872565b630b7b594b81186107a957346151065760115460405260206040f35b630c46b72a811861287257346151065760125460405260206040f3612872565b633dd6547881186107e557346151065760135460405260206040f35b6375b96abc81186128725760a436103417615106576084358060a01c61510657610620525b600054600214615106576002600055610824610680612b4d565b61068080516106405260208101516106605250600c5461068052600d546106a052600e546106c05260c0366106e0376004356024358082018281106151065790509050604435808201828110615106579050905015615106576001546107a0526107a051604052610896610800612ca6565b61080080516107c05260208101516107e0525061068051610800526106a051610820526106c051610840526060366108603760006003905b806108c0526108c051600281116151065760051b600401351561097f576108c0516040526108c051600281116151065760051b6004013560605233608052600060a05261091c6108e06128c3565b6108e0516108c051600281116151065760051b61086001526108c051600281116151065760051b61068001516108c051600281116151065760051b610860015180820182811061510657905090506108c051600281116151065760051b61068001525b6001018181186108ce57505061068051602061518960003960005180820281158383830414171561510657905090506106805261080051602061518960003960005180820281158383830414171561510657905090506108005260006003905b806108c05260016108c05110610b0957670de0b6b3a76400006108c051600281116151065760051b61068001516108c05160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905060206108c051600281116151065760051b615189016000396000518082028115838383041417156151065790509050046108c051600281116151065760051b6106800152670de0b6b3a76400006108c051600281116151065760051b610800015160206108c051600281116151065760051b615189016000396000516108c05160018103818111615106579050600181116151065760051b6107c00151028082028115838383041417156151065790509050046108c051600281116151065760051b61080001525b6108c051600281116151065760051b610860015115610b6d576108c051600281116151065760051b61068001516108c051600281116151065760051b610800015180820382811161510657905090506108c051600281116151065760051b6106e001525b6001018181186109df57505042600b5411610b8e57600f5461078052610bff565b60206151e9600039600051637b12e0096108c052610640516108e052610660516109005261080051610920526108205161094052610840516109605260006109805260206108c060c46108dc845afa610bec573d600060003e3d6000fd5b60203d10615106576108c0905051610780525b60206151e9600039600051637b12e0096108e0526106405161090052610660516109205261068051610940526106a051610960526106c0516109805260006109a05260206108e060c46108fc845afa610c5d573d600060003e3d6000fd5b60203d10615106576108e09050516108c0526019546108e0526107805115610cc7576108e0516108c051808202811583838304141715615106579050905061078051801561510657808204905090506108e051808203828111615106579050905061074052610ce9565b6108c0516040526107a051606052610ce0610900614247565b61090051610740525b6107405115615106576107805115610e42576106e0516101e0526107005161020052610720516102205261068051610240526106a051610260526106c05161028052610d3661090061433f565b610900516107405180820281158383830414171561510657905090506402540be40081049050600181018181106151065790506107605261074051610760518082038281116151065790509050610740526108e0516107405180820182811061510657905090506108e0526106205160405261074051606052610dba61090061446e565b610900506016546402540be4006107605164012a05f20081028164012a05f2008204186151065790500480820182811061510657905090506016556106405160e052610660516101005261068051610120526106a051610140526106c051610160526108c0516101805260006101a052610e35610900612f41565b610900516107a052610e92565b6108c051600f55670de0b6b3a7640000601255670de0b6b3a7640000601055670de0b6b3a7640000601155610740516003556106205160405261074051606052610e8d61090061446e565b610900505b606435610740511015610f05576008610900527f536c6970706167650000000000000000000000000000000000000000000000006109205261090050610900518061092001601f826000031636823750506308c379a06108c05260206108e052601f19601f6109005101166044016108dcfd5b610620517fe1b60455bd9e33720b547f60e4e0cfbf1252d0f2ee0147d53029945f39fe3c1a610860516109005261088051610920526108a0516109405261076051610960526108e051610980526107a0516109a05260c0610900a260206107406003600055f3612872565b634469ed148118610f8f57346151065764012a05f20060405260206040f35b632da5dc2181186113725760a436103417615106576084358060a01c61510657610180525b6000546002146151065760026000556004356101a052600c546101c052600d546101e052600e54610200526060366102203760195461028052336040526004356060526110026102a06144e3565b6102a050610280516101a051186110575760006003905b806102a0526102a051600281116151065760051b6101c001516102a051600281116151065760051b6102200152600101818118611019575050611101565b6101a051600181038181116151065790506101a05260006003905b806102a0526102a051600281116151065760051b6101c001516101a051808202811583838304141715615106579050905061028051801561510657808204905090506102a051600281116151065760051b61022001526102a051600281116151065760051b602401356102a051600281116151065760051b610220015110615106576001018181186110725750505b600f546102a0526102a051610280516102a0516101a0518082028115838383041417156151065790509050048082038281116151065790509050600f5560006003905b806102c0526102c0516040526102c051600281116151065760051b610220015160605261018051608052611176612aa0565b600101818118611144575050337fd6cc314a0b1e3b2579f8e64248e82434072e8271290eef8ad0886709304195f5610220516102c052610240516102e05261026051610300526102805160043580820382811161510657905090506103205260806102c0a2600c54604052600d54606052600e5460805260015460a0526111fe610320614558565b61032080516102c05260208101516102e0526040810151610300525060206151e960003960005163bad1dc26610340526102c051610360526102e05161038052610300516103a0526020610340606461035c845afa611262573d600060003e3d6000fd5b60203d106151065761034090505161032052600554604052611285610380612e01565b610380805161034052602081015161036052504261036051101561135f5760035461038052610360516040526007546060526112c26103c0612e26565b6103c0516103a052670de0b6b3a7640000610320516103a05180670de0b6b3a764000003670de0b6b3a764000081116151065790508082028115838383041417156151065790509050610380516103a0518082028115838383041417156151065790509050808201828110615106579050905004600355426103605261034051604052610360516060526113576103c06128b4565b6103c0516005555b6103205160065560606102206003600055f35b63572e56258118612872576064361034176151065760206060600460603761139b6101e0612d08565b6101e0f3612872565b6306fdde038118612872573461510657602080604052806040016020602061528960003960005101806152898339508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f3612872565b6395d89b41811861287257346151065760208060405280604001602060206152e960003960005101806152e98339508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f3612872565b63313ce5678118611479573461510657601260405260206040f35b6392526c0c811861149f573461510657602060145460405261149b6060612878565b6060f35b6309c3da6a81186128725734615106576013546040526114bf6060612878565b6060604081019050516102b68102816102b68204186151065790506103e88104905060c052602060c0f3612872565b6354fd4d50811861287257346151065760208060805260066040527f76322e302e30000000000000000000000000000000000000000000000000000060605260408160800181518152602082015160208201528051806020830101601f82600003163682375050601f19601f8251602001011690509050810190506080f3612872565b6370a08231811861287257602436103417615106576004358060a01c61510657604052601760405160205260005260406000205460605260206060f3612872565b6318160ddd811861287257346151065760195460405260206040f3612872565b63bfa0b13381186115f0573461510657602061536960403960206040f35b63767691e781186117085760a436103417615106576084358060a01c615106576109a0525b60005460021461510657600260005560043560405260443560605233608052600160a0526116446109e06128c3565b6109e0516109c05260406004610620376109c051610660526064356106805261166e610a40613beb565b610a4080516109e0526020810151610a00526040810151610a2052506024356040526109e0516060526109a0516080526116a6612aa0565b337f143f1f8e861fbdeddd5b46e844b7d3ac7b86a122f36e8c463859ee6811b1f29c600435610a40526109c051610a6052602435610a80526109e051610aa052610a0051610ac052610a2051610ae05260c0610a40a260206109e06003600055f35b63a3f7cdd58118612872576024361034176151065760005460021461510657602060015460405261173960e0612ca6565b60e0600435600181116151065760051b81019050f3612872565b63a64833a0811861186b5760a436103417615106576084358060a01c615106576109a0525b60005460021461510657600260005560043560405260443560605233608052600060a0526117a76109e06128c3565b6109e0516109c05260406004610620376109c05161066052606435610680526117d1610a40613beb565b610a4080516109e0526020810151610a00526040810151610a2052506024356040526109e0516060526109a051608052611809612aa0565b337f143f1f8e861fbdeddd5b46e844b7d3ac7b86a122f36e8c463859ee6811b1f29c600435610a40526109c051610a6052602435610a80526109e051610aa052610a0051610ac052610a2051610ae05260c0610a40a260206109e06003600055f35b63f1dc3cc98118612872576064361034176151065733610620526118f556612872565b6329b244bb81186128725760843610341761510657336109a05261161556612872565b63ecb586a581186118d057608436103417615106573361018052610fb4565b630fbcee6e8118611ada57608436103417615106576064358060a01c61510657610620525b60005460021461510657600260005561190c614668565b611917610680612b4d565b6106808051610640526020810151610660525060e03661068037610640516101e0526106605161020052604060046102203742600b54116102605261195d610760614a5f565b61076080516106805260208101516106a0526040810180516106e0526020810151610700526040810151610720525060a08101516107405250604435610680511015611a09576008610760527f536c6970706167650000000000000000000000000000000000000000000000006107805261076050610760518061078001601f826000031636823750506308c379a061072052602061074052601f19601f61076051011660440161073cfd5b33604052600435606052611a1e6107606144e3565b610760506106405160e05261066051610100526106e05161012052610700516101405261072051610160526106a0516101805260006101a052611a62610780612f41565b61078051610760526024356040526106805160605261062051608052611a86612aa0565b337fe200e24d4a4c7cd367dd9befe394dc8a14e6d58c88ff5e2f512d65a9e0aa9c5c6040600461078037610680516107c052610740516107e052610760516108005260a0610780a260206106806003600055f35b633883e119811861287257608436103417615106576064358060011c61510657604052602061526960003960005163e31593d8608052602060806004609c845afa611b2a573d600060003e3d6000fd5b60203d10615106576080518060a01c6151065760c05260c09050516060526020606051638585c4b16080526060600460a0376040516101005230610120526020608060a4609c845afa611b82573d600060003e3d6000fd5b60203d106151065760809050f3612872565b6323b872dd8118611c6457606436103417615106576004358060a01c6151065760c0526024358060a01c6151065760e052601860c051602052600052604060002080336020526000526040600020905054610100527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101005114611c3d5760c05160405233606052610100516044358082038281116151065790509050608052611c3d614f7c565b60c05160405260e051606052604435608052611c57614fd5565b6001610120526020610120f35b6375f6602681186128725760e43610341761510657600054600214615106576002600055602061526960003960005163f851a44060a052602060a0600460bc845afa611cb5573d600060003e3d6000fd5b60203d106151065760a0518060a01c6151065760e05260e09050513318615106576060600460a037601454604052611cee610160612878565b610160805161010052602081015161012052604081015161014052506402540be40060c0511115611d25576101205160c052611d32565b6207a12060c05110615106575b6402540be40160a05110611d48576101005160a0525b60c05160a0511161510657670de0b6b3a763ffff60e0511115611d71576101405160e052611d7a565b60e05115615106575b60a05160405260c05160605260e051608052611d97610160614f66565b610160516014556060606461016037601354604052611db7610220612878565b61022080516101c05260208101516101e05260408101516102005250670de0b6b3a76400016101605110611dee576101c051610160525b670de0b6b3a76400016101805110611e09576101e051610180525b620d505d6101a0511115611e2457610200516101a052611e30565b60576101a05110615106575b61016051604052610180516060526101a051608052611e50610220614f66565b6102205160135560c43561022052620d505d610220511115611e785760075461022052611e84565b60576102205110615106575b610220516007557f1c65bbdc939f346e5d6f0bde1f072819947438d4fc7b182cc59c2f6dc550408760a0516102405260c0516102605260e05161028052610160516102a052610180516102c0526101a0516102e05260c4356103005260e0610240a1600360005500612872565b63095ea7b3811861287257604436103417615106576004358060a01c6151065760c0523360405260c051606052602435608052611f2c614f7c565b600160e052602060e0f3612872565b63d505accf81186128725760e436103417615106576004358060a01c61510657610120526024358060a01c61510657610140526084358060081c6151065761016052610120511561510657606435421161510657601a6101205160205260005260406000205461018052600060026101c0527f19010000000000000000000000000000000000000000000000000000000000006101e0526101c0805160208201836103200181518152505080830192505050611ff8610200615071565b610200518161032001526020810190507f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c961024052610120516102605261014051610280526044356102a052610180516102c0526064356102e05260c061022052610220805160208201209050816103200152602081019050806103005261030090508051602082012090506101a052610120516000610240526101a0516101c052610160516101e052604060a461020037602061024060806101c060015afa5061024051186151065760016101805101601a6101205160205260005260406000205561012051604052610140516060526044356080526120f7614f7c565b60016101c05260206101c0f3612872565b63cab4d3db81186128725734615106576020602061526960003960005163cab4d3db604052602060406004605c845afa612147573d600060003e3d6000fd5b60203d10615106576040518060a01c6151065760805260809050f3612872565b63f851a44081186128725734615106576020602061526960003960005163f851a440604052602060406004605c845afa6121a6573d600060003e3d6000fd5b60203d10615106576040518060a01c6151065760805260809050f3612872565b6337ed3a7a81186128725760643610341761510657602061526960003960005163e31593d8606052602060606004607c845afa612208573d600060003e3d6000fd5b60203d10615106576060518060a01c6151065760a05260a090505160405260206040516399bf0b76606052606060046080373060e052602060606084607c845afa612258573d600060003e3d6000fd5b60203d106151065760609050f3612872565b6354f0f7d5811861234557346151065760005460021461510657600254604052612295610120612ca6565b610120805160e052602081015161010052506012546003810281600382041861510657905060206151e960003960005163f42c56c26101205260e051610100518082028115838383041417156151065790509050610140526020610120602461013c845afa612309573d600060003e3d6000fd5b60203d1061510657610120905051808202811583838304141715615106579050905069d3c21bcecceda100000081049050610160526020610160f35b63cde699fa81186128725760c436103417615106576020606060046101e037606060646102403761237761034061433f565b610340f3612872565b63bb7b8b80811861287257346151065760005460021461510657600f546040526001546060526123b1610180614247565b61018051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050601954801561510657808204905090506101a05260206101a0f3612872565b635918901781186128725760243610341761510657602060045460405261241960e0612ca6565b60e0600435600181116151065760051b81019050f3612872565b63ddca3f4381186128725734615106576020600c54604052600d54606052600e5460805260015460a0526124686101e0614558565b6101e080516102605260208101516102805260408101516102a0525061026051606052610280516080526102a05160a0526124a4610240612d08565b610240f3612872565b634fb08c5e8118612872576044361034176151065760206124cf610600612b4d565b61060080516107005260208101516107205250604060046107403742600b541161078052610700516101e0526107205161020052610740516102205261076051610240526107805161026052612526610640614a5f565b610640f3612872565b63f446c1d08118612872573461510657602061254c610120612b4d565b610120f3612872565b63b1373929811861257d5734615106576020612572610120612b4d565b610120602081019050f35b63ee8de6758118612872573461510657602060145460405261259f6060612878565b6060602081019050f3612872565b6349fe9e77811861287257346151065760206013546040526125cf6060612878565b6060f3612872565b63083812e5811861287257346151065760206013546040526125f96060612878565b6060602081019050f3612872565b633620604b8118612872573461510657606061518960403960606040f3612872565b635e24807281186128725760643610341761510657602061526960003960005163f851a440610120526020610120600461013c845afa61266e573d600060003e3d6000fd5b60203d1061510657610120518060a01c61510657610160526101609050513318615106576009546201517f81018181106151065790504211156151065742620151808101818110615106579050600181038181116151065790506044351115615106576126dc610160612b4d565b610160805161012052602081015161014052506101205160801b6101605261014051610160511761016052610a8c6004351061510657631017df8060043511615106576402540be400602435106151065766b1a2bc2ec500006024351161510657600435670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610120518015615106578082049050905061018052678ac7230489e8000061018051116151065767016345785d8a0000610180511061510657602435670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610140518015615106578082049050905061018052678ac7230489e8000061018051116151065767016345785d8a0000610180511061510657610160516008554260095560043560801b6101a0526024356101a051176101a052604435600b556101a051600a557fe35f0559b0642164e286b30df2077ec3a05426617a25db7578fd20ba39a6cd05610120516101c0526004356101e05261014051610200526024356102205242610240526044356102605260c06101c0a1005b60006000fd5b67ffffffffffffffff60405160801c16815267ffffffffffffffff60405160401c16602082015267ffffffffffffffff60405116604082015250565b60605160801b60405117815250565b6020604051600281116151065760051b608001615189016000396000516370a0823160e0523061010052602060e0602460fc845afa612907573d600060003e3d6000fd5b60203d106151065760e090505160c05260a0511561297c5760c0516040516002811161510657600c0154808203828111615106579050905060e05260605160e05110615106576040516002811161510657600c01805460e051808201828110615106579050905081555060e051815250612a9e565b6020604051600281116151065760051b608001615189016000396000516323b872dd60e05260805161010052306101205260605161014052602060e0606460fc6000855af16129d0573d600060003e3d6000fd5b3d6129e757803b15615106576001610160526129ff565b60203d106151065760e0518060011c61510657610160525b61016090505115615106576020604051600281116151065760051b608001615189016000396000516370a082316101005230610120526020610100602461011c845afa612a51573d600060003e3d6000fd5b60203d106151065761010090505160c051808203828111615106579050905060e0526040516002811161510657600c01805460e051808201828110615106579050905081555060e0518152505b565b6040516002811161510657600c01805460605180820382811161510657905090508155506020604051600281116151065760051b6080016151890160003960005163a9059cbb60a05260805160c05260605160e052602060a0604460bc6000855af1612b11573d600060003e3d6000fd5b3d612b2857803b1561510657600161010052612b40565b60203d106151065760a0518060011c61510657610100525b6101009050511561510657565b600b54604052600a546060526fffffffffffffffffffffffffffffffff6060511660805260605160801c60a052604051421015612c965760085460c05260095460e05260405160e05180820382811161510657905090506040524260e051808203828111615106579050905060e05260405160e05180820382811161510657905090506101005260c05160801c61010051808202811583838304141715615106579050905060a05160e051808202811583838304141715615106579050905080820182811061510657905090506040518015615106578082049050905060a0526fffffffffffffffffffffffffffffffff60c0511661010051808202811583838304141715615106579050905060805160e05180820281158383830414171561510657905090508082018281106151065790509050604051801561510657808204905090506080525b60a0518152608051602082015250565b60403660603760405160a05260006002905b8060c0526fffffffffffffffffffffffffffffffff60a0511660c051600181116151065760051b6060015260a05160801c60a052600101818118612cb85750506060518152608051602082015250565b601454604052612d19610120612878565b610120805160c052602081015160e0526040810151610100525060206151e960003960005163fa18042d61014052606051610160526080516101805260a0516101a052610100516101c0526020610140608461015c845afa612d80573d600060003e3d6000fd5b60203d106151065761014090505161012052670de0b6b3a764000060c05161012051808202811583838304141715615106579050905060e0516101205180670de0b6b3a764000003670de0b6b3a764000081116151065790508082028115838383041417156151065790509050808201828110615106579050905004815250565b6fffffffffffffffffffffffffffffffff60405116815260405160801c602082015250565b60206151e96000396000516381d18d87608052606051426040518082038281116151065790509050670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050048060ff1c615106577f800000000000000000000000000000000000000000000000000000000000000081146151065760000360a052602060806024609c845afa612ebc573d600060003e3d6000fd5b60203d10615106576080905051815250565b60403660803760006002905b8060c05260805160801b60805260c0518060010360018111615106579050600181116151065760051b6040015160a0526ffffffffffffffffffffffffffffffffe60a051116151065760805160a05117608052600101818118612eda575050608051815250565b600254604052612f52610200612ca6565b61020080516101c05260208101516101e05250600454604052612f76610240612ca6565b610240805161020052602081015161022052506001546102405261024051604052612fa26102a0612ca6565b6102a080516102605260208101516102805250601354604052612fc6610300612878565b61030080516102a05260208101516102c05260408101516102e052506019546103005260105461032052601254610340526005546040526130086103a0612e01565b6103a08051610360526020810151610380525060006103a0524261036051101561315b57610360516040526102e0516060526130456103c0612e26565b6103c0516103a05260006002905b806103c052670de0b6b3a76400006103c051600181116151065760051b61020001516103c051600181116151065760051b61026001518060011b818160011c18615106579050808281188284100218905090506103a05180670de0b6b3a764000003670de0b6b3a7640000811161510657905080820281158383830414171561510657905090506103c051600181116151065760051b6101c001516103a05180820281158383830414171561510657905090508082018281106151065790509050046103c051600181116151065760051b6101c001526001018181186130535750506101c0516040526101e05160605261314e6103c0612ece565b6103c05160025542610360525b42610380511015613201576003546103c052610380516040526007546060526131856103e0612e26565b6103e0516103a052670de0b6b3a76400006006546103a05180670de0b6b3a764000003670de0b6b3a7640000811161510657905080820281158383830414171561510657905090506103c0516103a051808202811583838304141715615106579050905080820182811061510657905090500460035542610380525b610360516040526103805160605261321a6103c06128b4565b6103c051600555610180516103c052610180516132a35760206151e9600039600051637b12e0096103e05260e0516104005261010051610420526101205161044052610140516104605261016051610480526101a0516104a05260206103e060c46103fc845afa613290573d600060003e3d6000fd5b60203d10615106576103e09050516103c0525b60206151e960003960005163754b76b36103e0526101205161040052610140516104205261016051610440526103c0516104605260e05161048052610100516104a05260406103e060c46103fc845afa613302573d600060003e3d6000fd5b60403d10615106576103e090508051610200526020810151610220525060006002905b806103e052670de0b6b3a76400006103e051600181116151065760051b61020001516103e051600181116151065760051b61026001518082028115838383041417156151065790509050046103e051600181116151065760051b610200015260010181811861332557505061020051604052610220516060526133a96103e0612ece565b6103e0516004556060366103e03760036103c051046103e05260006002905b80610440526103c051670de0b6b3a7640000810281670de0b6b3a764000082041861510657905061044051600181116151065760051b610260015160038102816003820418615106579050801561510657808204905090506104405160018101818110615106579050600281116151065760051b6103e001526001018181186133c8575050670de0b6b3a764000061044052670de0b6b3a76400006104605261034051156135ab5760206151e960003960005163bad1dc266104a0526103e0516104c052610400516104e052610420516105005260206104a060646104bc845afa6134b8573d600060003e3d6000fd5b60203d10615106576104a09050516104805261048051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506103005180156151065780820490509050610460526103405161032051610460518082028115838383041417156151065790509050046104405242600b5410156135a3576103405161046051116135a35760046104a0527f4c6f7373000000000000000000000000000000000000000000000000000000006104c0526104a0506104a051806104c001601f826000031636823750506308c379a061046052602061048052601f19601f6104a051011660440161047cfd5b610480516006555b61044051601055610440516102a0518060011b818160011c186151065790508082018281106151065790509050610460518060011b818160011c18615106579050670de0b6b3a764000081038181116151065790501115613bd3576040366104803760006002905b806104c0526104c051600181116151065760051b61026001516104c051600181116151065760051b6101c00151670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050046104a052670de0b6b3a76400016104a051101561368d576104a051670de0b6b3a7640000036104a0526136a0565b670de0b6b3a76400006104a051036104a0525b6104a0516fffffffffffffffffffffffffffffffff8111615106576002810a9050610480510161048052600101818118613613575050610480518060b5710100000000000000000000000000000000008210613703578160801c91508060401b90505b69010000000000000000008210613721578160401c91508060201b90505b65010000000000821061373b578160201c91508060101b90505b63010000008210613753578160101c91508060081b90505b620100008201810260121c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c90508083048082811882841002189050905090509050610480526102c05160056104805104808281188284110218905090506104c0526104c051610480511115613bd3576040366104e03760006002905b80610520526104805161052051600181116151065760051b61026001516104c051610480510380820281158383830414171561510657905090506104c05161052051600181116151065760051b6101c00151808202811583838304141715615106579050905080820182811061510657905090500461052051600181116151065760051b6104e001526001018181186137f5575050610120516103e0526101405161040052610160516104205260006002905b806105205261052051600181116151065760051b61026001516105205160018101818110615106579050600281116151065760051b610120015161052051600181116151065760051b6104e001518082028115838383041417156151065790509050046105205160018101818110615106579050600281116151065760051b6103e001526001018181186138a857505060206151e9600039600051637b12e0096105405260e0516105605261010051610580526103e0516105a052610400516105c052610420516105e052600061060052602061054060c461055c845afa613995573d600060003e3d6000fd5b60203d10615106576105409050516105205261052051156151065760006003905b80610540526105205161054051600281116151065760051b6103e00151670de0b6b3a7640000810281670de0b6b3a76400008204186151065790500461056052662386f26fc10000610560511015613a0f576000613a20565b68056bc75e2d631000006105605111155b15615106576001018181186139b6575050600361052051046103e05260006002905b806105405261052051670de0b6b3a7640000810281670de0b6b3a764000082041861510657905061054051600181116151065760051b6104e0015160038102816003820418615106579050801561510657808204905090506105405160018101818110615106579050600281116151065760051b6103e00152600101818118613a425750506103005160206151e960003960005163bad1dc26610540526103e051610560526104005161058052610420516105a0526020610540606461055c845afa613b13573d600060003e3d6000fd5b60203d1061510657610540905051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790500461034052670de0b6b3a7640001610340511015613b5e576000613b8c565b61044051610340518060011b818160011c18615106579050670de0b6b3a76400008103818111615106579050115b15613bd3576104e05160405261050051606052613baa610540612ece565b610540516102405261052051600f55610340516012556102405160015561024051815250613be9565b6103c051600f5561046051601255610240518152505b565b61064051610620511461510657610660511561510657613c0c6106e0612b4d565b6106e080516106a05260208101516106c05250600c546106e052600d5461070052600e546107205260006107405261064051600281116151065760051b6106e001516107605261062051600281116151065760051b6106e00151610660518082038281116151065790509050610780526001546107a0526107a051604052613c95610800612ca6565b61080080516107c05260208101516107e052506106e051602061518960003960005180820281158383830414171561510657905090506106e052600160028101905b8061080052670de0b6b3a764000061080051600281116151065760051b6106e001516108005160018103818111615106579050600181116151065760051b6107c001518082028115838383041417156151065790509050602061080051600281116151065760051b6151890160003960005180820281158383830414171561510657905090500461080051600281116151065760051b6106e00152600101818118613cd7575050602061062051600281116151065760051b6151890161080039600b546108205242610820511115613ed25761078051610800518082028115838383041417156151065790509050610780526106205115613e1a57670de0b6b3a7640000610780516106205160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905004610780525b61062051600281116151065760051b6106e00151610840526107805161062051600281116151065760051b6106e0015260206151e9600039600051637b12e009610860526106a051610880526106c0516108a0526106e0516108c052610700516108e0526107205161090052600061092052602061086060c461087c845afa613ea8573d600060003e3d6000fd5b60203d1061510657610860905051600f556108405161062051600281116151065760051b6106e001525b600f546108405260206151e9600039600051634a2ab3be6108a0526106a0516108c0526106c0516108e0526106e05161090052610700516109205261072051610940526108405161096052610640516109805260406108a060e46108bc845afa613f41573d600060003e3d6000fd5b60403d10615106576108a090508051610860526020810151610880525061064051600281116151065760051b6106e001516108605180820382811161510657905090506107405261064051600281116151065760051b6106e001805161074051808203828111615106579050905081525061074051600181038181116151065790506107405261064051156140255761074051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506106405160018103818111615106579050600181116151065760051b6107c0015180156151065780820490509050610740525b61074051602061064051600281116151065760051b6151890160003960005180156151065780820490509050610740526402540be4006106e051606052610700516080526107205160a05261407b6108c0612d08565b6108c051610740518082028115838383041417156151065790509050046108a052610740516108a0518082038281116151065790509050610740526106805161074051101561412a5760086108c0527f536c6970706167650000000000000000000000000000000000000000000000006108e0526108c0506108c051806108e001601f826000031636823750506308c379a06108805260206108a052601f19601f6108c051011660440161089cfd5b610760516107405180820382811161510657905090506107605261076051602061064051600281116151065760051b6151890160003960005180820281158383830414171561510657905090506107605261064051156141cc57670de0b6b3a7640000610760516106405160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905004610760525b6107605161064051600281116151065760051b6106e001526106a05160e0526106c051610100526106e0516101205261070051610140526107205161016052600061018052610880516101a0526142246108c0612f41565b6108c0516107a0526107405181526108a05160208201526107a051604082015250565b60603660803760405160038104905060805260605160e052600160028101905b8061010052604051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506fffffffffffffffffffffffffffffffff60e05116600381028160038204186151065790508015615106578082049050905061010051600281116151065760051b6080015260e05160801c60e05260010181811861426757505060206151e960003960005163bad1dc26610100526080516101205260a0516101405260c051610160526020610100606461011c845afa61432c573d600060003e3d6000fd5b60203d1061510657610100905051815250565b600361024051606052610260516080526102805160a0526143616102c0612d08565b6102c0510260031c6102a05260006102c05260006003905b8060051b6101e001516102e0526102c0516102e05180820182811061510657905090506102c05260010181811861437957505060036102c051046102e05260006103005260006003905b8060051b6101e00151610320526102e05161032051116144015761030051610320516102e05103808201828110615106579050905061030052614421565b610300516102e05161032051038082018281106151065790509050610300525b6001018181186143c35750506102a0516103005180820281158383830414171561510657905090506102c05180156151065780820490509050620186a08101818110615106579050815250565b6019546060518082018281106151065790509050601955601760405160205260005260406000208054606051808201828110615106579050905081555060405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60605160805260206080a36001815250565b6019546060518082038281116151065790509050601955601760405160205260005260406000208054606051808203828111615106579050905081555060006040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60605160805260206080a36001815250565b60405160c05260605160e0526080516101005260c0516020615189600039600051808202811583838304141715615106579050905060c05260a05161012052600160028101905b80610140526fffffffffffffffffffffffffffffffff6101205116602061014051600281116151065760051b6151890160003960005180820281158383830414171561510657905090506101605261014051600281116151065760051b60c00151610160518082028115838383041417156151065790509050670de0b6b3a76400008104905061014051600281116151065760051b60c001526101205160801c6101205260010181811861459f57505060c051815260e051602082015261010051604082015250565b601554610180526201517f61018051420311156146895742600b541161468c565b60015b1561469657614a5d565b6010546101a0526011546101c0526019546101e0526101c0516101a05111156146cd57670de0b6b3a763ffff6101e05111156146d0565b60015b156146da57614a5d565b6146e5610240612b4d565b61024080516102005260208101516102205250600f54610240526012546102605260015461028052602061526960003960005163cab4d3db6102c05260206102c060046102dc845afa61473d573d600060003e3d6000fd5b60203d10615106576102c0518060a01c61510657610300526103009050516102a052600c546102c052600d546102e052600e54610300526404a817c8006101c0516101a0510364012a05f20081028164012a05f2008204186151065790500461032052601654610340526000610360526102a051156147c1576103205115156147c4565b60005b156148905761026051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506102605161032051808203828111615106579050905080156151065780820490509050670de0b6b3a7640000810381811161510657905061036052610340516101e051610360518082028115838383041417156151065790509050670de0b6b3a7640000810490508082018281106151065790509050610340526101a051610320518060011b818160011c1861510657905080820382811161510657905090506101a0525b6101e0516103405180820182811061510657905090506103805261024051604052610280516060526148c36103a0614247565b6103a051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610380518015615106578082049050905061026052670de0b6b3a763ffff610260511161491157614a5d565b60006016556101a0516010554260155561026051601255610240516103805161024051610340518082028115838383041417156151065790509050048082038281116151065790509050600f556101c0516101a0511115614974576101a0516011555b6060366103a0376103405115614a5d5760006003905b806104005261040051600281116151065760051b6102c00151610340518082028115838383041417156151065790509050610380518015615106578082049050905061040051600281116151065760051b6103a001526104005160405261040051600281116151065760051b6103a001516060526102a051608052614a0d612aa0565b60010181811861498a5750506102a0517f5ebe66c628d0969a211b99673a322baa5fc8a62baf286c175e254daef76f5bcc6103a051610400526103c051610420526103e051610440526060610400a25b565b60195461028052610280516102205111615106576002610240511161510657600c546102a052600d546102c052600e546102e0526060615189610300396000610360526020615189600039600051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610380526001546103a052610300516102a051808202811583838304141715615106579050905061030052600160028101905b806103c0526fffffffffffffffffffffffffffffffff6103a051166103e0526103c0516102405118614b5b576103e05161024051600281116151065760051b61030001518082028115838383041417156151065790509050610380525b670de0b6b3a76400006103c051600281116151065760051b61030001516103c051600281116151065760051b6102a0015180820281158383830414171561510657905090506103e0518082028115838383041417156151065790509050046103c051600281116151065760051b61030001526103a05160801c6103a052600101818118614afe57505061026051614bf857600f5461036052614c69565b60206151e9600039600051637b12e0096103c0526101e0516103e052610200516104005261030051610420526103205161044052610340516104605260006104805260206103c060c46103dc845afa614c56573d600060003e3d6000fd5b60203d10615106576103c0905051610360525b610360516103c052610300516103e0526103205161040052610340516104205261024051600281116151065760051b610300015160038102816003820418615106579050610220518082028115838383041417156151065790509050610280518015615106578082049050905061044052601454604052614ceb610480612878565b610480602081019050516104605261024051600281116151065760051b6103e00151610440511015614d6a5761024051600281116151065760051b6103e00180516104405180820382811161510657905090508152506103e051606052610400516080526104205160a052614d61610480612d08565b61048051610460525b61028051610220516103c05180820281158383830414171561510657905090500461048052610460516104805180820281158383830414171561510657905090506404a817c80081049050600181018181106151065790506104a0526104a0516003810281600382041861510657905061024051600281116151065760051b6102a0015180820281158383830414171561510657905090506103c051801561510657808204905090506104c0526103c051610480516104a051808203828111615106579050905080820382811161510657905090506103c05260206151e9600039600051634a2ab3be610500526101e05161052052610200516105405261030051610560526103205161058052610340516105a0526103c0516105c052610240516105e052604061050060e461051c845afa614eab573d600060003e3d6000fd5b60403d10615106576105009050516104e05261024051600281116151065760051b61030001516104e0518082038281116151065790509050670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506103805180156151065780820490509050610500526104e05161024051600281116151065760051b61030001526105005181526103c051602082015260408101610300518152610320516020820152610340516040820152506104c05160a082015250565b60805160605160401b60405160801b1717815250565b608051601860405160205260005260406000208060605160205260005260406000209050556060516040517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560805160a052602060a0a3565b606051308114614fe757801515614fea565b60005b90501561510657601760405160205260005260406000208054608051808203828111615106579050905081555060176060516020526000526040600020805460805180820182811061510657905090508155506060516040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60805160a052602060a0a3565b602061534960003960005146146150fb577fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac5647260605260206153296080397fd61c1033330c368dfc371f5b1e7133f4794e104642e5a3c87aba7a6a3441c8ff60a0524660c0523060e05260206153696101003960c06040526040805160208201209050815250615104565b60206153898239505b565b600080fd031623f21b9425552380287214010f7025d7188e02f62872145e252f28722872078d2607287215b2287224ad226a0216011528720358019a15711ef1053013a428722872003c07c9262925ad28722872001a2872066a21c6175315d2287214ee2872017a21082872027118b11f3b243328722872287221672872287228728419518981187e190220a16576797065728300030a0017

Deployed Bytecode

0xfe71006153895150346103c557602061556a600039600051604060208261556a01600039600051116103c557602060208261556a0160003960005101808261556a016080395050602061558a600039600051602060208261556a01600039600051116103c557602060208261556a0160003960005101808261556a0160e039505060206155aa6000396000518060a01c6103c5576101205260206155ca6000396000518060a01c6103c5576101405260206155ea6000396000518060a01c6103c55761016052602061560a6000396000518060a01c6103c55761018052602061562a6000396000518060a01c6103c5576101a052610180516151e9523361526952602060805101600081601f0160051c600381116103c557801561013c57905b8060051b608001518160051b61010001615189015260010181811861011c575b505050602060e05101600081601f0160051c600281116103c557801561017e57905b8060051b60e001518160051b61016001615189015260010181811861015e575b505050610120516152095261014051615229526101605161524952602061566a6040396101ac6101c061037a565b6101c080516151895260208101516151a95260408101516151c95250602061568a600039600051600855602061568a600039600051600a5560206156ca60003960005160135560206156aa60003960005160145560206156ea60003960005160015560206156ea60003960005160025560206156ea600039600051600455426040524260605261023d6101c06103b6565b6101c051600555670de0b6b3a764000060115561f37460075560206152895101600081601f0160051c600381116103c557801561029757905b8060051b6101000161518901518160051b6101c00152600101818118610276575b5050506101c080516020820120905061532952602061564a6000396000516153695246615349527fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564726101e05261532951610200527fd61c1033330c368dfc371f5b1e7133f4794e104642e5a3c87aba7a6a3441c8ff6102205246610240523061026052615369516102805260c06101c0526101c0805160208201209050615389523060007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60006101c05260206101c0a36151896103ca610000396153a9610000f35b67ffffffffffffffff60405160801c16815267ffffffffffffffff60405160401c16602082015267ffffffffffffffff60405116604082015250565b60605160801b60405117815250565b600080fd60003560e01c6002603f820660011b61510b01601e39600051565b63ed6c1546811861287257346151065760206151e960403960206040f3612872565b63c6610657811861007157602436103417615106576020600435600281116151065760051b6080016151890160403960206040f35b63556d6e9f81186128725760643610341761510657602061526960003960005163e31593d8606052602060606004607c845afa6100b3573d600060003e3d6000fd5b60203d10615106576060518060a01c6151065760a05260a09050516040526020604051633bb1f8c1606052606060046080373060e052602060606084607c845afa610103573d600060003e3d6000fd5b60203d106151065760609050f3612872565b63c45a01558118610133573461510657602061526960403960206040f35b637ecebe00811460033611161561287257602436103417615106576004358060a01c61510657604052601a60405160205260005260406000205460605260206060f3612872565b634d23bfa0811861287257346151065760055460405260206040f3612872565b63175753e981186101b657346151065760065460405260206040f35b63dd62ed3e811861287257604436103417615106576004358060a01c615106576040526024358060a01c615106576060526018604051602052600052604060002080606051602052600052604060002090505460805260206080f3612872565b6399f6bdda811861023257346151065760075460405260206040f35b63204fe3d5811861024e57346151065760085460405260206040f35b634515cef381186128725760843610341761510657336106205261080a56612872565b63e89876ff811861028d57346151065760095460405260206040f35b635b41b90881186102ac5760843610341761510657336109a052611778565b63a9059cbb811861287257604436103417615106576004358060a01c6151065760c0523360405260c0516060526024356080526102e7614fd5565b600160e052602060e0f3612872565b63f30cfad58118612872573461510657600a5460405260206040f3612872565b63f9ed95978118610332573461510657600b5460405260206040f35b633644e5158118612872573461510657602061034f610120615071565b610120f3612872565b634903b0d1811861038557602436103417615106576004356002811161510657600c015460405260206040f35b636872765381186128725760243610341761510657600054600214615106576002546040526103b5610100612ca6565b610100600435600181116151065760051b810190505160e0526001546040526103df610120612ca6565b610120600435600181116151065760051b81019050516101005260055460405261040a610140612e01565b6101405161012052426101205110156105255760045460405261042e610160612ca6565b610160600435600181116151065760051b810190505161014052601354604052610459610180612878565b610180604081019050516101605261012051604052610160516060526104806101a0612e26565b6101a0516101805261014051610100518060011b818160011c18615106579050808281188284100218905090506101805180670de0b6b3a764000003670de0b6b3a76400008111615106579050808202811583838304141715615106579050905060e0516101805180820281158383830414171561510657905090508082018281106151065790509050670de0b6b3a7640000810490506101a05260206101a061052a565b602060e05bf3612872565b630f529ba2811861054c573461510657600f5460405260206040f35b6372d4f0e28118610578573461510657602060145460405261056e6060612878565b6060604081019050f35b63244c7c2e8118612872573461510657602061526960003960005163f851a440610120526020610120600461013c845afa6105b8573d600060003e3d6000fd5b60203d1061510657610120518060a01c61510657610160526101609050513318615106576105e7610160612b4d565b610160805161012052602081015161014052506101205160801b61016052610140516101605117610160526101605160085561016051600a554260095542600b557f5f0e7fba3d100c9e19446e1c92fe436f0a9a22fe99669360e4fdd6d3de2fc2846101205161018052610140516101a052426101c0526060610180a100612872565b637ba1a74d811861068657346151065760105460405260206040f35b63e361640581186106a257346151065760145460405260206040f35b6323c6afea8118612872573461510657600054600214615106576005546040526106cc60e0612e01565b60e06020810190505160c05260035460e0524260c05110156107825760c0516040526007546060526106ff610120612e26565b61012051610100526006546101005180670de0b6b3a764000003670de0b6b3a76400008111615106579050808202811583838304141715615106579050905060e0516101005180820281158383830414171561510657905090508082018281106151065790509050670de0b6b3a764000081049050610120526020610120610787565b602060e05bf3612872565b630b7b594b81186107a957346151065760115460405260206040f35b630c46b72a811861287257346151065760125460405260206040f3612872565b633dd6547881186107e557346151065760135460405260206040f35b6375b96abc81186128725760a436103417615106576084358060a01c61510657610620525b600054600214615106576002600055610824610680612b4d565b61068080516106405260208101516106605250600c5461068052600d546106a052600e546106c05260c0366106e0376004356024358082018281106151065790509050604435808201828110615106579050905015615106576001546107a0526107a051604052610896610800612ca6565b61080080516107c05260208101516107e0525061068051610800526106a051610820526106c051610840526060366108603760006003905b806108c0526108c051600281116151065760051b600401351561097f576108c0516040526108c051600281116151065760051b6004013560605233608052600060a05261091c6108e06128c3565b6108e0516108c051600281116151065760051b61086001526108c051600281116151065760051b61068001516108c051600281116151065760051b610860015180820182811061510657905090506108c051600281116151065760051b61068001525b6001018181186108ce57505061068051602061518960003960005180820281158383830414171561510657905090506106805261080051602061518960003960005180820281158383830414171561510657905090506108005260006003905b806108c05260016108c05110610b0957670de0b6b3a76400006108c051600281116151065760051b61068001516108c05160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905060206108c051600281116151065760051b615189016000396000518082028115838383041417156151065790509050046108c051600281116151065760051b6106800152670de0b6b3a76400006108c051600281116151065760051b610800015160206108c051600281116151065760051b615189016000396000516108c05160018103818111615106579050600181116151065760051b6107c00151028082028115838383041417156151065790509050046108c051600281116151065760051b61080001525b6108c051600281116151065760051b610860015115610b6d576108c051600281116151065760051b61068001516108c051600281116151065760051b610800015180820382811161510657905090506108c051600281116151065760051b6106e001525b6001018181186109df57505042600b5411610b8e57600f5461078052610bff565b60206151e9600039600051637b12e0096108c052610640516108e052610660516109005261080051610920526108205161094052610840516109605260006109805260206108c060c46108dc845afa610bec573d600060003e3d6000fd5b60203d10615106576108c0905051610780525b60206151e9600039600051637b12e0096108e0526106405161090052610660516109205261068051610940526106a051610960526106c0516109805260006109a05260206108e060c46108fc845afa610c5d573d600060003e3d6000fd5b60203d10615106576108e09050516108c0526019546108e0526107805115610cc7576108e0516108c051808202811583838304141715615106579050905061078051801561510657808204905090506108e051808203828111615106579050905061074052610ce9565b6108c0516040526107a051606052610ce0610900614247565b61090051610740525b6107405115615106576107805115610e42576106e0516101e0526107005161020052610720516102205261068051610240526106a051610260526106c05161028052610d3661090061433f565b610900516107405180820281158383830414171561510657905090506402540be40081049050600181018181106151065790506107605261074051610760518082038281116151065790509050610740526108e0516107405180820182811061510657905090506108e0526106205160405261074051606052610dba61090061446e565b610900506016546402540be4006107605164012a05f20081028164012a05f2008204186151065790500480820182811061510657905090506016556106405160e052610660516101005261068051610120526106a051610140526106c051610160526108c0516101805260006101a052610e35610900612f41565b610900516107a052610e92565b6108c051600f55670de0b6b3a7640000601255670de0b6b3a7640000601055670de0b6b3a7640000601155610740516003556106205160405261074051606052610e8d61090061446e565b610900505b606435610740511015610f05576008610900527f536c6970706167650000000000000000000000000000000000000000000000006109205261090050610900518061092001601f826000031636823750506308c379a06108c05260206108e052601f19601f6109005101166044016108dcfd5b610620517fe1b60455bd9e33720b547f60e4e0cfbf1252d0f2ee0147d53029945f39fe3c1a610860516109005261088051610920526108a0516109405261076051610960526108e051610980526107a0516109a05260c0610900a260206107406003600055f3612872565b634469ed148118610f8f57346151065764012a05f20060405260206040f35b632da5dc2181186113725760a436103417615106576084358060a01c61510657610180525b6000546002146151065760026000556004356101a052600c546101c052600d546101e052600e54610200526060366102203760195461028052336040526004356060526110026102a06144e3565b6102a050610280516101a051186110575760006003905b806102a0526102a051600281116151065760051b6101c001516102a051600281116151065760051b6102200152600101818118611019575050611101565b6101a051600181038181116151065790506101a05260006003905b806102a0526102a051600281116151065760051b6101c001516101a051808202811583838304141715615106579050905061028051801561510657808204905090506102a051600281116151065760051b61022001526102a051600281116151065760051b602401356102a051600281116151065760051b610220015110615106576001018181186110725750505b600f546102a0526102a051610280516102a0516101a0518082028115838383041417156151065790509050048082038281116151065790509050600f5560006003905b806102c0526102c0516040526102c051600281116151065760051b610220015160605261018051608052611176612aa0565b600101818118611144575050337fd6cc314a0b1e3b2579f8e64248e82434072e8271290eef8ad0886709304195f5610220516102c052610240516102e05261026051610300526102805160043580820382811161510657905090506103205260806102c0a2600c54604052600d54606052600e5460805260015460a0526111fe610320614558565b61032080516102c05260208101516102e0526040810151610300525060206151e960003960005163bad1dc26610340526102c051610360526102e05161038052610300516103a0526020610340606461035c845afa611262573d600060003e3d6000fd5b60203d106151065761034090505161032052600554604052611285610380612e01565b610380805161034052602081015161036052504261036051101561135f5760035461038052610360516040526007546060526112c26103c0612e26565b6103c0516103a052670de0b6b3a7640000610320516103a05180670de0b6b3a764000003670de0b6b3a764000081116151065790508082028115838383041417156151065790509050610380516103a0518082028115838383041417156151065790509050808201828110615106579050905004600355426103605261034051604052610360516060526113576103c06128b4565b6103c0516005555b6103205160065560606102206003600055f35b63572e56258118612872576064361034176151065760206060600460603761139b6101e0612d08565b6101e0f3612872565b6306fdde038118612872573461510657602080604052806040016020602061528960003960005101806152898339508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f3612872565b6395d89b41811861287257346151065760208060405280604001602060206152e960003960005101806152e98339508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f3612872565b63313ce5678118611479573461510657601260405260206040f35b6392526c0c811861149f573461510657602060145460405261149b6060612878565b6060f35b6309c3da6a81186128725734615106576013546040526114bf6060612878565b6060604081019050516102b68102816102b68204186151065790506103e88104905060c052602060c0f3612872565b6354fd4d50811861287257346151065760208060805260066040527f76322e302e30000000000000000000000000000000000000000000000000000060605260408160800181518152602082015160208201528051806020830101601f82600003163682375050601f19601f8251602001011690509050810190506080f3612872565b6370a08231811861287257602436103417615106576004358060a01c61510657604052601760405160205260005260406000205460605260206060f3612872565b6318160ddd811861287257346151065760195460405260206040f3612872565b63bfa0b13381186115f0573461510657602061536960403960206040f35b63767691e781186117085760a436103417615106576084358060a01c615106576109a0525b60005460021461510657600260005560043560405260443560605233608052600160a0526116446109e06128c3565b6109e0516109c05260406004610620376109c051610660526064356106805261166e610a40613beb565b610a4080516109e0526020810151610a00526040810151610a2052506024356040526109e0516060526109a0516080526116a6612aa0565b337f143f1f8e861fbdeddd5b46e844b7d3ac7b86a122f36e8c463859ee6811b1f29c600435610a40526109c051610a6052602435610a80526109e051610aa052610a0051610ac052610a2051610ae05260c0610a40a260206109e06003600055f35b63a3f7cdd58118612872576024361034176151065760005460021461510657602060015460405261173960e0612ca6565b60e0600435600181116151065760051b81019050f3612872565b63a64833a0811861186b5760a436103417615106576084358060a01c615106576109a0525b60005460021461510657600260005560043560405260443560605233608052600060a0526117a76109e06128c3565b6109e0516109c05260406004610620376109c05161066052606435610680526117d1610a40613beb565b610a4080516109e0526020810151610a00526040810151610a2052506024356040526109e0516060526109a051608052611809612aa0565b337f143f1f8e861fbdeddd5b46e844b7d3ac7b86a122f36e8c463859ee6811b1f29c600435610a40526109c051610a6052602435610a80526109e051610aa052610a0051610ac052610a2051610ae05260c0610a40a260206109e06003600055f35b63f1dc3cc98118612872576064361034176151065733610620526118f556612872565b6329b244bb81186128725760843610341761510657336109a05261161556612872565b63ecb586a581186118d057608436103417615106573361018052610fb4565b630fbcee6e8118611ada57608436103417615106576064358060a01c61510657610620525b60005460021461510657600260005561190c614668565b611917610680612b4d565b6106808051610640526020810151610660525060e03661068037610640516101e0526106605161020052604060046102203742600b54116102605261195d610760614a5f565b61076080516106805260208101516106a0526040810180516106e0526020810151610700526040810151610720525060a08101516107405250604435610680511015611a09576008610760527f536c6970706167650000000000000000000000000000000000000000000000006107805261076050610760518061078001601f826000031636823750506308c379a061072052602061074052601f19601f61076051011660440161073cfd5b33604052600435606052611a1e6107606144e3565b610760506106405160e05261066051610100526106e05161012052610700516101405261072051610160526106a0516101805260006101a052611a62610780612f41565b61078051610760526024356040526106805160605261062051608052611a86612aa0565b337fe200e24d4a4c7cd367dd9befe394dc8a14e6d58c88ff5e2f512d65a9e0aa9c5c6040600461078037610680516107c052610740516107e052610760516108005260a0610780a260206106806003600055f35b633883e119811861287257608436103417615106576064358060011c61510657604052602061526960003960005163e31593d8608052602060806004609c845afa611b2a573d600060003e3d6000fd5b60203d10615106576080518060a01c6151065760c05260c09050516060526020606051638585c4b16080526060600460a0376040516101005230610120526020608060a4609c845afa611b82573d600060003e3d6000fd5b60203d106151065760809050f3612872565b6323b872dd8118611c6457606436103417615106576004358060a01c6151065760c0526024358060a01c6151065760e052601860c051602052600052604060002080336020526000526040600020905054610100527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101005114611c3d5760c05160405233606052610100516044358082038281116151065790509050608052611c3d614f7c565b60c05160405260e051606052604435608052611c57614fd5565b6001610120526020610120f35b6375f6602681186128725760e43610341761510657600054600214615106576002600055602061526960003960005163f851a44060a052602060a0600460bc845afa611cb5573d600060003e3d6000fd5b60203d106151065760a0518060a01c6151065760e05260e09050513318615106576060600460a037601454604052611cee610160612878565b610160805161010052602081015161012052604081015161014052506402540be40060c0511115611d25576101205160c052611d32565b6207a12060c05110615106575b6402540be40160a05110611d48576101005160a0525b60c05160a0511161510657670de0b6b3a763ffff60e0511115611d71576101405160e052611d7a565b60e05115615106575b60a05160405260c05160605260e051608052611d97610160614f66565b610160516014556060606461016037601354604052611db7610220612878565b61022080516101c05260208101516101e05260408101516102005250670de0b6b3a76400016101605110611dee576101c051610160525b670de0b6b3a76400016101805110611e09576101e051610180525b620d505d6101a0511115611e2457610200516101a052611e30565b60576101a05110615106575b61016051604052610180516060526101a051608052611e50610220614f66565b6102205160135560c43561022052620d505d610220511115611e785760075461022052611e84565b60576102205110615106575b610220516007557f1c65bbdc939f346e5d6f0bde1f072819947438d4fc7b182cc59c2f6dc550408760a0516102405260c0516102605260e05161028052610160516102a052610180516102c0526101a0516102e05260c4356103005260e0610240a1600360005500612872565b63095ea7b3811861287257604436103417615106576004358060a01c6151065760c0523360405260c051606052602435608052611f2c614f7c565b600160e052602060e0f3612872565b63d505accf81186128725760e436103417615106576004358060a01c61510657610120526024358060a01c61510657610140526084358060081c6151065761016052610120511561510657606435421161510657601a6101205160205260005260406000205461018052600060026101c0527f19010000000000000000000000000000000000000000000000000000000000006101e0526101c0805160208201836103200181518152505080830192505050611ff8610200615071565b610200518161032001526020810190507f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c961024052610120516102605261014051610280526044356102a052610180516102c0526064356102e05260c061022052610220805160208201209050816103200152602081019050806103005261030090508051602082012090506101a052610120516000610240526101a0516101c052610160516101e052604060a461020037602061024060806101c060015afa5061024051186151065760016101805101601a6101205160205260005260406000205561012051604052610140516060526044356080526120f7614f7c565b60016101c05260206101c0f3612872565b63cab4d3db81186128725734615106576020602061526960003960005163cab4d3db604052602060406004605c845afa612147573d600060003e3d6000fd5b60203d10615106576040518060a01c6151065760805260809050f3612872565b63f851a44081186128725734615106576020602061526960003960005163f851a440604052602060406004605c845afa6121a6573d600060003e3d6000fd5b60203d10615106576040518060a01c6151065760805260809050f3612872565b6337ed3a7a81186128725760643610341761510657602061526960003960005163e31593d8606052602060606004607c845afa612208573d600060003e3d6000fd5b60203d10615106576060518060a01c6151065760a05260a090505160405260206040516399bf0b76606052606060046080373060e052602060606084607c845afa612258573d600060003e3d6000fd5b60203d106151065760609050f3612872565b6354f0f7d5811861234557346151065760005460021461510657600254604052612295610120612ca6565b610120805160e052602081015161010052506012546003810281600382041861510657905060206151e960003960005163f42c56c26101205260e051610100518082028115838383041417156151065790509050610140526020610120602461013c845afa612309573d600060003e3d6000fd5b60203d1061510657610120905051808202811583838304141715615106579050905069d3c21bcecceda100000081049050610160526020610160f35b63cde699fa81186128725760c436103417615106576020606060046101e037606060646102403761237761034061433f565b610340f3612872565b63bb7b8b80811861287257346151065760005460021461510657600f546040526001546060526123b1610180614247565b61018051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050601954801561510657808204905090506101a05260206101a0f3612872565b635918901781186128725760243610341761510657602060045460405261241960e0612ca6565b60e0600435600181116151065760051b81019050f3612872565b63ddca3f4381186128725734615106576020600c54604052600d54606052600e5460805260015460a0526124686101e0614558565b6101e080516102605260208101516102805260408101516102a0525061026051606052610280516080526102a05160a0526124a4610240612d08565b610240f3612872565b634fb08c5e8118612872576044361034176151065760206124cf610600612b4d565b61060080516107005260208101516107205250604060046107403742600b541161078052610700516101e0526107205161020052610740516102205261076051610240526107805161026052612526610640614a5f565b610640f3612872565b63f446c1d08118612872573461510657602061254c610120612b4d565b610120f3612872565b63b1373929811861257d5734615106576020612572610120612b4d565b610120602081019050f35b63ee8de6758118612872573461510657602060145460405261259f6060612878565b6060602081019050f3612872565b6349fe9e77811861287257346151065760206013546040526125cf6060612878565b6060f3612872565b63083812e5811861287257346151065760206013546040526125f96060612878565b6060602081019050f3612872565b633620604b8118612872573461510657606061518960403960606040f3612872565b635e24807281186128725760643610341761510657602061526960003960005163f851a440610120526020610120600461013c845afa61266e573d600060003e3d6000fd5b60203d1061510657610120518060a01c61510657610160526101609050513318615106576009546201517f81018181106151065790504211156151065742620151808101818110615106579050600181038181116151065790506044351115615106576126dc610160612b4d565b610160805161012052602081015161014052506101205160801b6101605261014051610160511761016052610a8c6004351061510657631017df8060043511615106576402540be400602435106151065766b1a2bc2ec500006024351161510657600435670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610120518015615106578082049050905061018052678ac7230489e8000061018051116151065767016345785d8a0000610180511061510657602435670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610140518015615106578082049050905061018052678ac7230489e8000061018051116151065767016345785d8a0000610180511061510657610160516008554260095560043560801b6101a0526024356101a051176101a052604435600b556101a051600a557fe35f0559b0642164e286b30df2077ec3a05426617a25db7578fd20ba39a6cd05610120516101c0526004356101e05261014051610200526024356102205242610240526044356102605260c06101c0a1005b60006000fd5b67ffffffffffffffff60405160801c16815267ffffffffffffffff60405160401c16602082015267ffffffffffffffff60405116604082015250565b60605160801b60405117815250565b6020604051600281116151065760051b608001615189016000396000516370a0823160e0523061010052602060e0602460fc845afa612907573d600060003e3d6000fd5b60203d106151065760e090505160c05260a0511561297c5760c0516040516002811161510657600c0154808203828111615106579050905060e05260605160e05110615106576040516002811161510657600c01805460e051808201828110615106579050905081555060e051815250612a9e565b6020604051600281116151065760051b608001615189016000396000516323b872dd60e05260805161010052306101205260605161014052602060e0606460fc6000855af16129d0573d600060003e3d6000fd5b3d6129e757803b15615106576001610160526129ff565b60203d106151065760e0518060011c61510657610160525b61016090505115615106576020604051600281116151065760051b608001615189016000396000516370a082316101005230610120526020610100602461011c845afa612a51573d600060003e3d6000fd5b60203d106151065761010090505160c051808203828111615106579050905060e0526040516002811161510657600c01805460e051808201828110615106579050905081555060e0518152505b565b6040516002811161510657600c01805460605180820382811161510657905090508155506020604051600281116151065760051b6080016151890160003960005163a9059cbb60a05260805160c05260605160e052602060a0604460bc6000855af1612b11573d600060003e3d6000fd5b3d612b2857803b1561510657600161010052612b40565b60203d106151065760a0518060011c61510657610100525b6101009050511561510657565b600b54604052600a546060526fffffffffffffffffffffffffffffffff6060511660805260605160801c60a052604051421015612c965760085460c05260095460e05260405160e05180820382811161510657905090506040524260e051808203828111615106579050905060e05260405160e05180820382811161510657905090506101005260c05160801c61010051808202811583838304141715615106579050905060a05160e051808202811583838304141715615106579050905080820182811061510657905090506040518015615106578082049050905060a0526fffffffffffffffffffffffffffffffff60c0511661010051808202811583838304141715615106579050905060805160e05180820281158383830414171561510657905090508082018281106151065790509050604051801561510657808204905090506080525b60a0518152608051602082015250565b60403660603760405160a05260006002905b8060c0526fffffffffffffffffffffffffffffffff60a0511660c051600181116151065760051b6060015260a05160801c60a052600101818118612cb85750506060518152608051602082015250565b601454604052612d19610120612878565b610120805160c052602081015160e0526040810151610100525060206151e960003960005163fa18042d61014052606051610160526080516101805260a0516101a052610100516101c0526020610140608461015c845afa612d80573d600060003e3d6000fd5b60203d106151065761014090505161012052670de0b6b3a764000060c05161012051808202811583838304141715615106579050905060e0516101205180670de0b6b3a764000003670de0b6b3a764000081116151065790508082028115838383041417156151065790509050808201828110615106579050905004815250565b6fffffffffffffffffffffffffffffffff60405116815260405160801c602082015250565b60206151e96000396000516381d18d87608052606051426040518082038281116151065790509050670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050048060ff1c615106577f800000000000000000000000000000000000000000000000000000000000000081146151065760000360a052602060806024609c845afa612ebc573d600060003e3d6000fd5b60203d10615106576080905051815250565b60403660803760006002905b8060c05260805160801b60805260c0518060010360018111615106579050600181116151065760051b6040015160a0526ffffffffffffffffffffffffffffffffe60a051116151065760805160a05117608052600101818118612eda575050608051815250565b600254604052612f52610200612ca6565b61020080516101c05260208101516101e05250600454604052612f76610240612ca6565b610240805161020052602081015161022052506001546102405261024051604052612fa26102a0612ca6565b6102a080516102605260208101516102805250601354604052612fc6610300612878565b61030080516102a05260208101516102c05260408101516102e052506019546103005260105461032052601254610340526005546040526130086103a0612e01565b6103a08051610360526020810151610380525060006103a0524261036051101561315b57610360516040526102e0516060526130456103c0612e26565b6103c0516103a05260006002905b806103c052670de0b6b3a76400006103c051600181116151065760051b61020001516103c051600181116151065760051b61026001518060011b818160011c18615106579050808281188284100218905090506103a05180670de0b6b3a764000003670de0b6b3a7640000811161510657905080820281158383830414171561510657905090506103c051600181116151065760051b6101c001516103a05180820281158383830414171561510657905090508082018281106151065790509050046103c051600181116151065760051b6101c001526001018181186130535750506101c0516040526101e05160605261314e6103c0612ece565b6103c05160025542610360525b42610380511015613201576003546103c052610380516040526007546060526131856103e0612e26565b6103e0516103a052670de0b6b3a76400006006546103a05180670de0b6b3a764000003670de0b6b3a7640000811161510657905080820281158383830414171561510657905090506103c0516103a051808202811583838304141715615106579050905080820182811061510657905090500460035542610380525b610360516040526103805160605261321a6103c06128b4565b6103c051600555610180516103c052610180516132a35760206151e9600039600051637b12e0096103e05260e0516104005261010051610420526101205161044052610140516104605261016051610480526101a0516104a05260206103e060c46103fc845afa613290573d600060003e3d6000fd5b60203d10615106576103e09050516103c0525b60206151e960003960005163754b76b36103e0526101205161040052610140516104205261016051610440526103c0516104605260e05161048052610100516104a05260406103e060c46103fc845afa613302573d600060003e3d6000fd5b60403d10615106576103e090508051610200526020810151610220525060006002905b806103e052670de0b6b3a76400006103e051600181116151065760051b61020001516103e051600181116151065760051b61026001518082028115838383041417156151065790509050046103e051600181116151065760051b610200015260010181811861332557505061020051604052610220516060526133a96103e0612ece565b6103e0516004556060366103e03760036103c051046103e05260006002905b80610440526103c051670de0b6b3a7640000810281670de0b6b3a764000082041861510657905061044051600181116151065760051b610260015160038102816003820418615106579050801561510657808204905090506104405160018101818110615106579050600281116151065760051b6103e001526001018181186133c8575050670de0b6b3a764000061044052670de0b6b3a76400006104605261034051156135ab5760206151e960003960005163bad1dc266104a0526103e0516104c052610400516104e052610420516105005260206104a060646104bc845afa6134b8573d600060003e3d6000fd5b60203d10615106576104a09050516104805261048051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506103005180156151065780820490509050610460526103405161032051610460518082028115838383041417156151065790509050046104405242600b5410156135a3576103405161046051116135a35760046104a0527f4c6f7373000000000000000000000000000000000000000000000000000000006104c0526104a0506104a051806104c001601f826000031636823750506308c379a061046052602061048052601f19601f6104a051011660440161047cfd5b610480516006555b61044051601055610440516102a0518060011b818160011c186151065790508082018281106151065790509050610460518060011b818160011c18615106579050670de0b6b3a764000081038181116151065790501115613bd3576040366104803760006002905b806104c0526104c051600181116151065760051b61026001516104c051600181116151065760051b6101c00151670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050046104a052670de0b6b3a76400016104a051101561368d576104a051670de0b6b3a7640000036104a0526136a0565b670de0b6b3a76400006104a051036104a0525b6104a0516fffffffffffffffffffffffffffffffff8111615106576002810a9050610480510161048052600101818118613613575050610480518060b5710100000000000000000000000000000000008210613703578160801c91508060401b90505b69010000000000000000008210613721578160401c91508060201b90505b65010000000000821061373b578160201c91508060101b90505b63010000008210613753578160101c91508060081b90505b620100008201810260121c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c9050808184040160011c90508083048082811882841002189050905090509050610480526102c05160056104805104808281188284110218905090506104c0526104c051610480511115613bd3576040366104e03760006002905b80610520526104805161052051600181116151065760051b61026001516104c051610480510380820281158383830414171561510657905090506104c05161052051600181116151065760051b6101c00151808202811583838304141715615106579050905080820182811061510657905090500461052051600181116151065760051b6104e001526001018181186137f5575050610120516103e0526101405161040052610160516104205260006002905b806105205261052051600181116151065760051b61026001516105205160018101818110615106579050600281116151065760051b610120015161052051600181116151065760051b6104e001518082028115838383041417156151065790509050046105205160018101818110615106579050600281116151065760051b6103e001526001018181186138a857505060206151e9600039600051637b12e0096105405260e0516105605261010051610580526103e0516105a052610400516105c052610420516105e052600061060052602061054060c461055c845afa613995573d600060003e3d6000fd5b60203d10615106576105409050516105205261052051156151065760006003905b80610540526105205161054051600281116151065760051b6103e00151670de0b6b3a7640000810281670de0b6b3a76400008204186151065790500461056052662386f26fc10000610560511015613a0f576000613a20565b68056bc75e2d631000006105605111155b15615106576001018181186139b6575050600361052051046103e05260006002905b806105405261052051670de0b6b3a7640000810281670de0b6b3a764000082041861510657905061054051600181116151065760051b6104e0015160038102816003820418615106579050801561510657808204905090506105405160018101818110615106579050600281116151065760051b6103e00152600101818118613a425750506103005160206151e960003960005163bad1dc26610540526103e051610560526104005161058052610420516105a0526020610540606461055c845afa613b13573d600060003e3d6000fd5b60203d1061510657610540905051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790500461034052670de0b6b3a7640001610340511015613b5e576000613b8c565b61044051610340518060011b818160011c18615106579050670de0b6b3a76400008103818111615106579050115b15613bd3576104e05160405261050051606052613baa610540612ece565b610540516102405261052051600f55610340516012556102405160015561024051815250613be9565b6103c051600f5561046051601255610240518152505b565b61064051610620511461510657610660511561510657613c0c6106e0612b4d565b6106e080516106a05260208101516106c05250600c546106e052600d5461070052600e546107205260006107405261064051600281116151065760051b6106e001516107605261062051600281116151065760051b6106e00151610660518082038281116151065790509050610780526001546107a0526107a051604052613c95610800612ca6565b61080080516107c05260208101516107e052506106e051602061518960003960005180820281158383830414171561510657905090506106e052600160028101905b8061080052670de0b6b3a764000061080051600281116151065760051b6106e001516108005160018103818111615106579050600181116151065760051b6107c001518082028115838383041417156151065790509050602061080051600281116151065760051b6151890160003960005180820281158383830414171561510657905090500461080051600281116151065760051b6106e00152600101818118613cd7575050602061062051600281116151065760051b6151890161080039600b546108205242610820511115613ed25761078051610800518082028115838383041417156151065790509050610780526106205115613e1a57670de0b6b3a7640000610780516106205160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905004610780525b61062051600281116151065760051b6106e00151610840526107805161062051600281116151065760051b6106e0015260206151e9600039600051637b12e009610860526106a051610880526106c0516108a0526106e0516108c052610700516108e0526107205161090052600061092052602061086060c461087c845afa613ea8573d600060003e3d6000fd5b60203d1061510657610860905051600f556108405161062051600281116151065760051b6106e001525b600f546108405260206151e9600039600051634a2ab3be6108a0526106a0516108c0526106c0516108e0526106e05161090052610700516109205261072051610940526108405161096052610640516109805260406108a060e46108bc845afa613f41573d600060003e3d6000fd5b60403d10615106576108a090508051610860526020810151610880525061064051600281116151065760051b6106e001516108605180820382811161510657905090506107405261064051600281116151065760051b6106e001805161074051808203828111615106579050905081525061074051600181038181116151065790506107405261064051156140255761074051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506106405160018103818111615106579050600181116151065760051b6107c0015180156151065780820490509050610740525b61074051602061064051600281116151065760051b6151890160003960005180156151065780820490509050610740526402540be4006106e051606052610700516080526107205160a05261407b6108c0612d08565b6108c051610740518082028115838383041417156151065790509050046108a052610740516108a0518082038281116151065790509050610740526106805161074051101561412a5760086108c0527f536c6970706167650000000000000000000000000000000000000000000000006108e0526108c0506108c051806108e001601f826000031636823750506308c379a06108805260206108a052601f19601f6108c051011660440161089cfd5b610760516107405180820382811161510657905090506107605261076051602061064051600281116151065760051b6151890160003960005180820281158383830414171561510657905090506107605261064051156141cc57670de0b6b3a7640000610760516106405160018103818111615106579050600181116151065760051b6107c00151808202811583838304141715615106579050905004610760525b6107605161064051600281116151065760051b6106e001526106a05160e0526106c051610100526106e0516101205261070051610140526107205161016052600061018052610880516101a0526142246108c0612f41565b6108c0516107a0526107405181526108a05160208201526107a051604082015250565b60603660803760405160038104905060805260605160e052600160028101905b8061010052604051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506fffffffffffffffffffffffffffffffff60e05116600381028160038204186151065790508015615106578082049050905061010051600281116151065760051b6080015260e05160801c60e05260010181811861426757505060206151e960003960005163bad1dc26610100526080516101205260a0516101405260c051610160526020610100606461011c845afa61432c573d600060003e3d6000fd5b60203d1061510657610100905051815250565b600361024051606052610260516080526102805160a0526143616102c0612d08565b6102c0510260031c6102a05260006102c05260006003905b8060051b6101e001516102e0526102c0516102e05180820182811061510657905090506102c05260010181811861437957505060036102c051046102e05260006103005260006003905b8060051b6101e00151610320526102e05161032051116144015761030051610320516102e05103808201828110615106579050905061030052614421565b610300516102e05161032051038082018281106151065790509050610300525b6001018181186143c35750506102a0516103005180820281158383830414171561510657905090506102c05180156151065780820490509050620186a08101818110615106579050815250565b6019546060518082018281106151065790509050601955601760405160205260005260406000208054606051808201828110615106579050905081555060405160007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60605160805260206080a36001815250565b6019546060518082038281116151065790509050601955601760405160205260005260406000208054606051808203828111615106579050905081555060006040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60605160805260206080a36001815250565b60405160c05260605160e0526080516101005260c0516020615189600039600051808202811583838304141715615106579050905060c05260a05161012052600160028101905b80610140526fffffffffffffffffffffffffffffffff6101205116602061014051600281116151065760051b6151890160003960005180820281158383830414171561510657905090506101605261014051600281116151065760051b60c00151610160518082028115838383041417156151065790509050670de0b6b3a76400008104905061014051600281116151065760051b60c001526101205160801c6101205260010181811861459f57505060c051815260e051602082015261010051604082015250565b601554610180526201517f61018051420311156146895742600b541161468c565b60015b1561469657614a5d565b6010546101a0526011546101c0526019546101e0526101c0516101a05111156146cd57670de0b6b3a763ffff6101e05111156146d0565b60015b156146da57614a5d565b6146e5610240612b4d565b61024080516102005260208101516102205250600f54610240526012546102605260015461028052602061526960003960005163cab4d3db6102c05260206102c060046102dc845afa61473d573d600060003e3d6000fd5b60203d10615106576102c0518060a01c61510657610300526103009050516102a052600c546102c052600d546102e052600e54610300526404a817c8006101c0516101a0510364012a05f20081028164012a05f2008204186151065790500461032052601654610340526000610360526102a051156147c1576103205115156147c4565b60005b156148905761026051670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506102605161032051808203828111615106579050905080156151065780820490509050670de0b6b3a7640000810381811161510657905061036052610340516101e051610360518082028115838383041417156151065790509050670de0b6b3a7640000810490508082018281106151065790509050610340526101a051610320518060011b818160011c1861510657905080820382811161510657905090506101a0525b6101e0516103405180820182811061510657905090506103805261024051604052610280516060526148c36103a0614247565b6103a051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610380518015615106578082049050905061026052670de0b6b3a763ffff610260511161491157614a5d565b60006016556101a0516010554260155561026051601255610240516103805161024051610340518082028115838383041417156151065790509050048082038281116151065790509050600f556101c0516101a0511115614974576101a0516011555b6060366103a0376103405115614a5d5760006003905b806104005261040051600281116151065760051b6102c00151610340518082028115838383041417156151065790509050610380518015615106578082049050905061040051600281116151065760051b6103a001526104005160405261040051600281116151065760051b6103a001516060526102a051608052614a0d612aa0565b60010181811861498a5750506102a0517f5ebe66c628d0969a211b99673a322baa5fc8a62baf286c175e254daef76f5bcc6103a051610400526103c051610420526103e051610440526060610400a25b565b60195461028052610280516102205111615106576002610240511161510657600c546102a052600d546102c052600e546102e0526060615189610300396000610360526020615189600039600051670de0b6b3a7640000810281670de0b6b3a7640000820418615106579050610380526001546103a052610300516102a051808202811583838304141715615106579050905061030052600160028101905b806103c0526fffffffffffffffffffffffffffffffff6103a051166103e0526103c0516102405118614b5b576103e05161024051600281116151065760051b61030001518082028115838383041417156151065790509050610380525b670de0b6b3a76400006103c051600281116151065760051b61030001516103c051600281116151065760051b6102a0015180820281158383830414171561510657905090506103e0518082028115838383041417156151065790509050046103c051600281116151065760051b61030001526103a05160801c6103a052600101818118614afe57505061026051614bf857600f5461036052614c69565b60206151e9600039600051637b12e0096103c0526101e0516103e052610200516104005261030051610420526103205161044052610340516104605260006104805260206103c060c46103dc845afa614c56573d600060003e3d6000fd5b60203d10615106576103c0905051610360525b610360516103c052610300516103e0526103205161040052610340516104205261024051600281116151065760051b610300015160038102816003820418615106579050610220518082028115838383041417156151065790509050610280518015615106578082049050905061044052601454604052614ceb610480612878565b610480602081019050516104605261024051600281116151065760051b6103e00151610440511015614d6a5761024051600281116151065760051b6103e00180516104405180820382811161510657905090508152506103e051606052610400516080526104205160a052614d61610480612d08565b61048051610460525b61028051610220516103c05180820281158383830414171561510657905090500461048052610460516104805180820281158383830414171561510657905090506404a817c80081049050600181018181106151065790506104a0526104a0516003810281600382041861510657905061024051600281116151065760051b6102a0015180820281158383830414171561510657905090506103c051801561510657808204905090506104c0526103c051610480516104a051808203828111615106579050905080820382811161510657905090506103c05260206151e9600039600051634a2ab3be610500526101e05161052052610200516105405261030051610560526103205161058052610340516105a0526103c0516105c052610240516105e052604061050060e461051c845afa614eab573d600060003e3d6000fd5b60403d10615106576105009050516104e05261024051600281116151065760051b61030001516104e0518082038281116151065790509050670de0b6b3a7640000810281670de0b6b3a76400008204186151065790506103805180156151065780820490509050610500526104e05161024051600281116151065760051b61030001526105005181526103c051602082015260408101610300518152610320516020820152610340516040820152506104c05160a082015250565b60805160605160401b60405160801b1717815250565b608051601860405160205260005260406000208060605160205260005260406000209050556060516040517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560805160a052602060a0a3565b606051308114614fe757801515614fea565b60005b90501561510657601760405160205260005260406000208054608051808203828111615106579050905081555060176060516020526000526040600020805460805180820182811061510657905090508155506060516040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60805160a052602060a0a3565b602061534960003960005146146150fb577fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac5647260605260206153296080397fd61c1033330c368dfc371f5b1e7133f4794e104642e5a3c87aba7a6a3441c8ff60a0524660c0523060e05260206153696101003960c06040526040805160208201209050815250615104565b60206153898239505b565b600080fd031623f21b9425552380287214010f7025d7188e02f62872145e252f28722872078d2607287215b2287224ad226a0216011528720358019a15711ef1053013a428722872003c07c9262925ad28722872001a2872066a21c6175315d2287214ee2872017a21082872027118b11f3b243328722872287221672872287228728419518981187e190220a16576797065728300030a0017

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

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