ETH Price: $2,428.33 (+0.22%)

Transaction Decoder

Block:
15580886 at Sep-21-2022 09:30:47 AM +UTC
Transaction Fee:
0.000750303004371484 ETH $1.82
Gas Used:
118,054 Gas / 6.355591546 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x4Aab9e6e...25E3A804F
19.144299259232526894 Eth
Nonce: 1456
19.14354895622815541 Eth
Nonce: 1457
0.000750303004371484
(Fee Recipient: 0xE88...7F7)
33.326935491494992334 Eth33.327112572494992334 Eth0.000177081

Execution Trace

Vyper_contract.exchange( i=1, j=0, dx=4371299989920927739745, min_dy=6306984048977004536, use_eth=True ) => ( 3963877391197344453575983046348115674221700746820753546331534351508065746944 )
  • Vyper_contract.exchange( i=1, j=0, dx=4371299989920927739745, min_dy=6306984048977004536, use_eth=True ) => ( 3963877391197344453575983046348115674221700746820753546331534351508065746944 )
    • CNCToken.transferFrom( from=0x4Aab9e6eB125cDd378671188255f19325E3A804F, to=0x838af967537350D2C44ABB8c010E49E32673ab94, amount=4371299989920927739745 )
      File 1 of 3: Vyper_contract
      # @version 0.3.1
      # (c) Curve.Fi, 2021
      # Pool for two crypto assets
      
      # Universal implementation which can use both ETH and ERC20s
      from vyper.interfaces import ERC20
      
      
      interface Factory:
          def admin() -> address: view
          def fee_receiver() -> address: view
      
      interface CurveToken:
          def totalSupply() -> uint256: view
          def mint(_to: address, _value: uint256) -> bool: nonpayable
          def mint_relative(_to: address, frac: uint256) -> uint256: nonpayable
          def burnFrom(_to: address, _value: uint256) -> bool: nonpayable
      
      interface WETH:
          def deposit(): payable
          def withdraw(_amount: uint256): nonpayable
      
      
      # Events
      event TokenExchange:
          buyer: indexed(address)
          sold_id: uint256
          tokens_sold: uint256
          bought_id: uint256
          tokens_bought: uint256
      
      event AddLiquidity:
          provider: indexed(address)
          token_amounts: uint256[N_COINS]
          fee: uint256
          token_supply: 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
      
      event CommitNewParameters:
          deadline: indexed(uint256)
          admin_fee: uint256
          mid_fee: uint256
          out_fee: uint256
          fee_gamma: uint256
          allowed_extra_profit: uint256
          adjustment_step: uint256
          ma_half_time: uint256
      
      event NewParameters:
          admin_fee: uint256
          mid_fee: uint256
          out_fee: uint256
          fee_gamma: uint256
          allowed_extra_profit: uint256
          adjustment_step: uint256
          ma_half_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
      
      
      ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400
      MIN_RAMP_TIME: constant(uint256) = 86400
      
      MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9
      MIN_FEE: constant(uint256) = 5 * 10 ** 5  # 0.5 bps
      MAX_FEE: constant(uint256) = 10 * 10 ** 9
      MAX_A_CHANGE: constant(uint256) = 10
      NOISE_FEE: constant(uint256) = 10**5  # 0.1 bps
      
      MIN_GAMMA: constant(uint256) = 10**10
      MAX_GAMMA: constant(uint256) = 2 * 10**16
      
      MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 10
      MAX_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER * 100000
      
      EXP_PRECISION: constant(uint256) = 10**10
      
      N_COINS: constant(int128) = 2
      PRECISION: constant(uint256) = 10 ** 18  # The precision to convert to
      A_MULTIPLIER: constant(uint256) = 10000
      
      
      # Implementation can be changed by changing this constant
      WETH20: immutable(address)
      
      
      token: public(address)
      coins: public(address[N_COINS])
      
      price_scale: public(uint256)   # Internal price scale
      _price_oracle: uint256  # Price target given by MA
      
      last_prices: public(uint256)
      last_prices_timestamp: public(uint256)
      
      initial_A_gamma: public(uint256)
      future_A_gamma: public(uint256)
      initial_A_gamma_time: public(uint256)
      future_A_gamma_time: public(uint256)
      
      allowed_extra_profit: public(uint256)  # 2 * 10**12 - recommended value
      future_allowed_extra_profit: public(uint256)
      
      fee_gamma: public(uint256)
      future_fee_gamma: public(uint256)
      
      adjustment_step: public(uint256)
      future_adjustment_step: public(uint256)
      
      ma_half_time: public(uint256)
      future_ma_half_time: public(uint256)
      
      mid_fee: public(uint256)
      out_fee: public(uint256)
      admin_fee: public(uint256)
      future_mid_fee: public(uint256)
      future_out_fee: public(uint256)
      future_admin_fee: public(uint256)
      
      balances: public(uint256[N_COINS])
      D: public(uint256)
      
      factory: public(address)
      
      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 also used internally
      not_adjusted: bool
      
      admin_actions_deadline: public(uint256)
      
      # This must be changed for different N_COINS
      # For example:
      # N_COINS = 3 -> 1  (10**18 -> 10**18)
      # N_COINS = 4 -> 10**8  (10**18 -> 10**10)
      # PRICE_PRECISION_MUL: constant(uint256) = 1
      PRECISIONS: uint256  # packed
      
      
      @external
      def __init__(_weth: address):
          WETH20 = _weth
          self.mid_fee = 22022022
      
      
      @payable
      @external
      def __default__():
          pass
      
      
      # Internal Functions
      
      @internal
      @view
      def _get_precisions() -> uint256[2]:
          p0: uint256 = self.PRECISIONS
          p1: uint256 = 10 ** shift(p0, -8)
          p0 = 10 ** bitwise_and(p0, 255)
          return [p0, p1]
      
      
      @internal
      @view
      def xp() -> uint256[N_COINS]:
          precisions: uint256[2] = self._get_precisions()
          return [self.balances[0] * precisions[0],
                  self.balances[1] * precisions[1] * self.price_scale / PRECISION]
      
      
      @view
      @internal
      def _A_gamma() -> uint256[2]:
          t1: uint256 = self.future_A_gamma_time
      
          A_gamma_1: uint256 = self.future_A_gamma
          gamma1: uint256 = bitwise_and(A_gamma_1, 2**128-1)
          A1: uint256 = shift(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
      
              # Less readable but more compact way of writing and converting to uint256
              # gamma0: uint256 = bitwise_and(A_gamma_0, 2**128-1)
              # A0: uint256 = shift(A_gamma_0, -128)
              # A1 = A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0)
              # gamma1 = gamma0 + (gamma1 - gamma0) * (block.timestamp - t0) / (t1 - t0)
      
              t1 -= t0
              t0 = block.timestamp - t0
              t2: uint256 = t1 - t0
      
              A1 = (shift(A_gamma_0, -128) * t2 + A1 * t0) / t1
              gamma1 = (bitwise_and(A_gamma_0, 2**128-1) * t2 + gamma1 * t0) / t1
      
          return [A1, gamma1]
      
      
      @internal
      @view
      def _fee(xp: uint256[N_COINS]) -> uint256:
          """
          f = fee_gamma / (fee_gamma + (1 - K))
          where
          K = prod(x) / (sum(x) / N)**N
          (all normalized to 1e18)
          """
          fee_gamma: uint256 = self.fee_gamma
          f: uint256 = xp[0] + xp[1]  # sum
          f = fee_gamma * 10**18 / (
              fee_gamma + 10**18 - (10**18 * N_COINS**N_COINS) * xp[0] / f * xp[1] / f
          )
          return (self.mid_fee * f + self.out_fee * (10**18 - f)) / 10**18
      
      
      ### Math functions
      @internal
      @pure
      def geometric_mean(unsorted_x: uint256[N_COINS], sort: bool) -> uint256:
          """
          (x[0] * x[1] * ...) ** (1/N)
          """
          x: uint256[N_COINS] = unsorted_x
          if sort and x[0] < x[1]:
              x = [unsorted_x[1], unsorted_x[0]]
          D: uint256 = x[0]
          diff: uint256 = 0
          for i in range(255):
              D_prev: uint256 = D
              # tmp: uint256 = 10**18
              # for _x in x:
              #     tmp = tmp * _x / D
              # D = D * ((N_COINS - 1) * 10**18 + tmp) / (N_COINS * 10**18)
              # line below makes it for 2 coins
              D = (D + x[0] * x[1] / D) / N_COINS
              if D > D_prev:
                  diff = D - D_prev
              else:
                  diff = D_prev - D
              if diff <= 1 or diff * 10**18 < D:
                  return D
          raise "Did not converge"
      
      
      @internal
      @view
      def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256:
          """
          Finding the invariant using Newton method.
          ANN is higher by the factor A_MULTIPLIER
          ANN is already A * N**N
      
          Currently uses 60k gas
          """
          # Safety checks
          assert ANN > MIN_A - 1 and ANN < MAX_A + 1  # dev: unsafe values A
          assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1  # dev: unsafe values gamma
      
          # Initial value of invariant D is that for constant-product invariant
          x: uint256[N_COINS] = x_unsorted
          if x[0] < x[1]:
              x = [x_unsorted[1], x_unsorted[0]]
      
          assert x[0] > 10**9 - 1 and x[0] < 10**15 * 10**18 + 1  # dev: unsafe values x[0]
          assert x[1] * 10**18 / x[0] > 10**14-1  # dev: unsafe values x[i] (input)
      
          D: uint256 = N_COINS * self.geometric_mean(x, False)
          S: uint256 = x[0] + x[1]
      
          for i in range(255):
              D_prev: uint256 = D
      
              # K0: uint256 = 10**18
              # for _x in x:
              #     K0 = K0 * _x * N_COINS / D
              # collapsed for 2 coins
              K0: uint256 = (10**18 * N_COINS**2) * x[0] / D * x[1] / D
      
              _g1k0: uint256 = gamma + 10**18
              if _g1k0 > K0:
                  _g1k0 = _g1k0 - K0 + 1
              else:
                  _g1k0 = K0 - _g1k0 + 1
      
              # D / (A * N**N) * _g1k0**2 / gamma**2
              mul1: uint256 = 10**18 * D / gamma * _g1k0 / gamma * _g1k0 * A_MULTIPLIER / ANN
      
              # 2*N*K0 / _g1k0
              mul2: uint256 = (2 * 10**18) * N_COINS * K0 / _g1k0
      
              neg_fprime: uint256 = (S + S * mul2 / 10**18) + mul1 * N_COINS / K0 - mul2 * D / 10**18
      
              # D -= f / fprime
              D_plus: uint256 = D * (neg_fprime + S) / neg_fprime
              D_minus: uint256 = D*D / neg_fprime
              if 10**18 > K0:
                  D_minus += D * (mul1 / neg_fprime) / 10**18 * (10**18 - K0) / K0
              else:
                  D_minus -= D * (mul1 / neg_fprime) / 10**18 * (K0 - 10**18) / K0
      
              if D_plus > D_minus:
                  D = D_plus - D_minus
              else:
                  D = (D_minus - D_plus) / 2
      
              diff: uint256 = 0
              if D > D_prev:
                  diff = D - D_prev
              else:
                  diff = D_prev - D
              if diff * 10**14 < max(10**16, D):  # Could reduce precision for gas efficiency here
                  # Test that we are safe with the next newton_y
                  for _x in x:
                      frac: uint256 = _x * 10**18 / D
                      assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  # dev: unsafe values x[i]
                  return D
      
          raise "Did not converge"
      
      
      @internal
      @pure
      def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256:
          """
          Calculating x[i] given other balances x[0..N_COINS-1] and invariant D
          ANN = A * N**N
          """
          # Safety checks
          assert ANN > MIN_A - 1 and ANN < MAX_A + 1  # dev: unsafe values A
          assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1  # dev: unsafe values gamma
          assert D > 10**17 - 1 and D < 10**15 * 10**18 + 1 # dev: unsafe values D
      
          x_j: uint256 = x[1 - i]
          y: uint256 = D**2 / (x_j * N_COINS**2)
          K0_i: uint256 = (10**18 * N_COINS) * x_j / D
          # S_i = x_j
      
          # frac = x_j * 1e18 / D => frac = K0_i / N_COINS
          assert (K0_i > 10**16*N_COINS - 1) and (K0_i < 10**20*N_COINS + 1)  # dev: unsafe values x[i]
      
          # x_sorted: uint256[N_COINS] = x
          # x_sorted[i] = 0
          # x_sorted = self.sort(x_sorted)  # From high to low
          # x[not i] instead of x_sorted since x_soted has only 1 element
      
          convergence_limit: uint256 = max(max(x_j / 10**14, D / 10**14), 100)
      
          for j in range(255):
              y_prev: uint256 = y
      
              K0: uint256 = K0_i * y * N_COINS / D
              S: uint256 = x_j + y
      
              _g1k0: uint256 = gamma + 10**18
              if _g1k0 > K0:
                  _g1k0 = _g1k0 - K0 + 1
              else:
                  _g1k0 = K0 - _g1k0 + 1
      
              # D / (A * N**N) * _g1k0**2 / gamma**2
              mul1: uint256 = 10**18 * D / gamma * _g1k0 / gamma * _g1k0 * A_MULTIPLIER / ANN
      
              # 2*K0 / _g1k0
              mul2: uint256 = 10**18 + (2 * 10**18) * K0 / _g1k0
      
              yfprime: uint256 = 10**18 * y + S * mul2 + mul1
              _dyfprime: uint256 = D * mul2
              if yfprime < _dyfprime:
                  y = y_prev / 2
                  continue
              else:
                  yfprime -= _dyfprime
              fprime: uint256 = yfprime / y
      
              # y -= f / f_prime;  y = (y * fprime - f) / fprime
              # y = (yfprime + 10**18 * D - 10**18 * S) // fprime + mul1 // fprime * (10**18 - K0) // K0
              y_minus: uint256 = mul1 / fprime
              y_plus: uint256 = (yfprime + 10**18 * D) / fprime + y_minus * 10**18 / K0
              y_minus += 10**18 * S / fprime
      
              if y_plus < y_minus:
                  y = y_prev / 2
              else:
                  y = y_plus - y_minus
      
              diff: uint256 = 0
              if y > y_prev:
                  diff = y - y_prev
              else:
                  diff = y_prev - y
              if diff < max(convergence_limit, y / 10**14):
                  frac: uint256 = y * 10**18 / D
                  assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  # dev: unsafe value for y
                  return y
      
          raise "Did not converge"
      
      
      @internal
      @pure
      def halfpow(power: uint256) -> uint256:
          """
          1e18 * 0.5 ** (power/1e18)
      
          Inspired by: https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L128
          """
          intpow: uint256 = power / 10**18
          otherpow: uint256 = power - intpow * 10**18
          if intpow > 59:
              return 0
          result: uint256 = 10**18 / (2**intpow)
          if otherpow == 0:
              return result
      
          term: uint256 = 10**18
          x: uint256 = 5 * 10**17
          S: uint256 = 10**18
          neg: bool = False
      
          for i in range(1, 256):
              K: uint256 = i * 10**18
              c: uint256 = K - 10**18
              if otherpow > c:
                  c = otherpow - c
                  neg = not neg
              else:
                  c -= otherpow
              term = term * (c * x / 10**18) / K
              if neg:
                  S -= term
              else:
                  S += term
              if term < EXP_PRECISION:
                  return result * S / 10**18
      
          raise "Did not converge"
      ### end of Math functions
      
      
      @internal
      @view
      def get_xcp(D: uint256) -> uint256:
          x: uint256[N_COINS] = [D / N_COINS, D * PRECISION / (self.price_scale * N_COINS)]
          return self.geometric_mean(x, True)
      
      
      @internal
      def _claim_admin_fees():
          A_gamma: uint256[2] = self._A_gamma()
      
          xcp_profit: uint256 = self.xcp_profit
          xcp_profit_a: uint256 = self.xcp_profit_a
      
          # Gulp here
          for i in range(N_COINS):
              coin: address = self.coins[i]
              if coin == WETH20:
                  self.balances[i] = self.balance
              else:
                  self.balances[i] = ERC20(coin).balanceOf(self)
      
          vprice: uint256 = self.virtual_price
      
          if xcp_profit > xcp_profit_a:
              fees: uint256 = (xcp_profit - xcp_profit_a) * self.admin_fee / (2 * 10**10)
              if fees > 0:
                  receiver: address = Factory(self.factory).fee_receiver()
                  if receiver != ZERO_ADDRESS:
                      frac: uint256 = vprice * 10**18 / (vprice - fees) - 10**18
                      claimed: uint256 = CurveToken(self.token).mint_relative(receiver, frac)
                      xcp_profit -= fees*2
                      self.xcp_profit = xcp_profit
                      log ClaimAdminFee(receiver, claimed)
      
          total_supply: uint256 = CurveToken(self.token).totalSupply()
      
          # Recalculate D b/c we gulped
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], self.xp())
          self.D = D
      
          self.virtual_price = 10**18 * self.get_xcp(D) / total_supply
      
          if xcp_profit > xcp_profit_a:
              self.xcp_profit_a = xcp_profit
      
      
      @internal
      @view
      def internal_price_oracle() -> uint256:
          price_oracle: uint256 = self._price_oracle
          last_prices_timestamp: uint256 = self.last_prices_timestamp
      
          if last_prices_timestamp < block.timestamp:
              ma_half_time: uint256 = self.ma_half_time
              last_prices: uint256 = self.last_prices
              alpha: uint256 = self.halfpow((block.timestamp - last_prices_timestamp) * 10**18 / ma_half_time)
              return (last_prices * (10**18 - alpha) + price_oracle * alpha) / 10**18
      
          else:
              return price_oracle
      
      
      @internal
      def tweak_price(A_gamma: uint256[2],_xp: uint256[N_COINS], p_i: uint256, new_D: uint256):
          price_oracle: uint256 = self._price_oracle
          last_prices: uint256 = self.last_prices
          price_scale: uint256 = self.price_scale
          last_prices_timestamp: uint256 = self.last_prices_timestamp
          p_new: uint256 = 0
      
          if last_prices_timestamp < block.timestamp:
              # MA update required
              ma_half_time: uint256 = self.ma_half_time
              alpha: uint256 = self.halfpow((block.timestamp - last_prices_timestamp) * 10**18 / ma_half_time)
              price_oracle = (last_prices * (10**18 - alpha) + price_oracle * alpha) / 10**18
              self._price_oracle = price_oracle
              self.last_prices_timestamp = block.timestamp
      
          D_unadjusted: uint256 = new_D  # Withdrawal methods know new D already
          if new_D == 0:
              # We will need this a few times (35k gas)
              D_unadjusted = self.newton_D(A_gamma[0], A_gamma[1], _xp)
      
          if p_i > 0:
              last_prices = p_i
      
          else:
              # calculate real prices
              __xp: uint256[N_COINS] = _xp
              dx_price: uint256 = __xp[0] / 10**6
              __xp[0] += dx_price
              last_prices = price_scale * dx_price / (_xp[1] - self.newton_y(A_gamma[0], A_gamma[1], __xp, D_unadjusted, 1))
      
          self.last_prices = last_prices
      
          total_supply: uint256 = CurveToken(self.token).totalSupply()
          old_xcp_profit: uint256 = self.xcp_profit
          old_virtual_price: uint256 = self.virtual_price
      
          # Update profit numbers without price adjustment first
          xp: uint256[N_COINS] = [D_unadjusted / N_COINS, D_unadjusted * PRECISION / (N_COINS * price_scale)]
          xcp_profit: uint256 = 10**18
          virtual_price: uint256 = 10**18
      
          if old_virtual_price > 0:
              xcp: uint256 = self.geometric_mean(xp, True)
              virtual_price = 10**18 * xcp / total_supply
              xcp_profit = old_xcp_profit * virtual_price / old_virtual_price
      
              t: uint256 = self.future_A_gamma_time
              if virtual_price < old_virtual_price and t == 0:
                  raise "Loss"
              if t == 1:
                  self.future_A_gamma_time = 0
      
          self.xcp_profit = xcp_profit
      
          norm: uint256 = price_oracle * 10**18 / price_scale
          if norm > 10**18:
              norm -= 10**18
          else:
              norm = 10**18 - norm
          adjustment_step: uint256 = max(self.adjustment_step, norm / 5)
      
          needs_adjustment: bool = self.not_adjusted
          # if not needs_adjustment and (virtual_price-10**18 > (xcp_profit-10**18)/2 + self.allowed_extra_profit):
          # (re-arrange for gas efficiency)
          if not needs_adjustment and (virtual_price * 2 - 10**18 > xcp_profit + 2*self.allowed_extra_profit) and (norm > adjustment_step) and (old_virtual_price > 0):
              needs_adjustment = True
              self.not_adjusted = True
      
          if needs_adjustment:
              if norm > adjustment_step and old_virtual_price > 0:
                  p_new = (price_scale * (norm - adjustment_step) + adjustment_step * price_oracle) / norm
      
                  # Calculate balances*prices
                  xp = [_xp[0], _xp[1] * p_new / price_scale]
      
                  # Calculate "extended constant product" invariant xCP and virtual price
                  D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
                  xp = [D / N_COINS, D * PRECISION / (N_COINS * p_new)]
                  # We reuse old_virtual_price here but it's not old anymore
                  old_virtual_price = 10**18 * self.geometric_mean(xp, True) / total_supply
      
                  # Proceed if we've got enough profit
                  # if (old_virtual_price > 10**18) and (2 * (old_virtual_price - 10**18) > xcp_profit - 10**18):
                  if (old_virtual_price > 10**18) and (2 * old_virtual_price - 10**18 > xcp_profit):
                      self.price_scale = p_new
                      self.D = D
                      self.virtual_price = old_virtual_price
      
                      return
      
                  else:
                      self.not_adjusted = False
      
                      # Can instead do another flag variable if we want to save bytespace
                      self.D = D_unadjusted
                      self.virtual_price = virtual_price
                      self._claim_admin_fees()
      
                      return
      
          # If we are here, the price_scale adjustment did not happen
          # Still need to update the profit counter and D
          self.D = D_unadjusted
          self.virtual_price = virtual_price
      
          # norm appeared < adjustment_step after
          if needs_adjustment:
              self.not_adjusted = False
              self._claim_admin_fees()
      
      
      @internal
      def _exchange(sender: address, mvalue: uint256, i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                    use_eth: bool, receiver: address, callbacker: address, callback_sig: bytes32) -> uint256:
          assert i != j  # dev: coin index out of range
          assert i < N_COINS  # dev: coin index out of range
          assert j < N_COINS  # dev: coin index out of range
          assert dx > 0  # dev: do not exchange 0 coins
      
          A_gamma: uint256[2] = self._A_gamma()
          xp: uint256[N_COINS] = self.balances
          p: uint256 = 0
          dy: uint256 = 0
      
          in_coin: address = self.coins[i]
          out_coin: address = self.coins[j]
      
          y: uint256 = xp[j]
          x0: uint256 = xp[i]
          xp[i] = x0 + dx
          self.balances[i] = xp[i]
      
          price_scale: uint256 = self.price_scale
          precisions: uint256[2] = self._get_precisions()
      
          xp = [xp[0] * precisions[0], xp[1] * price_scale * precisions[1] / PRECISION]
      
          prec_i: uint256 = precisions[0]
          prec_j: uint256 = precisions[1]
          if i == 1:
              prec_i = precisions[1]
              prec_j = precisions[0]
      
          # In case ramp is happening
          t: uint256 = self.future_A_gamma_time
          if t > 0:
              x0 *= prec_i
              if i > 0:
                  x0 = x0 * price_scale / PRECISION
              x1: uint256 = xp[i]  # Back up old value in xp
              xp[i] = x0
              self.D = self.newton_D(A_gamma[0], A_gamma[1], xp)
              xp[i] = x1  # And restore
              if block.timestamp >= t:
                  self.future_A_gamma_time = 1
      
          dy = xp[j] - self.newton_y(A_gamma[0], A_gamma[1], xp, self.D, j)
          # Not defining new "y" here to have less variables / make subsequent calls cheaper
          xp[j] -= dy
          dy -= 1
      
          if j > 0:
              dy = dy * PRECISION / price_scale
          dy /= prec_j
      
          dy -= self._fee(xp) * dy / 10**10
          assert dy >= min_dy, "Slippage"
          y -= dy
      
          self.balances[j] = y
      
          # Do transfers in and out together
          # XXX coin vs ETH
          if use_eth and in_coin == WETH20:
              assert mvalue == dx  # dev: incorrect eth amount
          else:
              assert mvalue == 0  # dev: nonzero eth amount
              if callback_sig == EMPTY_BYTES32:
                  response: Bytes[32] = raw_call(
                      in_coin,
                      _abi_encode(
                          sender, self, dx, method_id=method_id("transferFrom(address,address,uint256)")
                      ),
                      max_outsize=32,
                  )
                  if len(response) != 0:
                      assert convert(response, bool)  # dev: failed transfer
              else:
                  b: uint256 = ERC20(in_coin).balanceOf(self)
                  raw_call(
                      callbacker,
                      concat(slice(callback_sig, 0, 4), _abi_encode(sender, receiver, in_coin, dx, dy))
                  )
                  assert ERC20(in_coin).balanceOf(self) - b == dx  # dev: callback didn't give us coins
              if in_coin == WETH20:
                  WETH(WETH20).withdraw(dx)
      
          if use_eth and out_coin == WETH20:
              raw_call(receiver, b"", value=dy)
          else:
              if out_coin == WETH20:
                  WETH(WETH20).deposit(value=dy)
              response: Bytes[32] = raw_call(
                  out_coin,
                  _abi_encode(receiver, dy, method_id=method_id("transfer(address,uint256)")),
                  max_outsize=32,
              )
              if len(response) != 0:
                  assert convert(response, bool)
      
          y *= prec_j
          if j > 0:
              y = y * price_scale / PRECISION
          xp[j] = y
      
          # Calculate price
          if dx > 10**5 and dy > 10**5:
              _dx: uint256 = dx * prec_i
              _dy: uint256 = dy * prec_j
              if i == 0:
                  p = _dx * 10**18 / _dy
              else:  # j == 0
                  p = _dy * 10**18 / _dx
      
          self.tweak_price(A_gamma, xp, p, 0)
      
          log TokenExchange(sender, i, dx, j, dy)
      
          return dy
      
      
      @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 = self._fee(xp) * N_COINS / (4 * (N_COINS-1))
          S: uint256 = 0
          for _x in amounts:
              S += _x
          avg: uint256 = S / N_COINS
          Sdiff: uint256 = 0
          for _x in amounts:
              if _x > avg:
                  Sdiff += _x - avg
              else:
                  Sdiff += 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,
                                  calc_price: bool) -> (uint256, uint256, uint256, uint256[N_COINS]):
          token_supply: uint256 = CurveToken(self.token).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
          D0: uint256 = 0
          precisions: uint256[2] = self._get_precisions()
      
          price_scale_i: uint256 = self.price_scale * precisions[1]
          xp: uint256[N_COINS] = [xx[0] * precisions[0], xx[1] * price_scale_i / PRECISION]
          if i == 0:
              price_scale_i = PRECISION * precisions[0]
      
          if update_D:
              D0 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          else:
              D0 = self.D
      
          D: uint256 = D0
      
          # Charge the fee on D, not on y, e.g. reducing invariant LESS than charging the user
          fee: uint256 = self._fee(xp)
          dD: uint256 = token_amount * D / token_supply
          D -= (dD - (fee * dD / (2 * 10**10) + 1))
          y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, i)
          dy: uint256 = (xp[i] - y) * PRECISION / price_scale_i
          xp[i] = y
      
          # Price calc
          p: uint256 = 0
          if calc_price and dy > 10**5 and token_amount > 10**5:
              # p_i = dD / D0 * sum'(p_k * x_k) / (dy - dD / D0 * y0)
              S: uint256 = 0
              precision: uint256 = precisions[0]
              if i == 1:
                  S = xx[0] * precisions[0]
                  precision = precisions[1]
              else:
                  S = xx[1] * precisions[1]
              S = S * dD / D0
              p = S * PRECISION / (dy * precision - dD * xx[i] * precision / D0)
              if i == 0:
                  p = (10**18)**2 / p
      
          return dy, p, D, xp
      
      
      @internal
      @pure
      def sqrt_int(x: uint256) -> uint256:
          """
          Originating from: https://github.com/vyperlang/vyper/issues/1266
          """
      
          if x == 0:
              return 0
      
          z: uint256 = (x + 10**18) / 2
          y: uint256 = x
      
          for i in range(256):
              if z == y:
                  return y
              y = z
              z = (x * 10**18 / z + z) / 2
      
          raise "Did not converge"
      
      
      # External Functions
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                   use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          """
          Exchange using WETH by default
          """
          return self._exchange(msg.sender, msg.value, i, j, dx, min_dy, use_eth, receiver, ZERO_ADDRESS, EMPTY_BYTES32)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange_underlying(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                              receiver: address = msg.sender) -> uint256:
          """
          Exchange using ETH
          """
          return self._exchange(msg.sender, msg.value, i, j, dx, min_dy, True, receiver, ZERO_ADDRESS, EMPTY_BYTES32)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange_extended(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                            use_eth: bool, sender: address, receiver: address, cb: bytes32) -> uint256:
          assert cb != EMPTY_BYTES32  # dev: No callback specified
          return self._exchange(sender, msg.value, i, j, dx, min_dy, use_eth, receiver, msg.sender, cb)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256,
                        use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          assert amounts[0] > 0 or amounts[1] > 0  # dev: no coins to add
      
          A_gamma: uint256[2] = self._A_gamma()
      
          xp: uint256[N_COINS] = self.balances
          amountsp: uint256[N_COINS] = empty(uint256[N_COINS])
          xx: uint256[N_COINS] = empty(uint256[N_COINS])
          d_token: uint256 = 0
          d_token_fee: uint256 = 0
          old_D: uint256 = 0
      
          xp_old: uint256[N_COINS] = xp
      
          for i in range(N_COINS):
              bal: uint256 = xp[i] + amounts[i]
              xp[i] = bal
              self.balances[i] = bal
          xx = xp
      
          precisions: uint256[2] = self._get_precisions()
      
          price_scale: uint256 = self.price_scale * precisions[1]
          xp = [xp[0] * precisions[0], xp[1] * price_scale / PRECISION]
          xp_old = [xp_old[0] * precisions[0], xp_old[1] * price_scale / PRECISION]
      
          if not use_eth:
              assert msg.value == 0  # dev: nonzero eth amount
      
          for i in range(N_COINS):
              coin: address = self.coins[i]
              if use_eth and coin == WETH20:
                  assert msg.value == amounts[i]  # dev: incorrect eth amount
              if amounts[i] > 0:
                  if (not use_eth) or (coin != WETH20):
                      response: Bytes[32] = raw_call(
                          coin,
                          _abi_encode(
                              msg.sender,
                              self,
                              amounts[i],
                              method_id=method_id("transferFrom(address,address,uint256)"),
                          ),
                          max_outsize=32,
                      )
                      if len(response) != 0:
                          assert convert(response, bool)  # dev: failed transfer
                      if coin == WETH20:
                          WETH(WETH20).withdraw(amounts[i])
                  amountsp[i] = xp[i] - xp_old[i]
      
          t: uint256 = self.future_A_gamma_time
          if t > 0:
              old_D = self.newton_D(A_gamma[0], A_gamma[1], xp_old)
              if block.timestamp >= t:
                  self.future_A_gamma_time = 1
          else:
              old_D = self.D
      
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
      
          lp_token: address = self.token
          token_supply: uint256 = CurveToken(lp_token).totalSupply()
          if old_D > 0:
              d_token = token_supply * D / old_D - token_supply
          else:
              d_token = self.get_xcp(D)  # 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
              CurveToken(lp_token).mint(receiver, d_token)
      
              # Calculate price
              # p_i * (dx_i - dtoken / token_supply * xx_i) = sum{k!=i}(p_k * (dtoken / token_supply * xx_k - dx_k))
              # Simplified for 2 coins
              p: uint256 = 0
              if d_token > 10**5:
                  if amounts[0] == 0 or amounts[1] == 0:
                      S: uint256 = 0
                      precision: uint256 = 0
                      ix: uint256 = 0
                      if amounts[0] == 0:
                          S = xx[0] * precisions[0]
                          precision = precisions[1]
                          ix = 1
                      else:
                          S = xx[1] * precisions[1]
                          precision = precisions[0]
                      S = S * d_token / token_supply
                      p = S * PRECISION / (amounts[ix] * precision - d_token * xx[ix] * precision / token_supply)
                      if ix == 0:
                          p = (10**18)**2 / p
      
              self.tweak_price(A_gamma, xp, p, D)
      
          else:
              self.D = D
              self.virtual_price = 10**18
              self.xcp_profit = 10**18
              CurveToken(lp_token).mint(receiver, d_token)
      
          assert d_token >= min_mint_amount, "Slippage"
      
          log AddLiquidity(receiver, amounts, d_token_fee, token_supply)
      
          return d_token
      
      
      @external
      @nonreentrant('lock')
      def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS],
                           use_eth: bool = False, receiver: address = msg.sender):
          """
          This withdrawal method is very safe, does no complex math
          """
          lp_token: address = self.token
          total_supply: uint256 = CurveToken(lp_token).totalSupply()
          CurveToken(lp_token).burnFrom(msg.sender, _amount)
          balances: uint256[N_COINS] = self.balances
          amount: uint256 = _amount - 1  # Make rounding errors favoring other LPs a tiny bit
      
          for i in range(N_COINS):
              d_balance: uint256 = balances[i] * amount / total_supply
              assert d_balance >= min_amounts[i]
              self.balances[i] = balances[i] - d_balance
              balances[i] = d_balance  # now it's the amounts going out
              coin: address = self.coins[i]
              if use_eth and coin == WETH20:
                  raw_call(receiver, b"", value=d_balance)
              else:
                  if coin == WETH20:
                      WETH(WETH20).deposit(value=d_balance)
                  response: Bytes[32] = raw_call(
                      coin,
                      _abi_encode(receiver, d_balance, method_id=method_id("transfer(address,uint256)")),
                      max_outsize=32,
                  )
                  if len(response) != 0:
                      assert convert(response, bool)
      
          D: uint256 = self.D
          self.D = D - D * amount / total_supply
      
          log RemoveLiquidity(msg.sender, balances, total_supply - _amount)
      
      
      @external
      @nonreentrant('lock')
      def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256,
                                    use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          A_gamma: uint256[2] = self._A_gamma()
      
          dy: uint256 = 0
          D: uint256 = 0
          p: uint256 = 0
          xp: uint256[N_COINS] = empty(uint256[N_COINS])
          future_A_gamma_time: uint256 = self.future_A_gamma_time
          dy, p, D, xp = self._calc_withdraw_one_coin(A_gamma, token_amount, i, (future_A_gamma_time > 0), True)
          assert dy >= min_amount, "Slippage"
      
          if block.timestamp >= future_A_gamma_time:
              self.future_A_gamma_time = 1
      
          self.balances[i] -= dy
          CurveToken(self.token).burnFrom(msg.sender, token_amount)
      
          coin: address = self.coins[i]
          if use_eth and coin == WETH20:
              raw_call(receiver, b"", value=dy)
          else:
              if coin == WETH20:
                  WETH(WETH20).deposit(value=dy)
              response: Bytes[32] = raw_call(
                  coin,
                  _abi_encode(receiver, dy, method_id=method_id("transfer(address,uint256)")),
                  max_outsize=32,
              )
              if len(response) != 0:
                  assert convert(response, bool)
      
          self.tweak_price(A_gamma, xp, p, D)
      
          log RemoveLiquidityOne(msg.sender, token_amount, i, dy)
      
          return dy
      
      
      @external
      @nonreentrant('lock')
      def claim_admin_fees():
          self._claim_admin_fees()
      
      
      # Admin parameters
      @external
      def ramp_A_gamma(future_A: uint256, future_gamma: uint256, future_time: uint256):
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert block.timestamp > self.initial_A_gamma_time + (MIN_RAMP_TIME-1)
          assert future_time > block.timestamp + (MIN_RAMP_TIME-1)  # dev: insufficient time
      
          A_gamma: uint256[2] = self._A_gamma()
          initial_A_gamma: uint256 = shift(A_gamma[0], 128)
          initial_A_gamma = bitwise_or(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 = shift(future_A, 128)
          future_A_gamma = bitwise_or(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():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
      
          A_gamma: uint256[2] = self._A_gamma()
          current_A_gamma: uint256 = shift(A_gamma[0], 128)
          current_A_gamma = bitwise_or(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
      def commit_new_parameters(
          _new_mid_fee: uint256,
          _new_out_fee: uint256,
          _new_admin_fee: uint256,
          _new_fee_gamma: uint256,
          _new_allowed_extra_profit: uint256,
          _new_adjustment_step: uint256,
          _new_ma_half_time: uint256,
          ):
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert self.admin_actions_deadline == 0  # dev: active action
      
          new_mid_fee: uint256 = _new_mid_fee
          new_out_fee: uint256 = _new_out_fee
          new_admin_fee: uint256 = _new_admin_fee
          new_fee_gamma: uint256 = _new_fee_gamma
          new_allowed_extra_profit: uint256 = _new_allowed_extra_profit
          new_adjustment_step: uint256 = _new_adjustment_step
          new_ma_half_time: uint256 = _new_ma_half_time
      
          # Fees
          if new_out_fee < MAX_FEE+1:
              assert new_out_fee > MIN_FEE-1  # dev: fee is out of range
          else:
              new_out_fee = self.out_fee
          if new_mid_fee > MAX_FEE:
              new_mid_fee = self.mid_fee
          assert new_mid_fee <= new_out_fee  # dev: mid-fee is too high
          if new_admin_fee > MAX_ADMIN_FEE:
              new_admin_fee = self.admin_fee
      
          # AMM parameters
          if new_fee_gamma < 10**18:
              assert new_fee_gamma > 0  # dev: fee_gamma out of range [1 .. 10**18]
          else:
              new_fee_gamma = self.fee_gamma
          if new_allowed_extra_profit > 10**18:
              new_allowed_extra_profit = self.allowed_extra_profit
          if new_adjustment_step > 10**18:
              new_adjustment_step = self.adjustment_step
      
          # MA
          if new_ma_half_time < 7*86400:
              assert new_ma_half_time > 0  # dev: MA time should be longer than 1 second
          else:
              new_ma_half_time = self.ma_half_time
      
          _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
          self.admin_actions_deadline = _deadline
      
          self.future_admin_fee = new_admin_fee
          self.future_mid_fee = new_mid_fee
          self.future_out_fee = new_out_fee
          self.future_fee_gamma = new_fee_gamma
          self.future_allowed_extra_profit = new_allowed_extra_profit
          self.future_adjustment_step = new_adjustment_step
          self.future_ma_half_time = new_ma_half_time
      
          log CommitNewParameters(_deadline, new_admin_fee, new_mid_fee, new_out_fee,
                                  new_fee_gamma,
                                  new_allowed_extra_profit, new_adjustment_step,
                                  new_ma_half_time)
      
      
      @external
      @nonreentrant('lock')
      def apply_new_parameters():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert block.timestamp >= self.admin_actions_deadline  # dev: insufficient time
          assert self.admin_actions_deadline != 0  # dev: no active action
      
          self.admin_actions_deadline = 0
      
          admin_fee: uint256 = self.future_admin_fee
          if self.admin_fee != admin_fee:
              self._claim_admin_fees()
              self.admin_fee = admin_fee
      
          mid_fee: uint256 = self.future_mid_fee
          self.mid_fee = mid_fee
          out_fee: uint256 = self.future_out_fee
          self.out_fee = out_fee
          fee_gamma: uint256 = self.future_fee_gamma
          self.fee_gamma = fee_gamma
          allowed_extra_profit: uint256 = self.future_allowed_extra_profit
          self.allowed_extra_profit = allowed_extra_profit
          adjustment_step: uint256 = self.future_adjustment_step
          self.adjustment_step = adjustment_step
          ma_half_time: uint256 = self.future_ma_half_time
          self.ma_half_time = ma_half_time
      
          log NewParameters(admin_fee, mid_fee, out_fee,
                            fee_gamma,
                            allowed_extra_profit, adjustment_step,
                            ma_half_time)
      
      
      @external
      def revert_new_parameters():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
      
          self.admin_actions_deadline = 0
      
      
      # View Methods
      
      
      @external
      @view
      def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256:
          assert i != j  # dev: same input and output coin
          assert i < N_COINS  # dev: coin index out of range
          assert j < N_COINS  # dev: coin index out of range
      
          precisions: uint256[2] = self._get_precisions()
      
          price_scale: uint256 = self.price_scale * precisions[1]
          xp: uint256[N_COINS] = self.balances
      
          A_gamma: uint256[2] = self._A_gamma()
          D: uint256 = self.D
          if self.future_A_gamma_time > 0:
              D = self.newton_D(A_gamma[0], A_gamma[1], self.xp())
      
          xp[i] += dx
          xp = [xp[0] * precisions[0], xp[1] * price_scale / PRECISION]
      
          y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, j)
          dy: uint256 = xp[j] - y - 1
          xp[j] = y
          if j > 0:
              dy = dy * PRECISION / price_scale
          else:
              dy /= precisions[0]
          dy -= self._fee(xp) * dy / 10**10
      
          return dy
      
      
      @view
      @external
      def calc_token_amount(amounts: uint256[N_COINS]) -> uint256:
          token_supply: uint256 = CurveToken(self.token).totalSupply()
          precisions: uint256[2] = self._get_precisions()
          price_scale: uint256 = self.price_scale * precisions[1]
          A_gamma: uint256[2] = self._A_gamma()
          xp: uint256[N_COINS] = self.xp()
          amountsp: uint256[N_COINS] = [
              amounts[0] * precisions[0],
              amounts[1] * price_scale / PRECISION]
          D0: uint256 = self.D
          if self.future_A_gamma_time > 0:
              D0 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          xp[0] += amountsp[0]
          xp[1] += amountsp[1]
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          d_token: uint256 = token_supply * D / D0 - token_supply
          d_token -= self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1
          return d_token
      
      
      @view
      @external
      def calc_withdraw_one_coin(token_amount: uint256, i: uint256) -> uint256:
          return self._calc_withdraw_one_coin(self._A_gamma(), token_amount, i, True, False)[0]
      
      
      @external
      @view
      def lp_price() -> uint256:
          """
          Approximate LP token price
          """
          return 2 * self.virtual_price * self.sqrt_int(self.internal_price_oracle()) / 10**18
      
      
      @view
      @external
      def A() -> uint256:
          return self._A_gamma()[0]
      
      
      @view
      @external
      def gamma() -> uint256:
          return self._A_gamma()[1]
      
      
      @external
      @view
      def fee() -> uint256:
          return self._fee(self.xp())
      
      
      @external
      @view
      def get_virtual_price() -> uint256:
          return 10**18 * self.get_xcp(self.D) / CurveToken(self.token).totalSupply()
      
      
      @external
      @view
      def price_oracle() -> uint256:
          return self.internal_price_oracle()
      
      
      # Initializer
      
      
      @external
      def initialize(
          A: uint256,
          gamma: uint256,
          mid_fee: uint256,
          out_fee: uint256,
          allowed_extra_profit: uint256,
          fee_gamma: uint256,
          adjustment_step: uint256,
          admin_fee: uint256,
          ma_half_time: uint256,
          initial_price: uint256,
          _token: address,
          _coins: address[N_COINS],
          _precisions: uint256,
      ):
          assert self.mid_fee == 0  # dev: check that we call it from factory
      
          self.factory = msg.sender
      
          # Pack A and gamma:
          # shifted A + gamma
          A_gamma: uint256 = shift(A, 128)
          A_gamma = bitwise_or(A_gamma, gamma)
          self.initial_A_gamma = A_gamma
          self.future_A_gamma = A_gamma
      
          self.mid_fee = mid_fee
          self.out_fee = out_fee
          self.allowed_extra_profit = allowed_extra_profit
          self.fee_gamma = fee_gamma
          self.adjustment_step = adjustment_step
          self.admin_fee = admin_fee
      
          self.price_scale = initial_price
          self._price_oracle = initial_price
          self.last_prices = initial_price
          self.last_prices_timestamp = block.timestamp
          self.ma_half_time = ma_half_time
      
          self.xcp_profit_a = 10**18
      
          self.token = _token
          self.coins = _coins
          self.PRECISIONS = _precisions

      File 2 of 3: Vyper_contract
      # @version 0.3.1
      # (c) Curve.Fi, 2021
      # Pool for two crypto assets
      
      # Universal implementation which can use both ETH and ERC20s
      from vyper.interfaces import ERC20
      
      
      interface Factory:
          def admin() -> address: view
          def fee_receiver() -> address: view
      
      interface CurveToken:
          def totalSupply() -> uint256: view
          def mint(_to: address, _value: uint256) -> bool: nonpayable
          def mint_relative(_to: address, frac: uint256) -> uint256: nonpayable
          def burnFrom(_to: address, _value: uint256) -> bool: nonpayable
      
      interface WETH:
          def deposit(): payable
          def withdraw(_amount: uint256): nonpayable
      
      
      # Events
      event TokenExchange:
          buyer: indexed(address)
          sold_id: uint256
          tokens_sold: uint256
          bought_id: uint256
          tokens_bought: uint256
      
      event AddLiquidity:
          provider: indexed(address)
          token_amounts: uint256[N_COINS]
          fee: uint256
          token_supply: 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
      
      event CommitNewParameters:
          deadline: indexed(uint256)
          admin_fee: uint256
          mid_fee: uint256
          out_fee: uint256
          fee_gamma: uint256
          allowed_extra_profit: uint256
          adjustment_step: uint256
          ma_half_time: uint256
      
      event NewParameters:
          admin_fee: uint256
          mid_fee: uint256
          out_fee: uint256
          fee_gamma: uint256
          allowed_extra_profit: uint256
          adjustment_step: uint256
          ma_half_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
      
      
      ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400
      MIN_RAMP_TIME: constant(uint256) = 86400
      
      MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9
      MIN_FEE: constant(uint256) = 5 * 10 ** 5  # 0.5 bps
      MAX_FEE: constant(uint256) = 10 * 10 ** 9
      MAX_A_CHANGE: constant(uint256) = 10
      NOISE_FEE: constant(uint256) = 10**5  # 0.1 bps
      
      MIN_GAMMA: constant(uint256) = 10**10
      MAX_GAMMA: constant(uint256) = 2 * 10**16
      
      MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 10
      MAX_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER * 100000
      
      EXP_PRECISION: constant(uint256) = 10**10
      
      N_COINS: constant(int128) = 2
      PRECISION: constant(uint256) = 10 ** 18  # The precision to convert to
      A_MULTIPLIER: constant(uint256) = 10000
      
      
      # Implementation can be changed by changing this constant
      WETH20: immutable(address)
      
      
      token: public(address)
      coins: public(address[N_COINS])
      
      price_scale: public(uint256)   # Internal price scale
      _price_oracle: uint256  # Price target given by MA
      
      last_prices: public(uint256)
      last_prices_timestamp: public(uint256)
      
      initial_A_gamma: public(uint256)
      future_A_gamma: public(uint256)
      initial_A_gamma_time: public(uint256)
      future_A_gamma_time: public(uint256)
      
      allowed_extra_profit: public(uint256)  # 2 * 10**12 - recommended value
      future_allowed_extra_profit: public(uint256)
      
      fee_gamma: public(uint256)
      future_fee_gamma: public(uint256)
      
      adjustment_step: public(uint256)
      future_adjustment_step: public(uint256)
      
      ma_half_time: public(uint256)
      future_ma_half_time: public(uint256)
      
      mid_fee: public(uint256)
      out_fee: public(uint256)
      admin_fee: public(uint256)
      future_mid_fee: public(uint256)
      future_out_fee: public(uint256)
      future_admin_fee: public(uint256)
      
      balances: public(uint256[N_COINS])
      D: public(uint256)
      
      factory: public(address)
      
      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 also used internally
      not_adjusted: bool
      
      admin_actions_deadline: public(uint256)
      
      # This must be changed for different N_COINS
      # For example:
      # N_COINS = 3 -> 1  (10**18 -> 10**18)
      # N_COINS = 4 -> 10**8  (10**18 -> 10**10)
      # PRICE_PRECISION_MUL: constant(uint256) = 1
      PRECISIONS: uint256  # packed
      
      
      @external
      def __init__(_weth: address):
          WETH20 = _weth
          self.mid_fee = 22022022
      
      
      @payable
      @external
      def __default__():
          pass
      
      
      # Internal Functions
      
      @internal
      @view
      def _get_precisions() -> uint256[2]:
          p0: uint256 = self.PRECISIONS
          p1: uint256 = 10 ** shift(p0, -8)
          p0 = 10 ** bitwise_and(p0, 255)
          return [p0, p1]
      
      
      @internal
      @view
      def xp() -> uint256[N_COINS]:
          precisions: uint256[2] = self._get_precisions()
          return [self.balances[0] * precisions[0],
                  self.balances[1] * precisions[1] * self.price_scale / PRECISION]
      
      
      @view
      @internal
      def _A_gamma() -> uint256[2]:
          t1: uint256 = self.future_A_gamma_time
      
          A_gamma_1: uint256 = self.future_A_gamma
          gamma1: uint256 = bitwise_and(A_gamma_1, 2**128-1)
          A1: uint256 = shift(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
      
              # Less readable but more compact way of writing and converting to uint256
              # gamma0: uint256 = bitwise_and(A_gamma_0, 2**128-1)
              # A0: uint256 = shift(A_gamma_0, -128)
              # A1 = A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0)
              # gamma1 = gamma0 + (gamma1 - gamma0) * (block.timestamp - t0) / (t1 - t0)
      
              t1 -= t0
              t0 = block.timestamp - t0
              t2: uint256 = t1 - t0
      
              A1 = (shift(A_gamma_0, -128) * t2 + A1 * t0) / t1
              gamma1 = (bitwise_and(A_gamma_0, 2**128-1) * t2 + gamma1 * t0) / t1
      
          return [A1, gamma1]
      
      
      @internal
      @view
      def _fee(xp: uint256[N_COINS]) -> uint256:
          """
          f = fee_gamma / (fee_gamma + (1 - K))
          where
          K = prod(x) / (sum(x) / N)**N
          (all normalized to 1e18)
          """
          fee_gamma: uint256 = self.fee_gamma
          f: uint256 = xp[0] + xp[1]  # sum
          f = fee_gamma * 10**18 / (
              fee_gamma + 10**18 - (10**18 * N_COINS**N_COINS) * xp[0] / f * xp[1] / f
          )
          return (self.mid_fee * f + self.out_fee * (10**18 - f)) / 10**18
      
      
      ### Math functions
      @internal
      @pure
      def geometric_mean(unsorted_x: uint256[N_COINS], sort: bool) -> uint256:
          """
          (x[0] * x[1] * ...) ** (1/N)
          """
          x: uint256[N_COINS] = unsorted_x
          if sort and x[0] < x[1]:
              x = [unsorted_x[1], unsorted_x[0]]
          D: uint256 = x[0]
          diff: uint256 = 0
          for i in range(255):
              D_prev: uint256 = D
              # tmp: uint256 = 10**18
              # for _x in x:
              #     tmp = tmp * _x / D
              # D = D * ((N_COINS - 1) * 10**18 + tmp) / (N_COINS * 10**18)
              # line below makes it for 2 coins
              D = (D + x[0] * x[1] / D) / N_COINS
              if D > D_prev:
                  diff = D - D_prev
              else:
                  diff = D_prev - D
              if diff <= 1 or diff * 10**18 < D:
                  return D
          raise "Did not converge"
      
      
      @internal
      @view
      def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS]) -> uint256:
          """
          Finding the invariant using Newton method.
          ANN is higher by the factor A_MULTIPLIER
          ANN is already A * N**N
      
          Currently uses 60k gas
          """
          # Safety checks
          assert ANN > MIN_A - 1 and ANN < MAX_A + 1  # dev: unsafe values A
          assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1  # dev: unsafe values gamma
      
          # Initial value of invariant D is that for constant-product invariant
          x: uint256[N_COINS] = x_unsorted
          if x[0] < x[1]:
              x = [x_unsorted[1], x_unsorted[0]]
      
          assert x[0] > 10**9 - 1 and x[0] < 10**15 * 10**18 + 1  # dev: unsafe values x[0]
          assert x[1] * 10**18 / x[0] > 10**14-1  # dev: unsafe values x[i] (input)
      
          D: uint256 = N_COINS * self.geometric_mean(x, False)
          S: uint256 = x[0] + x[1]
      
          for i in range(255):
              D_prev: uint256 = D
      
              # K0: uint256 = 10**18
              # for _x in x:
              #     K0 = K0 * _x * N_COINS / D
              # collapsed for 2 coins
              K0: uint256 = (10**18 * N_COINS**2) * x[0] / D * x[1] / D
      
              _g1k0: uint256 = gamma + 10**18
              if _g1k0 > K0:
                  _g1k0 = _g1k0 - K0 + 1
              else:
                  _g1k0 = K0 - _g1k0 + 1
      
              # D / (A * N**N) * _g1k0**2 / gamma**2
              mul1: uint256 = 10**18 * D / gamma * _g1k0 / gamma * _g1k0 * A_MULTIPLIER / ANN
      
              # 2*N*K0 / _g1k0
              mul2: uint256 = (2 * 10**18) * N_COINS * K0 / _g1k0
      
              neg_fprime: uint256 = (S + S * mul2 / 10**18) + mul1 * N_COINS / K0 - mul2 * D / 10**18
      
              # D -= f / fprime
              D_plus: uint256 = D * (neg_fprime + S) / neg_fprime
              D_minus: uint256 = D*D / neg_fprime
              if 10**18 > K0:
                  D_minus += D * (mul1 / neg_fprime) / 10**18 * (10**18 - K0) / K0
              else:
                  D_minus -= D * (mul1 / neg_fprime) / 10**18 * (K0 - 10**18) / K0
      
              if D_plus > D_minus:
                  D = D_plus - D_minus
              else:
                  D = (D_minus - D_plus) / 2
      
              diff: uint256 = 0
              if D > D_prev:
                  diff = D - D_prev
              else:
                  diff = D_prev - D
              if diff * 10**14 < max(10**16, D):  # Could reduce precision for gas efficiency here
                  # Test that we are safe with the next newton_y
                  for _x in x:
                      frac: uint256 = _x * 10**18 / D
                      assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  # dev: unsafe values x[i]
                  return D
      
          raise "Did not converge"
      
      
      @internal
      @pure
      def newton_y(ANN: uint256, gamma: uint256, x: uint256[N_COINS], D: uint256, i: uint256) -> uint256:
          """
          Calculating x[i] given other balances x[0..N_COINS-1] and invariant D
          ANN = A * N**N
          """
          # Safety checks
          assert ANN > MIN_A - 1 and ANN < MAX_A + 1  # dev: unsafe values A
          assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1  # dev: unsafe values gamma
          assert D > 10**17 - 1 and D < 10**15 * 10**18 + 1 # dev: unsafe values D
      
          x_j: uint256 = x[1 - i]
          y: uint256 = D**2 / (x_j * N_COINS**2)
          K0_i: uint256 = (10**18 * N_COINS) * x_j / D
          # S_i = x_j
      
          # frac = x_j * 1e18 / D => frac = K0_i / N_COINS
          assert (K0_i > 10**16*N_COINS - 1) and (K0_i < 10**20*N_COINS + 1)  # dev: unsafe values x[i]
      
          # x_sorted: uint256[N_COINS] = x
          # x_sorted[i] = 0
          # x_sorted = self.sort(x_sorted)  # From high to low
          # x[not i] instead of x_sorted since x_soted has only 1 element
      
          convergence_limit: uint256 = max(max(x_j / 10**14, D / 10**14), 100)
      
          for j in range(255):
              y_prev: uint256 = y
      
              K0: uint256 = K0_i * y * N_COINS / D
              S: uint256 = x_j + y
      
              _g1k0: uint256 = gamma + 10**18
              if _g1k0 > K0:
                  _g1k0 = _g1k0 - K0 + 1
              else:
                  _g1k0 = K0 - _g1k0 + 1
      
              # D / (A * N**N) * _g1k0**2 / gamma**2
              mul1: uint256 = 10**18 * D / gamma * _g1k0 / gamma * _g1k0 * A_MULTIPLIER / ANN
      
              # 2*K0 / _g1k0
              mul2: uint256 = 10**18 + (2 * 10**18) * K0 / _g1k0
      
              yfprime: uint256 = 10**18 * y + S * mul2 + mul1
              _dyfprime: uint256 = D * mul2
              if yfprime < _dyfprime:
                  y = y_prev / 2
                  continue
              else:
                  yfprime -= _dyfprime
              fprime: uint256 = yfprime / y
      
              # y -= f / f_prime;  y = (y * fprime - f) / fprime
              # y = (yfprime + 10**18 * D - 10**18 * S) // fprime + mul1 // fprime * (10**18 - K0) // K0
              y_minus: uint256 = mul1 / fprime
              y_plus: uint256 = (yfprime + 10**18 * D) / fprime + y_minus * 10**18 / K0
              y_minus += 10**18 * S / fprime
      
              if y_plus < y_minus:
                  y = y_prev / 2
              else:
                  y = y_plus - y_minus
      
              diff: uint256 = 0
              if y > y_prev:
                  diff = y - y_prev
              else:
                  diff = y_prev - y
              if diff < max(convergence_limit, y / 10**14):
                  frac: uint256 = y * 10**18 / D
                  assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  # dev: unsafe value for y
                  return y
      
          raise "Did not converge"
      
      
      @internal
      @pure
      def halfpow(power: uint256) -> uint256:
          """
          1e18 * 0.5 ** (power/1e18)
      
          Inspired by: https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L128
          """
          intpow: uint256 = power / 10**18
          otherpow: uint256 = power - intpow * 10**18
          if intpow > 59:
              return 0
          result: uint256 = 10**18 / (2**intpow)
          if otherpow == 0:
              return result
      
          term: uint256 = 10**18
          x: uint256 = 5 * 10**17
          S: uint256 = 10**18
          neg: bool = False
      
          for i in range(1, 256):
              K: uint256 = i * 10**18
              c: uint256 = K - 10**18
              if otherpow > c:
                  c = otherpow - c
                  neg = not neg
              else:
                  c -= otherpow
              term = term * (c * x / 10**18) / K
              if neg:
                  S -= term
              else:
                  S += term
              if term < EXP_PRECISION:
                  return result * S / 10**18
      
          raise "Did not converge"
      ### end of Math functions
      
      
      @internal
      @view
      def get_xcp(D: uint256) -> uint256:
          x: uint256[N_COINS] = [D / N_COINS, D * PRECISION / (self.price_scale * N_COINS)]
          return self.geometric_mean(x, True)
      
      
      @internal
      def _claim_admin_fees():
          A_gamma: uint256[2] = self._A_gamma()
      
          xcp_profit: uint256 = self.xcp_profit
          xcp_profit_a: uint256 = self.xcp_profit_a
      
          # Gulp here
          for i in range(N_COINS):
              coin: address = self.coins[i]
              if coin == WETH20:
                  self.balances[i] = self.balance
              else:
                  self.balances[i] = ERC20(coin).balanceOf(self)
      
          vprice: uint256 = self.virtual_price
      
          if xcp_profit > xcp_profit_a:
              fees: uint256 = (xcp_profit - xcp_profit_a) * self.admin_fee / (2 * 10**10)
              if fees > 0:
                  receiver: address = Factory(self.factory).fee_receiver()
                  if receiver != ZERO_ADDRESS:
                      frac: uint256 = vprice * 10**18 / (vprice - fees) - 10**18
                      claimed: uint256 = CurveToken(self.token).mint_relative(receiver, frac)
                      xcp_profit -= fees*2
                      self.xcp_profit = xcp_profit
                      log ClaimAdminFee(receiver, claimed)
      
          total_supply: uint256 = CurveToken(self.token).totalSupply()
      
          # Recalculate D b/c we gulped
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], self.xp())
          self.D = D
      
          self.virtual_price = 10**18 * self.get_xcp(D) / total_supply
      
          if xcp_profit > xcp_profit_a:
              self.xcp_profit_a = xcp_profit
      
      
      @internal
      @view
      def internal_price_oracle() -> uint256:
          price_oracle: uint256 = self._price_oracle
          last_prices_timestamp: uint256 = self.last_prices_timestamp
      
          if last_prices_timestamp < block.timestamp:
              ma_half_time: uint256 = self.ma_half_time
              last_prices: uint256 = self.last_prices
              alpha: uint256 = self.halfpow((block.timestamp - last_prices_timestamp) * 10**18 / ma_half_time)
              return (last_prices * (10**18 - alpha) + price_oracle * alpha) / 10**18
      
          else:
              return price_oracle
      
      
      @internal
      def tweak_price(A_gamma: uint256[2],_xp: uint256[N_COINS], p_i: uint256, new_D: uint256):
          price_oracle: uint256 = self._price_oracle
          last_prices: uint256 = self.last_prices
          price_scale: uint256 = self.price_scale
          last_prices_timestamp: uint256 = self.last_prices_timestamp
          p_new: uint256 = 0
      
          if last_prices_timestamp < block.timestamp:
              # MA update required
              ma_half_time: uint256 = self.ma_half_time
              alpha: uint256 = self.halfpow((block.timestamp - last_prices_timestamp) * 10**18 / ma_half_time)
              price_oracle = (last_prices * (10**18 - alpha) + price_oracle * alpha) / 10**18
              self._price_oracle = price_oracle
              self.last_prices_timestamp = block.timestamp
      
          D_unadjusted: uint256 = new_D  # Withdrawal methods know new D already
          if new_D == 0:
              # We will need this a few times (35k gas)
              D_unadjusted = self.newton_D(A_gamma[0], A_gamma[1], _xp)
      
          if p_i > 0:
              last_prices = p_i
      
          else:
              # calculate real prices
              __xp: uint256[N_COINS] = _xp
              dx_price: uint256 = __xp[0] / 10**6
              __xp[0] += dx_price
              last_prices = price_scale * dx_price / (_xp[1] - self.newton_y(A_gamma[0], A_gamma[1], __xp, D_unadjusted, 1))
      
          self.last_prices = last_prices
      
          total_supply: uint256 = CurveToken(self.token).totalSupply()
          old_xcp_profit: uint256 = self.xcp_profit
          old_virtual_price: uint256 = self.virtual_price
      
          # Update profit numbers without price adjustment first
          xp: uint256[N_COINS] = [D_unadjusted / N_COINS, D_unadjusted * PRECISION / (N_COINS * price_scale)]
          xcp_profit: uint256 = 10**18
          virtual_price: uint256 = 10**18
      
          if old_virtual_price > 0:
              xcp: uint256 = self.geometric_mean(xp, True)
              virtual_price = 10**18 * xcp / total_supply
              xcp_profit = old_xcp_profit * virtual_price / old_virtual_price
      
              t: uint256 = self.future_A_gamma_time
              if virtual_price < old_virtual_price and t == 0:
                  raise "Loss"
              if t == 1:
                  self.future_A_gamma_time = 0
      
          self.xcp_profit = xcp_profit
      
          norm: uint256 = price_oracle * 10**18 / price_scale
          if norm > 10**18:
              norm -= 10**18
          else:
              norm = 10**18 - norm
          adjustment_step: uint256 = max(self.adjustment_step, norm / 5)
      
          needs_adjustment: bool = self.not_adjusted
          # if not needs_adjustment and (virtual_price-10**18 > (xcp_profit-10**18)/2 + self.allowed_extra_profit):
          # (re-arrange for gas efficiency)
          if not needs_adjustment and (virtual_price * 2 - 10**18 > xcp_profit + 2*self.allowed_extra_profit) and (norm > adjustment_step) and (old_virtual_price > 0):
              needs_adjustment = True
              self.not_adjusted = True
      
          if needs_adjustment:
              if norm > adjustment_step and old_virtual_price > 0:
                  p_new = (price_scale * (norm - adjustment_step) + adjustment_step * price_oracle) / norm
      
                  # Calculate balances*prices
                  xp = [_xp[0], _xp[1] * p_new / price_scale]
      
                  # Calculate "extended constant product" invariant xCP and virtual price
                  D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
                  xp = [D / N_COINS, D * PRECISION / (N_COINS * p_new)]
                  # We reuse old_virtual_price here but it's not old anymore
                  old_virtual_price = 10**18 * self.geometric_mean(xp, True) / total_supply
      
                  # Proceed if we've got enough profit
                  # if (old_virtual_price > 10**18) and (2 * (old_virtual_price - 10**18) > xcp_profit - 10**18):
                  if (old_virtual_price > 10**18) and (2 * old_virtual_price - 10**18 > xcp_profit):
                      self.price_scale = p_new
                      self.D = D
                      self.virtual_price = old_virtual_price
      
                      return
      
                  else:
                      self.not_adjusted = False
      
                      # Can instead do another flag variable if we want to save bytespace
                      self.D = D_unadjusted
                      self.virtual_price = virtual_price
                      self._claim_admin_fees()
      
                      return
      
          # If we are here, the price_scale adjustment did not happen
          # Still need to update the profit counter and D
          self.D = D_unadjusted
          self.virtual_price = virtual_price
      
          # norm appeared < adjustment_step after
          if needs_adjustment:
              self.not_adjusted = False
              self._claim_admin_fees()
      
      
      @internal
      def _exchange(sender: address, mvalue: uint256, i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                    use_eth: bool, receiver: address, callbacker: address, callback_sig: bytes32) -> uint256:
          assert i != j  # dev: coin index out of range
          assert i < N_COINS  # dev: coin index out of range
          assert j < N_COINS  # dev: coin index out of range
          assert dx > 0  # dev: do not exchange 0 coins
      
          A_gamma: uint256[2] = self._A_gamma()
          xp: uint256[N_COINS] = self.balances
          p: uint256 = 0
          dy: uint256 = 0
      
          in_coin: address = self.coins[i]
          out_coin: address = self.coins[j]
      
          y: uint256 = xp[j]
          x0: uint256 = xp[i]
          xp[i] = x0 + dx
          self.balances[i] = xp[i]
      
          price_scale: uint256 = self.price_scale
          precisions: uint256[2] = self._get_precisions()
      
          xp = [xp[0] * precisions[0], xp[1] * price_scale * precisions[1] / PRECISION]
      
          prec_i: uint256 = precisions[0]
          prec_j: uint256 = precisions[1]
          if i == 1:
              prec_i = precisions[1]
              prec_j = precisions[0]
      
          # In case ramp is happening
          t: uint256 = self.future_A_gamma_time
          if t > 0:
              x0 *= prec_i
              if i > 0:
                  x0 = x0 * price_scale / PRECISION
              x1: uint256 = xp[i]  # Back up old value in xp
              xp[i] = x0
              self.D = self.newton_D(A_gamma[0], A_gamma[1], xp)
              xp[i] = x1  # And restore
              if block.timestamp >= t:
                  self.future_A_gamma_time = 1
      
          dy = xp[j] - self.newton_y(A_gamma[0], A_gamma[1], xp, self.D, j)
          # Not defining new "y" here to have less variables / make subsequent calls cheaper
          xp[j] -= dy
          dy -= 1
      
          if j > 0:
              dy = dy * PRECISION / price_scale
          dy /= prec_j
      
          dy -= self._fee(xp) * dy / 10**10
          assert dy >= min_dy, "Slippage"
          y -= dy
      
          self.balances[j] = y
      
          # Do transfers in and out together
          # XXX coin vs ETH
          if use_eth and in_coin == WETH20:
              assert mvalue == dx  # dev: incorrect eth amount
          else:
              assert mvalue == 0  # dev: nonzero eth amount
              if callback_sig == EMPTY_BYTES32:
                  response: Bytes[32] = raw_call(
                      in_coin,
                      _abi_encode(
                          sender, self, dx, method_id=method_id("transferFrom(address,address,uint256)")
                      ),
                      max_outsize=32,
                  )
                  if len(response) != 0:
                      assert convert(response, bool)  # dev: failed transfer
              else:
                  b: uint256 = ERC20(in_coin).balanceOf(self)
                  raw_call(
                      callbacker,
                      concat(slice(callback_sig, 0, 4), _abi_encode(sender, receiver, in_coin, dx, dy))
                  )
                  assert ERC20(in_coin).balanceOf(self) - b == dx  # dev: callback didn't give us coins
              if in_coin == WETH20:
                  WETH(WETH20).withdraw(dx)
      
          if use_eth and out_coin == WETH20:
              raw_call(receiver, b"", value=dy)
          else:
              if out_coin == WETH20:
                  WETH(WETH20).deposit(value=dy)
              response: Bytes[32] = raw_call(
                  out_coin,
                  _abi_encode(receiver, dy, method_id=method_id("transfer(address,uint256)")),
                  max_outsize=32,
              )
              if len(response) != 0:
                  assert convert(response, bool)
      
          y *= prec_j
          if j > 0:
              y = y * price_scale / PRECISION
          xp[j] = y
      
          # Calculate price
          if dx > 10**5 and dy > 10**5:
              _dx: uint256 = dx * prec_i
              _dy: uint256 = dy * prec_j
              if i == 0:
                  p = _dx * 10**18 / _dy
              else:  # j == 0
                  p = _dy * 10**18 / _dx
      
          self.tweak_price(A_gamma, xp, p, 0)
      
          log TokenExchange(sender, i, dx, j, dy)
      
          return dy
      
      
      @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 = self._fee(xp) * N_COINS / (4 * (N_COINS-1))
          S: uint256 = 0
          for _x in amounts:
              S += _x
          avg: uint256 = S / N_COINS
          Sdiff: uint256 = 0
          for _x in amounts:
              if _x > avg:
                  Sdiff += _x - avg
              else:
                  Sdiff += 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,
                                  calc_price: bool) -> (uint256, uint256, uint256, uint256[N_COINS]):
          token_supply: uint256 = CurveToken(self.token).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
          D0: uint256 = 0
          precisions: uint256[2] = self._get_precisions()
      
          price_scale_i: uint256 = self.price_scale * precisions[1]
          xp: uint256[N_COINS] = [xx[0] * precisions[0], xx[1] * price_scale_i / PRECISION]
          if i == 0:
              price_scale_i = PRECISION * precisions[0]
      
          if update_D:
              D0 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          else:
              D0 = self.D
      
          D: uint256 = D0
      
          # Charge the fee on D, not on y, e.g. reducing invariant LESS than charging the user
          fee: uint256 = self._fee(xp)
          dD: uint256 = token_amount * D / token_supply
          D -= (dD - (fee * dD / (2 * 10**10) + 1))
          y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, i)
          dy: uint256 = (xp[i] - y) * PRECISION / price_scale_i
          xp[i] = y
      
          # Price calc
          p: uint256 = 0
          if calc_price and dy > 10**5 and token_amount > 10**5:
              # p_i = dD / D0 * sum'(p_k * x_k) / (dy - dD / D0 * y0)
              S: uint256 = 0
              precision: uint256 = precisions[0]
              if i == 1:
                  S = xx[0] * precisions[0]
                  precision = precisions[1]
              else:
                  S = xx[1] * precisions[1]
              S = S * dD / D0
              p = S * PRECISION / (dy * precision - dD * xx[i] * precision / D0)
              if i == 0:
                  p = (10**18)**2 / p
      
          return dy, p, D, xp
      
      
      @internal
      @pure
      def sqrt_int(x: uint256) -> uint256:
          """
          Originating from: https://github.com/vyperlang/vyper/issues/1266
          """
      
          if x == 0:
              return 0
      
          z: uint256 = (x + 10**18) / 2
          y: uint256 = x
      
          for i in range(256):
              if z == y:
                  return y
              y = z
              z = (x * 10**18 / z + z) / 2
      
          raise "Did not converge"
      
      
      # External Functions
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                   use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          """
          Exchange using WETH by default
          """
          return self._exchange(msg.sender, msg.value, i, j, dx, min_dy, use_eth, receiver, ZERO_ADDRESS, EMPTY_BYTES32)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange_underlying(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                              receiver: address = msg.sender) -> uint256:
          """
          Exchange using ETH
          """
          return self._exchange(msg.sender, msg.value, i, j, dx, min_dy, True, receiver, ZERO_ADDRESS, EMPTY_BYTES32)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def exchange_extended(i: uint256, j: uint256, dx: uint256, min_dy: uint256,
                            use_eth: bool, sender: address, receiver: address, cb: bytes32) -> uint256:
          assert cb != EMPTY_BYTES32  # dev: No callback specified
          return self._exchange(sender, msg.value, i, j, dx, min_dy, use_eth, receiver, msg.sender, cb)
      
      
      @payable
      @external
      @nonreentrant('lock')
      def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256,
                        use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          assert amounts[0] > 0 or amounts[1] > 0  # dev: no coins to add
      
          A_gamma: uint256[2] = self._A_gamma()
      
          xp: uint256[N_COINS] = self.balances
          amountsp: uint256[N_COINS] = empty(uint256[N_COINS])
          xx: uint256[N_COINS] = empty(uint256[N_COINS])
          d_token: uint256 = 0
          d_token_fee: uint256 = 0
          old_D: uint256 = 0
      
          xp_old: uint256[N_COINS] = xp
      
          for i in range(N_COINS):
              bal: uint256 = xp[i] + amounts[i]
              xp[i] = bal
              self.balances[i] = bal
          xx = xp
      
          precisions: uint256[2] = self._get_precisions()
      
          price_scale: uint256 = self.price_scale * precisions[1]
          xp = [xp[0] * precisions[0], xp[1] * price_scale / PRECISION]
          xp_old = [xp_old[0] * precisions[0], xp_old[1] * price_scale / PRECISION]
      
          if not use_eth:
              assert msg.value == 0  # dev: nonzero eth amount
      
          for i in range(N_COINS):
              coin: address = self.coins[i]
              if use_eth and coin == WETH20:
                  assert msg.value == amounts[i]  # dev: incorrect eth amount
              if amounts[i] > 0:
                  if (not use_eth) or (coin != WETH20):
                      response: Bytes[32] = raw_call(
                          coin,
                          _abi_encode(
                              msg.sender,
                              self,
                              amounts[i],
                              method_id=method_id("transferFrom(address,address,uint256)"),
                          ),
                          max_outsize=32,
                      )
                      if len(response) != 0:
                          assert convert(response, bool)  # dev: failed transfer
                      if coin == WETH20:
                          WETH(WETH20).withdraw(amounts[i])
                  amountsp[i] = xp[i] - xp_old[i]
      
          t: uint256 = self.future_A_gamma_time
          if t > 0:
              old_D = self.newton_D(A_gamma[0], A_gamma[1], xp_old)
              if block.timestamp >= t:
                  self.future_A_gamma_time = 1
          else:
              old_D = self.D
      
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
      
          lp_token: address = self.token
          token_supply: uint256 = CurveToken(lp_token).totalSupply()
          if old_D > 0:
              d_token = token_supply * D / old_D - token_supply
          else:
              d_token = self.get_xcp(D)  # 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
              CurveToken(lp_token).mint(receiver, d_token)
      
              # Calculate price
              # p_i * (dx_i - dtoken / token_supply * xx_i) = sum{k!=i}(p_k * (dtoken / token_supply * xx_k - dx_k))
              # Simplified for 2 coins
              p: uint256 = 0
              if d_token > 10**5:
                  if amounts[0] == 0 or amounts[1] == 0:
                      S: uint256 = 0
                      precision: uint256 = 0
                      ix: uint256 = 0
                      if amounts[0] == 0:
                          S = xx[0] * precisions[0]
                          precision = precisions[1]
                          ix = 1
                      else:
                          S = xx[1] * precisions[1]
                          precision = precisions[0]
                      S = S * d_token / token_supply
                      p = S * PRECISION / (amounts[ix] * precision - d_token * xx[ix] * precision / token_supply)
                      if ix == 0:
                          p = (10**18)**2 / p
      
              self.tweak_price(A_gamma, xp, p, D)
      
          else:
              self.D = D
              self.virtual_price = 10**18
              self.xcp_profit = 10**18
              CurveToken(lp_token).mint(receiver, d_token)
      
          assert d_token >= min_mint_amount, "Slippage"
      
          log AddLiquidity(receiver, amounts, d_token_fee, token_supply)
      
          return d_token
      
      
      @external
      @nonreentrant('lock')
      def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS],
                           use_eth: bool = False, receiver: address = msg.sender):
          """
          This withdrawal method is very safe, does no complex math
          """
          lp_token: address = self.token
          total_supply: uint256 = CurveToken(lp_token).totalSupply()
          CurveToken(lp_token).burnFrom(msg.sender, _amount)
          balances: uint256[N_COINS] = self.balances
          amount: uint256 = _amount - 1  # Make rounding errors favoring other LPs a tiny bit
      
          for i in range(N_COINS):
              d_balance: uint256 = balances[i] * amount / total_supply
              assert d_balance >= min_amounts[i]
              self.balances[i] = balances[i] - d_balance
              balances[i] = d_balance  # now it's the amounts going out
              coin: address = self.coins[i]
              if use_eth and coin == WETH20:
                  raw_call(receiver, b"", value=d_balance)
              else:
                  if coin == WETH20:
                      WETH(WETH20).deposit(value=d_balance)
                  response: Bytes[32] = raw_call(
                      coin,
                      _abi_encode(receiver, d_balance, method_id=method_id("transfer(address,uint256)")),
                      max_outsize=32,
                  )
                  if len(response) != 0:
                      assert convert(response, bool)
      
          D: uint256 = self.D
          self.D = D - D * amount / total_supply
      
          log RemoveLiquidity(msg.sender, balances, total_supply - _amount)
      
      
      @external
      @nonreentrant('lock')
      def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256,
                                    use_eth: bool = False, receiver: address = msg.sender) -> uint256:
          A_gamma: uint256[2] = self._A_gamma()
      
          dy: uint256 = 0
          D: uint256 = 0
          p: uint256 = 0
          xp: uint256[N_COINS] = empty(uint256[N_COINS])
          future_A_gamma_time: uint256 = self.future_A_gamma_time
          dy, p, D, xp = self._calc_withdraw_one_coin(A_gamma, token_amount, i, (future_A_gamma_time > 0), True)
          assert dy >= min_amount, "Slippage"
      
          if block.timestamp >= future_A_gamma_time:
              self.future_A_gamma_time = 1
      
          self.balances[i] -= dy
          CurveToken(self.token).burnFrom(msg.sender, token_amount)
      
          coin: address = self.coins[i]
          if use_eth and coin == WETH20:
              raw_call(receiver, b"", value=dy)
          else:
              if coin == WETH20:
                  WETH(WETH20).deposit(value=dy)
              response: Bytes[32] = raw_call(
                  coin,
                  _abi_encode(receiver, dy, method_id=method_id("transfer(address,uint256)")),
                  max_outsize=32,
              )
              if len(response) != 0:
                  assert convert(response, bool)
      
          self.tweak_price(A_gamma, xp, p, D)
      
          log RemoveLiquidityOne(msg.sender, token_amount, i, dy)
      
          return dy
      
      
      @external
      @nonreentrant('lock')
      def claim_admin_fees():
          self._claim_admin_fees()
      
      
      # Admin parameters
      @external
      def ramp_A_gamma(future_A: uint256, future_gamma: uint256, future_time: uint256):
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert block.timestamp > self.initial_A_gamma_time + (MIN_RAMP_TIME-1)
          assert future_time > block.timestamp + (MIN_RAMP_TIME-1)  # dev: insufficient time
      
          A_gamma: uint256[2] = self._A_gamma()
          initial_A_gamma: uint256 = shift(A_gamma[0], 128)
          initial_A_gamma = bitwise_or(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 = shift(future_A, 128)
          future_A_gamma = bitwise_or(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():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
      
          A_gamma: uint256[2] = self._A_gamma()
          current_A_gamma: uint256 = shift(A_gamma[0], 128)
          current_A_gamma = bitwise_or(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
      def commit_new_parameters(
          _new_mid_fee: uint256,
          _new_out_fee: uint256,
          _new_admin_fee: uint256,
          _new_fee_gamma: uint256,
          _new_allowed_extra_profit: uint256,
          _new_adjustment_step: uint256,
          _new_ma_half_time: uint256,
          ):
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert self.admin_actions_deadline == 0  # dev: active action
      
          new_mid_fee: uint256 = _new_mid_fee
          new_out_fee: uint256 = _new_out_fee
          new_admin_fee: uint256 = _new_admin_fee
          new_fee_gamma: uint256 = _new_fee_gamma
          new_allowed_extra_profit: uint256 = _new_allowed_extra_profit
          new_adjustment_step: uint256 = _new_adjustment_step
          new_ma_half_time: uint256 = _new_ma_half_time
      
          # Fees
          if new_out_fee < MAX_FEE+1:
              assert new_out_fee > MIN_FEE-1  # dev: fee is out of range
          else:
              new_out_fee = self.out_fee
          if new_mid_fee > MAX_FEE:
              new_mid_fee = self.mid_fee
          assert new_mid_fee <= new_out_fee  # dev: mid-fee is too high
          if new_admin_fee > MAX_ADMIN_FEE:
              new_admin_fee = self.admin_fee
      
          # AMM parameters
          if new_fee_gamma < 10**18:
              assert new_fee_gamma > 0  # dev: fee_gamma out of range [1 .. 10**18]
          else:
              new_fee_gamma = self.fee_gamma
          if new_allowed_extra_profit > 10**18:
              new_allowed_extra_profit = self.allowed_extra_profit
          if new_adjustment_step > 10**18:
              new_adjustment_step = self.adjustment_step
      
          # MA
          if new_ma_half_time < 7*86400:
              assert new_ma_half_time > 0  # dev: MA time should be longer than 1 second
          else:
              new_ma_half_time = self.ma_half_time
      
          _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
          self.admin_actions_deadline = _deadline
      
          self.future_admin_fee = new_admin_fee
          self.future_mid_fee = new_mid_fee
          self.future_out_fee = new_out_fee
          self.future_fee_gamma = new_fee_gamma
          self.future_allowed_extra_profit = new_allowed_extra_profit
          self.future_adjustment_step = new_adjustment_step
          self.future_ma_half_time = new_ma_half_time
      
          log CommitNewParameters(_deadline, new_admin_fee, new_mid_fee, new_out_fee,
                                  new_fee_gamma,
                                  new_allowed_extra_profit, new_adjustment_step,
                                  new_ma_half_time)
      
      
      @external
      @nonreentrant('lock')
      def apply_new_parameters():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
          assert block.timestamp >= self.admin_actions_deadline  # dev: insufficient time
          assert self.admin_actions_deadline != 0  # dev: no active action
      
          self.admin_actions_deadline = 0
      
          admin_fee: uint256 = self.future_admin_fee
          if self.admin_fee != admin_fee:
              self._claim_admin_fees()
              self.admin_fee = admin_fee
      
          mid_fee: uint256 = self.future_mid_fee
          self.mid_fee = mid_fee
          out_fee: uint256 = self.future_out_fee
          self.out_fee = out_fee
          fee_gamma: uint256 = self.future_fee_gamma
          self.fee_gamma = fee_gamma
          allowed_extra_profit: uint256 = self.future_allowed_extra_profit
          self.allowed_extra_profit = allowed_extra_profit
          adjustment_step: uint256 = self.future_adjustment_step
          self.adjustment_step = adjustment_step
          ma_half_time: uint256 = self.future_ma_half_time
          self.ma_half_time = ma_half_time
      
          log NewParameters(admin_fee, mid_fee, out_fee,
                            fee_gamma,
                            allowed_extra_profit, adjustment_step,
                            ma_half_time)
      
      
      @external
      def revert_new_parameters():
          assert msg.sender == Factory(self.factory).admin()  # dev: only owner
      
          self.admin_actions_deadline = 0
      
      
      # View Methods
      
      
      @external
      @view
      def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256:
          assert i != j  # dev: same input and output coin
          assert i < N_COINS  # dev: coin index out of range
          assert j < N_COINS  # dev: coin index out of range
      
          precisions: uint256[2] = self._get_precisions()
      
          price_scale: uint256 = self.price_scale * precisions[1]
          xp: uint256[N_COINS] = self.balances
      
          A_gamma: uint256[2] = self._A_gamma()
          D: uint256 = self.D
          if self.future_A_gamma_time > 0:
              D = self.newton_D(A_gamma[0], A_gamma[1], self.xp())
      
          xp[i] += dx
          xp = [xp[0] * precisions[0], xp[1] * price_scale / PRECISION]
      
          y: uint256 = self.newton_y(A_gamma[0], A_gamma[1], xp, D, j)
          dy: uint256 = xp[j] - y - 1
          xp[j] = y
          if j > 0:
              dy = dy * PRECISION / price_scale
          else:
              dy /= precisions[0]
          dy -= self._fee(xp) * dy / 10**10
      
          return dy
      
      
      @view
      @external
      def calc_token_amount(amounts: uint256[N_COINS]) -> uint256:
          token_supply: uint256 = CurveToken(self.token).totalSupply()
          precisions: uint256[2] = self._get_precisions()
          price_scale: uint256 = self.price_scale * precisions[1]
          A_gamma: uint256[2] = self._A_gamma()
          xp: uint256[N_COINS] = self.xp()
          amountsp: uint256[N_COINS] = [
              amounts[0] * precisions[0],
              amounts[1] * price_scale / PRECISION]
          D0: uint256 = self.D
          if self.future_A_gamma_time > 0:
              D0 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          xp[0] += amountsp[0]
          xp[1] += amountsp[1]
          D: uint256 = self.newton_D(A_gamma[0], A_gamma[1], xp)
          d_token: uint256 = token_supply * D / D0 - token_supply
          d_token -= self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1
          return d_token
      
      
      @view
      @external
      def calc_withdraw_one_coin(token_amount: uint256, i: uint256) -> uint256:
          return self._calc_withdraw_one_coin(self._A_gamma(), token_amount, i, True, False)[0]
      
      
      @external
      @view
      def lp_price() -> uint256:
          """
          Approximate LP token price
          """
          return 2 * self.virtual_price * self.sqrt_int(self.internal_price_oracle()) / 10**18
      
      
      @view
      @external
      def A() -> uint256:
          return self._A_gamma()[0]
      
      
      @view
      @external
      def gamma() -> uint256:
          return self._A_gamma()[1]
      
      
      @external
      @view
      def fee() -> uint256:
          return self._fee(self.xp())
      
      
      @external
      @view
      def get_virtual_price() -> uint256:
          return 10**18 * self.get_xcp(self.D) / CurveToken(self.token).totalSupply()
      
      
      @external
      @view
      def price_oracle() -> uint256:
          return self.internal_price_oracle()
      
      
      # Initializer
      
      
      @external
      def initialize(
          A: uint256,
          gamma: uint256,
          mid_fee: uint256,
          out_fee: uint256,
          allowed_extra_profit: uint256,
          fee_gamma: uint256,
          adjustment_step: uint256,
          admin_fee: uint256,
          ma_half_time: uint256,
          initial_price: uint256,
          _token: address,
          _coins: address[N_COINS],
          _precisions: uint256,
      ):
          assert self.mid_fee == 0  # dev: check that we call it from factory
      
          self.factory = msg.sender
      
          # Pack A and gamma:
          # shifted A + gamma
          A_gamma: uint256 = shift(A, 128)
          A_gamma = bitwise_or(A_gamma, gamma)
          self.initial_A_gamma = A_gamma
          self.future_A_gamma = A_gamma
      
          self.mid_fee = mid_fee
          self.out_fee = out_fee
          self.allowed_extra_profit = allowed_extra_profit
          self.fee_gamma = fee_gamma
          self.adjustment_step = adjustment_step
          self.admin_fee = admin_fee
      
          self.price_scale = initial_price
          self._price_oracle = initial_price
          self.last_prices = initial_price
          self.last_prices_timestamp = block.timestamp
          self.ma_half_time = ma_half_time
      
          self.xcp_profit_a = 10**18
      
          self.token = _token
          self.coins = _coins
          self.PRECISIONS = _precisions

      File 3 of 3: CNCToken
      // SPDX-License-Identifier: GPL-3.0-or-later
      // Sources flattened with hardhat v2.9.3 https://hardhat.org
      
      // File @openzeppelin/contracts/token/ERC20/[email protected]
      
      // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
      
      pragma solidity ^0.8.0;
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `to`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address to, uint256 amount) external returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `from` to `to` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address from,
              address to,
              uint256 amount
          ) external returns (bool);
      
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      
      
      // File @openzeppelin/contracts/token/ERC20/extensions/[email protected]
      
      // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
      
      pragma solidity ^0.8.0;
      
      /**
       * @dev Interface for the optional metadata functions from the ERC20 standard.
       *
       * _Available since v4.1._
       */
      interface IERC20Metadata is IERC20 {
          /**
           * @dev Returns the name of the token.
           */
          function name() external view returns (string memory);
      
          /**
           * @dev Returns the symbol of the token.
           */
          function symbol() external view returns (string memory);
      
          /**
           * @dev Returns the decimals places of the token.
           */
          function decimals() external view returns (uint8);
      }
      
      
      // File @openzeppelin/contracts/utils/[email protected]
      
      // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
      
      pragma solidity ^0.8.0;
      
      /**
       * @dev Provides information about the current execution context, including the
       * sender of the transaction and its data. While these are generally available
       * via msg.sender and msg.data, they should not be accessed in such a direct
       * manner, since when dealing with meta-transactions the account sending and
       * paying for execution may not be the actual sender (as far as an application
       * is concerned).
       *
       * This contract is only required for intermediate, library-like contracts.
       */
      abstract contract Context {
          function _msgSender() internal view virtual returns (address) {
              return msg.sender;
          }
      
          function _msgData() internal view virtual returns (bytes calldata) {
              return msg.data;
          }
      }
      
      
      // File @openzeppelin/contracts/token/ERC20/[email protected]
      
      // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol)
      
      pragma solidity ^0.8.0;
      
      
      
      /**
       * @dev Implementation of the {IERC20} interface.
       *
       * This implementation is agnostic to the way tokens are created. This means
       * that a supply mechanism has to be added in a derived contract using {_mint}.
       * For a generic mechanism see {ERC20PresetMinterPauser}.
       *
       * TIP: For a detailed writeup see our guide
       * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
       * to implement supply mechanisms].
       *
       * We have followed general OpenZeppelin Contracts guidelines: functions revert
       * instead returning `false` on failure. This behavior is nonetheless
       * conventional and does not conflict with the expectations of ERC20
       * applications.
       *
       * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
       * This allows applications to reconstruct the allowance for all accounts just
       * by listening to said events. Other implementations of the EIP may not emit
       * these events, as it isn't required by the specification.
       *
       * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
       * functions have been added to mitigate the well-known issues around setting
       * allowances. See {IERC20-approve}.
       */
      contract ERC20 is Context, IERC20, IERC20Metadata {
          mapping(address => uint256) private _balances;
      
          mapping(address => mapping(address => uint256)) private _allowances;
      
          uint256 private _totalSupply;
      
          string private _name;
          string private _symbol;
      
          /**
           * @dev Sets the values for {name} and {symbol}.
           *
           * The default value of {decimals} is 18. To select a different value for
           * {decimals} you should overload it.
           *
           * All two of these values are immutable: they can only be set once during
           * construction.
           */
          constructor(string memory name_, string memory symbol_) {
              _name = name_;
              _symbol = symbol_;
          }
      
          /**
           * @dev Returns the name of the token.
           */
          function name() public view virtual override returns (string memory) {
              return _name;
          }
      
          /**
           * @dev Returns the symbol of the token, usually a shorter version of the
           * name.
           */
          function symbol() public view virtual override returns (string memory) {
              return _symbol;
          }
      
          /**
           * @dev Returns the number of decimals used to get its user representation.
           * For example, if `decimals` equals `2`, a balance of `505` tokens should
           * be displayed to a user as `5.05` (`505 / 10 ** 2`).
           *
           * Tokens usually opt for a value of 18, imitating the relationship between
           * Ether and Wei. This is the value {ERC20} uses, unless this function is
           * overridden;
           *
           * NOTE: This information is only used for _display_ purposes: it in
           * no way affects any of the arithmetic of the contract, including
           * {IERC20-balanceOf} and {IERC20-transfer}.
           */
          function decimals() public view virtual override returns (uint8) {
              return 18;
          }
      
          /**
           * @dev See {IERC20-totalSupply}.
           */
          function totalSupply() public view virtual override returns (uint256) {
              return _totalSupply;
          }
      
          /**
           * @dev See {IERC20-balanceOf}.
           */
          function balanceOf(address account) public view virtual override returns (uint256) {
              return _balances[account];
          }
      
          /**
           * @dev See {IERC20-transfer}.
           *
           * Requirements:
           *
           * - `to` cannot be the zero address.
           * - the caller must have a balance of at least `amount`.
           */
          function transfer(address to, uint256 amount) public virtual override returns (bool) {
              address owner = _msgSender();
              _transfer(owner, to, amount);
              return true;
          }
      
          /**
           * @dev See {IERC20-allowance}.
           */
          function allowance(address owner, address spender) public view virtual override returns (uint256) {
              return _allowances[owner][spender];
          }
      
          /**
           * @dev See {IERC20-approve}.
           *
           * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
           * `transferFrom`. This is semantically equivalent to an infinite approval.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           */
          function approve(address spender, uint256 amount) public virtual override returns (bool) {
              address owner = _msgSender();
              _approve(owner, spender, amount);
              return true;
          }
      
          /**
           * @dev See {IERC20-transferFrom}.
           *
           * Emits an {Approval} event indicating the updated allowance. This is not
           * required by the EIP. See the note at the beginning of {ERC20}.
           *
           * NOTE: Does not update the allowance if the current allowance
           * is the maximum `uint256`.
           *
           * Requirements:
           *
           * - `from` and `to` cannot be the zero address.
           * - `from` must have a balance of at least `amount`.
           * - the caller must have allowance for ``from``'s tokens of at least
           * `amount`.
           */
          function transferFrom(
              address from,
              address to,
              uint256 amount
          ) public virtual override returns (bool) {
              address spender = _msgSender();
              _spendAllowance(from, spender, amount);
              _transfer(from, to, amount);
              return true;
          }
      
          /**
           * @dev Atomically increases the allowance granted to `spender` by the caller.
           *
           * This is an alternative to {approve} that can be used as a mitigation for
           * problems described in {IERC20-approve}.
           *
           * Emits an {Approval} event indicating the updated allowance.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           */
          function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
              address owner = _msgSender();
              _approve(owner, spender, _allowances[owner][spender] + addedValue);
              return true;
          }
      
          /**
           * @dev Atomically decreases the allowance granted to `spender` by the caller.
           *
           * This is an alternative to {approve} that can be used as a mitigation for
           * problems described in {IERC20-approve}.
           *
           * Emits an {Approval} event indicating the updated allowance.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           * - `spender` must have allowance for the caller of at least
           * `subtractedValue`.
           */
          function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
              address owner = _msgSender();
              uint256 currentAllowance = _allowances[owner][spender];
              require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
              unchecked {
                  _approve(owner, spender, currentAllowance - subtractedValue);
              }
      
              return true;
          }
      
          /**
           * @dev Moves `amount` of tokens from `sender` to `recipient`.
           *
           * This internal function is equivalent to {transfer}, and can be used to
           * e.g. implement automatic token fees, slashing mechanisms, etc.
           *
           * Emits a {Transfer} event.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `from` must have a balance of at least `amount`.
           */
          function _transfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {
              require(from != address(0), "ERC20: transfer from the zero address");
              require(to != address(0), "ERC20: transfer to the zero address");
      
              _beforeTokenTransfer(from, to, amount);
      
              uint256 fromBalance = _balances[from];
              require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
              unchecked {
                  _balances[from] = fromBalance - amount;
              }
              _balances[to] += amount;
      
              emit Transfer(from, to, amount);
      
              _afterTokenTransfer(from, to, amount);
          }
      
          /** @dev Creates `amount` tokens and assigns them to `account`, increasing
           * the total supply.
           *
           * Emits a {Transfer} event with `from` set to the zero address.
           *
           * Requirements:
           *
           * - `account` cannot be the zero address.
           */
          function _mint(address account, uint256 amount) internal virtual {
              require(account != address(0), "ERC20: mint to the zero address");
      
              _beforeTokenTransfer(address(0), account, amount);
      
              _totalSupply += amount;
              _balances[account] += amount;
              emit Transfer(address(0), account, amount);
      
              _afterTokenTransfer(address(0), account, amount);
          }
      
          /**
           * @dev Destroys `amount` tokens from `account`, reducing the
           * total supply.
           *
           * Emits a {Transfer} event with `to` set to the zero address.
           *
           * Requirements:
           *
           * - `account` cannot be the zero address.
           * - `account` must have at least `amount` tokens.
           */
          function _burn(address account, uint256 amount) internal virtual {
              require(account != address(0), "ERC20: burn from the zero address");
      
              _beforeTokenTransfer(account, address(0), amount);
      
              uint256 accountBalance = _balances[account];
              require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
              unchecked {
                  _balances[account] = accountBalance - amount;
              }
              _totalSupply -= amount;
      
              emit Transfer(account, address(0), amount);
      
              _afterTokenTransfer(account, address(0), amount);
          }
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
           *
           * This internal function is equivalent to `approve`, and can be used to
           * e.g. set automatic allowances for certain subsystems, etc.
           *
           * Emits an {Approval} event.
           *
           * Requirements:
           *
           * - `owner` cannot be the zero address.
           * - `spender` cannot be the zero address.
           */
          function _approve(
              address owner,
              address spender,
              uint256 amount
          ) internal virtual {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
      
              _allowances[owner][spender] = amount;
              emit Approval(owner, spender, amount);
          }
      
          /**
           * @dev Spend `amount` form the allowance of `owner` toward `spender`.
           *
           * Does not update the allowance amount in case of infinite allowance.
           * Revert if not enough allowance is available.
           *
           * Might emit an {Approval} event.
           */
          function _spendAllowance(
              address owner,
              address spender,
              uint256 amount
          ) internal virtual {
              uint256 currentAllowance = allowance(owner, spender);
              if (currentAllowance != type(uint256).max) {
                  require(currentAllowance >= amount, "ERC20: insufficient allowance");
                  unchecked {
                      _approve(owner, spender, currentAllowance - amount);
                  }
              }
          }
      
          /**
           * @dev Hook that is called before any transfer of tokens. This includes
           * minting and burning.
           *
           * Calling conditions:
           *
           * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
           * will be transferred to `to`.
           * - when `from` is zero, `amount` tokens will be minted for `to`.
           * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
           * - `from` and `to` are never both zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _beforeTokenTransfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {}
      
          /**
           * @dev Hook that is called after any transfer of tokens. This includes
           * minting and burning.
           *
           * Calling conditions:
           *
           * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
           * has been transferred to `to`.
           * - when `from` is zero, `amount` tokens have been minted for `to`.
           * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
           * - `from` and `to` are never both zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _afterTokenTransfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {}
      }
      
      
      // File @openzeppelin/contracts/utils/structs/[email protected]
      
      // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
      
      pragma solidity ^0.8.0;
      
      /**
       * @dev Library for managing
       * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
       * types.
       *
       * Sets have the following properties:
       *
       * - Elements are added, removed, and checked for existence in constant time
       * (O(1)).
       * - Elements are enumerated in O(n). No guarantees are made on the ordering.
       *
       * ```
       * contract Example {
       *     // Add the library methods
       *     using EnumerableSet for EnumerableSet.AddressSet;
       *
       *     // Declare a set state variable
       *     EnumerableSet.AddressSet private mySet;
       * }
       * ```
       *
       * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
       * and `uint256` (`UintSet`) are supported.
       */
      library EnumerableSet {
          // To implement this library for multiple types with as little code
          // repetition as possible, we write it in terms of a generic Set type with
          // bytes32 values.
          // The Set implementation uses private functions, and user-facing
          // implementations (such as AddressSet) are just wrappers around the
          // underlying Set.
          // This means that we can only create new EnumerableSets for types that fit
          // in bytes32.
      
          struct Set {
              // Storage of set values
              bytes32[] _values;
              // Position of the value in the `values` array, plus 1 because index 0
              // means a value is not in the set.
              mapping(bytes32 => uint256) _indexes;
          }
      
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function _add(Set storage set, bytes32 value) private returns (bool) {
              if (!_contains(set, value)) {
                  set._values.push(value);
                  // The value is stored at length-1, but we add 1 to all indexes
                  // and use 0 as a sentinel value
                  set._indexes[value] = set._values.length;
                  return true;
              } else {
                  return false;
              }
          }
      
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function _remove(Set storage set, bytes32 value) private returns (bool) {
              // We read and store the value's index to prevent multiple reads from the same storage slot
              uint256 valueIndex = set._indexes[value];
      
              if (valueIndex != 0) {
                  // Equivalent to contains(set, value)
                  // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                  // the array, and then remove the last element (sometimes called as 'swap and pop').
                  // This modifies the order of the array, as noted in {at}.
      
                  uint256 toDeleteIndex = valueIndex - 1;
                  uint256 lastIndex = set._values.length - 1;
      
                  if (lastIndex != toDeleteIndex) {
                      bytes32 lastvalue = set._values[lastIndex];
      
                      // Move the last value to the index where the value to delete is
                      set._values[toDeleteIndex] = lastvalue;
                      // Update the index for the moved value
                      set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
                  }
      
                  // Delete the slot where the moved value was stored
                  set._values.pop();
      
                  // Delete the index for the deleted slot
                  delete set._indexes[value];
      
                  return true;
              } else {
                  return false;
              }
          }
      
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function _contains(Set storage set, bytes32 value) private view returns (bool) {
              return set._indexes[value] != 0;
          }
      
          /**
           * @dev Returns the number of values on the set. O(1).
           */
          function _length(Set storage set) private view returns (uint256) {
              return set._values.length;
          }
      
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function _at(Set storage set, uint256 index) private view returns (bytes32) {
              return set._values[index];
          }
      
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function _values(Set storage set) private view returns (bytes32[] memory) {
              return set._values;
          }
      
          // Bytes32Set
      
          struct Bytes32Set {
              Set _inner;
          }
      
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
              return _add(set._inner, value);
          }
      
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
              return _remove(set._inner, value);
          }
      
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
              return _contains(set._inner, value);
          }
      
          /**
           * @dev Returns the number of values in the set. O(1).
           */
          function length(Bytes32Set storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
      
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
              return _at(set._inner, index);
          }
      
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
              return _values(set._inner);
          }
      
          // AddressSet
      
          struct AddressSet {
              Set _inner;
          }
      
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(AddressSet storage set, address value) internal returns (bool) {
              return _add(set._inner, bytes32(uint256(uint160(value))));
          }
      
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(AddressSet storage set, address value) internal returns (bool) {
              return _remove(set._inner, bytes32(uint256(uint160(value))));
          }
      
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(AddressSet storage set, address value) internal view returns (bool) {
              return _contains(set._inner, bytes32(uint256(uint160(value))));
          }
      
          /**
           * @dev Returns the number of values in the set. O(1).
           */
          function length(AddressSet storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
      
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(AddressSet storage set, uint256 index) internal view returns (address) {
              return address(uint160(uint256(_at(set._inner, index))));
          }
      
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(AddressSet storage set) internal view returns (address[] memory) {
              bytes32[] memory store = _values(set._inner);
              address[] memory result;
      
              assembly {
                  result := store
              }
      
              return result;
          }
      
          // UintSet
      
          struct UintSet {
              Set _inner;
          }
      
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(UintSet storage set, uint256 value) internal returns (bool) {
              return _add(set._inner, bytes32(value));
          }
      
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(UintSet storage set, uint256 value) internal returns (bool) {
              return _remove(set._inner, bytes32(value));
          }
      
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(UintSet storage set, uint256 value) internal view returns (bool) {
              return _contains(set._inner, bytes32(value));
          }
      
          /**
           * @dev Returns the number of values on the set. O(1).
           */
          function length(UintSet storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
      
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(UintSet storage set, uint256 index) internal view returns (uint256) {
              return uint256(_at(set._inner, index));
          }
      
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(UintSet storage set) internal view returns (uint256[] memory) {
              bytes32[] memory store = _values(set._inner);
              uint256[] memory result;
      
              assembly {
                  result := store
              }
      
              return result;
          }
      }
      
      
      // File libraries/ScaledMath.sol
      
      pragma solidity ^0.8.13;
      
      library ScaledMath {
          uint256 internal constant DECIMALS = 18;
          uint256 internal constant ONE = 10**DECIMALS;
      
          function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {
              return (a * b) / ONE;
          }
      
          function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
              return (a * ONE) / b;
          }
      }
      
      
      // File interfaces/tokenomics/ICNCToken.sol
      
      pragma solidity ^0.8.13;
      
      interface ICNCToken is IERC20 {
          event MinterAdded(address minter);
          event MinterRemoved(address minter);
          event InitialDistributionMinted(uint256 amount);
          event AirdropMinted(uint256 amount);
          event AMMRewardsMinted(uint256 amount);
          event TreasuryRewardsMinted(uint256 amount);
          event SeedShareMinted(uint256 amount);
      
          /// @notice mints the initial distribution amount to the distribution contract
          function mintInitialDistribution(address distribution) external;
      
          /// @notice mints the airdrop amount to the airdrop contract
          function mintAirdrop(address airdropHandler) external;
      
          /// @notice mints the amm rewards
          function mintAMMRewards(address ammGauge) external;
      
          /// @notice mints `amount` to `account`
          function mint(address account, uint256 amount) external returns (uint256);
      
          /// @notice returns a list of all authorized minters
          function listMinters() external view returns (address[] memory);
      
          /// @notice returns the ratio of inflation already minted
          function inflationMintedRatio() external view returns (uint256);
      }
      
      
      // File contracts/tokenomics/CNCToken.sol
      
      pragma solidity ^0.8.13;
      
      
      
      /// @notice the deployer will initially have minting rights
      /// The process will be to premint `PRE_MINT_RATIO` of `MAX_TOTAL_SUPPLY`
      /// to the initial distribution address, grant the minting rights to the inflation manager
      /// and renounce its minting rights
      contract CNCToken is ICNCToken, ERC20 {
          using EnumerableSet for EnumerableSet.AddressSet;
          using ScaledMath for uint256;
      
          uint256 public constant PRE_MINT_RATIO = 0.3e18;
          uint256 public constant AIRDROP_MINT_RATIO = 0.1e18;
          uint256 public constant AMM_REWARDS_RATIO = 0.1e18;
          uint256 public constant TREASURY_REWARDS_RATIO = 0.05e18;
          uint256 public constant TREASURY_SEED_RATIO = 0.01e18;
          uint256 public constant INFLATION_RATIO =
              1e18 -
                  AMM_REWARDS_RATIO -
                  AIRDROP_MINT_RATIO -
                  PRE_MINT_RATIO -
                  TREASURY_REWARDS_RATIO -
                  TREASURY_SEED_RATIO;
          uint256 public constant MAX_TOTAL_SUPPLY = 10_000_000e18;
      
          EnumerableSet.AddressSet internal authorizedMinters;
      
          bool public initialDistributionMintDone;
          bool public airdropMintDone;
          bool public ammGaugeMintDone;
          bool public treasuryMintDone;
          bool public seedShareMintDone;
      
          modifier onlyMinter() {
              require(authorizedMinters.contains(msg.sender), "not authorized");
              _;
          }
      
          constructor() ERC20("Conic Finance Token", "CNC") {
              authorizedMinters.add(msg.sender);
              emit MinterAdded(msg.sender);
          }
      
          function addMinter(address newMinter) external onlyMinter {
              if (authorizedMinters.add(newMinter)) {
                  emit MinterAdded(newMinter);
              }
          }
      
          function renounceMinterRights() external onlyMinter {
              authorizedMinters.remove(msg.sender);
              emit MinterRemoved(msg.sender);
          }
      
          function mintInitialDistribution(address distribution) external onlyMinter {
              require(!initialDistributionMintDone, "premint already done");
              uint256 mintAmount = MAX_TOTAL_SUPPLY.mulDown(PRE_MINT_RATIO);
              _mint(distribution, mintAmount);
              initialDistributionMintDone = true;
              emit InitialDistributionMinted(mintAmount);
          }
      
          function mintAirdrop(address airdropHandler) external onlyMinter {
              require(!airdropMintDone, "airdrop already done");
              uint256 mintAmount = MAX_TOTAL_SUPPLY.mulDown(AIRDROP_MINT_RATIO);
              _mint(airdropHandler, mintAmount);
              airdropMintDone = true;
              emit AirdropMinted(mintAmount);
          }
      
          function mintAMMRewards(address ammGauge) external onlyMinter {
              require(!ammGaugeMintDone, "amm rewards already minted");
              uint256 mintAmount = MAX_TOTAL_SUPPLY.mulDown(AMM_REWARDS_RATIO);
              _mint(ammGauge, mintAmount);
              ammGaugeMintDone = true;
              emit AMMRewardsMinted(mintAmount);
          }
      
          function mintTreasuryShare(address treasuryEscrow) external onlyMinter {
              require(!treasuryMintDone, "treasury rewards already minted");
              uint256 mintAmount = MAX_TOTAL_SUPPLY.mulDown(TREASURY_REWARDS_RATIO);
              _mint(treasuryEscrow, mintAmount);
              treasuryMintDone = true;
              emit TreasuryRewardsMinted(mintAmount);
          }
      
          function mintSeedShare(address treasury) external onlyMinter {
              require(!seedShareMintDone, "seed share already minted");
              uint256 mintAmount = MAX_TOTAL_SUPPLY.mulDown(TREASURY_SEED_RATIO);
              _mint(treasury, mintAmount);
              seedShareMintDone = true;
              emit SeedShareMinted(mintAmount);
          }
      
          function mint(address account, uint256 amount) external onlyMinter returns (uint256) {
              uint256 currentSupply = totalSupply();
              if (amount + currentSupply > MAX_TOTAL_SUPPLY) {
                  amount = MAX_TOTAL_SUPPLY - currentSupply;
              }
              if (amount > 0) {
                  _mint(account, amount);
              }
              return amount;
          }
      
          /// @dev this assumes that all the pre-mint events occured
          function inflationMintedRatio() external view returns (uint256) {
              uint256 currentSupply = totalSupply();
              uint256 totalToMint = MAX_TOTAL_SUPPLY.mulDown(INFLATION_RATIO);
              uint256 totalPreMinted = MAX_TOTAL_SUPPLY - totalToMint;
              uint256 totalInflationMinted = currentSupply - totalPreMinted;
              return totalInflationMinted.divDown(totalToMint);
          }
      
          function listMinters() external view returns (address[] memory) {
              return authorizedMinters.values();
          }
      }