ETH Price: $2,719.91 (+4.81%)

Transaction Decoder

Block:
16312915 at Jan-01-2023 03:48:59 PM +UTC
Transaction Fee:
0.002029224801715834 ETH $5.52
Gas Used:
133,442 Gas / 15.206792477 Gwei

Emitted Events:

211 Vyper_contract.Transfer( _from=[Sender] 0x8851924938db253c2602cfb473c33b88deb83c43, _to=0x0000000000000000000000000000000000000000, _value=640032808888149441028 )
212 TetherToken.Transfer( from=[Receiver] Vyper_contract, to=[Sender] 0x8851924938db253c2602cfb473c33b88deb83c43, value=654991192 )
213 Vyper_contract.RemoveLiquidityOne( provider=[Sender] 0x8851924938db253c2602cfb473c33b88deb83c43, token_amount=640032808888149441028, coin_amount=654991192 )

Account State Difference:

  Address   Before After State Difference Code
0x6c3F90f0...2BDe6E490
0x88519249...8dEb83C43
0.135701858038440429 Eth
Nonce: 1443
0.133672633236724595 Eth
Nonce: 1444
0.002029224801715834
0xbEbc4478...3032FF1C7
(Curve.fi: DAI/USDC/USDT Pool)
0xdAC17F95...13D831ec7
(Flashbots: Builder)
1.172489631627245269 Eth1.172689794627245269 Eth0.000200163

Execution Trace

Vyper_contract.remove_liquidity_one_coin( _token_amount=640032808888149441028, i=2, min_amount=654794694 )
  • Vyper_contract.STATICCALL( )
  • Vyper_contract.burnFrom( _to=0x8851924938db253C2602cFB473C33b88dEb83C43, _value=640032808888149441028 ) => ( True )
  • Null: 0x000...004.CALL( )
  • Null: 0x000...004.00000000( )
  • TetherToken.transfer( _to=0x8851924938db253C2602cFB473C33b88dEb83C43, _value=654991192 )
  • Null: 0x000...004.00000000( )
    File 1 of 3: Vyper_contract
    # @version 0.2.4
    # (c) Curve.Fi, 2020
    # Pool for DAI/USDC/USDT
    
    from vyper.interfaces import ERC20
    
    interface CurveToken:
        def totalSupply() -> uint256: view
        def mint(_to: address, _value: uint256) -> bool: nonpayable
        def burnFrom(_to: address, _value: uint256) -> bool: nonpayable
    
    
    # Events
    event TokenExchange:
        buyer: indexed(address)
        sold_id: int128
        tokens_sold: uint256
        bought_id: int128
        tokens_bought: uint256
    
    
    event AddLiquidity:
        provider: indexed(address)
        token_amounts: uint256[N_COINS]
        fees: uint256[N_COINS]
        invariant: uint256
        token_supply: uint256
    
    event RemoveLiquidity:
        provider: indexed(address)
        token_amounts: uint256[N_COINS]
        fees: uint256[N_COINS]
        token_supply: uint256
    
    event RemoveLiquidityOne:
        provider: indexed(address)
        token_amount: uint256
        coin_amount: uint256
    
    event RemoveLiquidityImbalance:
        provider: indexed(address)
        token_amounts: uint256[N_COINS]
        fees: uint256[N_COINS]
        invariant: uint256
        token_supply: uint256
    
    event CommitNewAdmin:
        deadline: indexed(uint256)
        admin: indexed(address)
    
    event NewAdmin:
        admin: indexed(address)
    
    
    event CommitNewFee:
        deadline: indexed(uint256)
        fee: uint256
        admin_fee: uint256
    
    event NewFee:
        fee: uint256
        admin_fee: uint256
    
    event RampA:
        old_A: uint256
        new_A: uint256
        initial_time: uint256
        future_time: uint256
    
    event StopRampA:
        A: uint256
        t: uint256
    
    
    # This can (and needs to) be changed at compile time
    N_COINS: constant(int128) = 3  # <- change
    
    FEE_DENOMINATOR: constant(uint256) = 10 ** 10
    LENDING_PRECISION: constant(uint256) = 10 ** 18
    PRECISION: constant(uint256) = 10 ** 18  # The precision to convert to
    PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1000000000000, 1000000000000]
    RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000]
    FEE_INDEX: constant(int128) = 2  # Which coin may potentially have fees (USDT)
    
    MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9
    MAX_FEE: constant(uint256) = 5 * 10 ** 9
    MAX_A: constant(uint256) = 10 ** 6
    MAX_A_CHANGE: constant(uint256) = 10
    
    ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400
    MIN_RAMP_TIME: constant(uint256) = 86400
    
    coins: public(address[N_COINS])
    balances: public(uint256[N_COINS])
    fee: public(uint256)  # fee * 1e10
    admin_fee: public(uint256)  # admin_fee * 1e10
    
    owner: public(address)
    token: CurveToken
    
    initial_A: public(uint256)
    future_A: public(uint256)
    initial_A_time: public(uint256)
    future_A_time: public(uint256)
    
    admin_actions_deadline: public(uint256)
    transfer_ownership_deadline: public(uint256)
    future_fee: public(uint256)
    future_admin_fee: public(uint256)
    future_owner: public(address)
    
    is_killed: bool
    kill_deadline: uint256
    KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400
    
    
    @external
    def __init__(
        _owner: address,
        _coins: address[N_COINS],
        _pool_token: address,
        _A: uint256,
        _fee: uint256,
        _admin_fee: uint256
    ):
        """
        @notice Contract constructor
        @param _owner Contract owner address
        @param _coins Addresses of ERC20 conracts of coins
        @param _pool_token Address of the token representing LP share
        @param _A Amplification coefficient multiplied by n * (n - 1)
        @param _fee Fee to charge for exchanges
        @param _admin_fee Admin fee
        """
        for i in range(N_COINS):
            assert _coins[i] != ZERO_ADDRESS
        self.coins = _coins
        self.initial_A = _A
        self.future_A = _A
        self.fee = _fee
        self.admin_fee = _admin_fee
        self.owner = _owner
        self.kill_deadline = block.timestamp + KILL_DEADLINE_DT
        self.token = CurveToken(_pool_token)
    
    
    @view
    @internal
    def _A() -> uint256:
        """
        Handle ramping A up or down
        """
        t1: uint256 = self.future_A_time
        A1: uint256 = self.future_A
    
        if block.timestamp < t1:
            A0: uint256 = self.initial_A
            t0: uint256 = self.initial_A_time
            # Expressions in uint256 cannot have negative numbers, thus "if"
            if A1 > A0:
                return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0)
            else:
                return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0)
    
        else:  # when t1 == 0 or block.timestamp >= t1
            return A1
    
    
    @view
    @external
    def A() -> uint256:
        return self._A()
    
    
    @view
    @internal
    def _xp() -> uint256[N_COINS]:
        result: uint256[N_COINS] = RATES
        for i in range(N_COINS):
            result[i] = result[i] * self.balances[i] / LENDING_PRECISION
        return result
    
    
    @pure
    @internal
    def _xp_mem(_balances: uint256[N_COINS]) -> uint256[N_COINS]:
        result: uint256[N_COINS] = RATES
        for i in range(N_COINS):
            result[i] = result[i] * _balances[i] / PRECISION
        return result
    
    
    @pure
    @internal
    def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256:
        S: uint256 = 0
        for _x in xp:
            S += _x
        if S == 0:
            return 0
    
        Dprev: uint256 = 0
        D: uint256 = S
        Ann: uint256 = amp * N_COINS
        for _i in range(255):
            D_P: uint256 = D
            for _x in xp:
                D_P = D_P * D / (_x * N_COINS)  # If division by 0, this will be borked: only withdrawal will work. And that is good
            Dprev = D
            D = (Ann * S + D_P * N_COINS) * D / ((Ann - 1) * D + (N_COINS + 1) * D_P)
            # Equality with the precision of 1
            if D > Dprev:
                if D - Dprev <= 1:
                    break
            else:
                if Dprev - D <= 1:
                    break
        return D
    
    
    @view
    @internal
    def get_D_mem(_balances: uint256[N_COINS], amp: uint256) -> uint256:
        return self.get_D(self._xp_mem(_balances), amp)
    
    
    @view
    @external
    def get_virtual_price() -> uint256:
        """
        Returns portfolio virtual price (for calculating profit)
        scaled up by 1e18
        """
        D: uint256 = self.get_D(self._xp(), self._A())
        # D is in the units similar to DAI (e.g. converted to precision 1e18)
        # When balanced, D = n * x_u - total virtual value of the portfolio
        token_supply: uint256 = self.token.totalSupply()
        return D * PRECISION / token_supply
    
    
    @view
    @external
    def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256:
        """
        Simplified method to calculate addition or reduction in token supply at
        deposit or withdrawal without taking fees into account (but looking at
        slippage).
        Needed to prevent front-running, not for precise calculations!
        """
        _balances: uint256[N_COINS] = self.balances
        amp: uint256 = self._A()
        D0: uint256 = self.get_D_mem(_balances, amp)
        for i in range(N_COINS):
            if deposit:
                _balances[i] += amounts[i]
            else:
                _balances[i] -= amounts[i]
        D1: uint256 = self.get_D_mem(_balances, amp)
        token_amount: uint256 = self.token.totalSupply()
        diff: uint256 = 0
        if deposit:
            diff = D1 - D0
        else:
            diff = D0 - D1
        return diff * token_amount / D0
    
    
    @external
    @nonreentrant('lock')
    def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256):
        assert not self.is_killed  # dev: is killed
    
        fees: uint256[N_COINS] = empty(uint256[N_COINS])
        _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
        _admin_fee: uint256 = self.admin_fee
        amp: uint256 = self._A()
    
        token_supply: uint256 = self.token.totalSupply()
        # Initial invariant
        D0: uint256 = 0
        old_balances: uint256[N_COINS] = self.balances
        if token_supply > 0:
            D0 = self.get_D_mem(old_balances, amp)
        new_balances: uint256[N_COINS] = old_balances
    
        for i in range(N_COINS):
            in_amount: uint256 = amounts[i]
            if token_supply == 0:
                assert in_amount > 0  # dev: initial deposit requires all coins
            in_coin: address = self.coins[i]
    
            # Take coins from the sender
            if in_amount > 0:
                if i == FEE_INDEX:
                    in_amount = ERC20(in_coin).balanceOf(self)
    
                # "safeTransferFrom" which works for ERC20s which return bool or not
                _response: Bytes[32] = raw_call(
                    in_coin,
                    concat(
                        method_id("transferFrom(address,address,uint256)"),
                        convert(msg.sender, bytes32),
                        convert(self, bytes32),
                        convert(amounts[i], bytes32),
                    ),
                    max_outsize=32,
                )  # dev: failed transfer
                if len(_response) > 0:
                    assert convert(_response, bool)  # dev: failed transfer
    
                if i == FEE_INDEX:
                    in_amount = ERC20(in_coin).balanceOf(self) - in_amount
    
            new_balances[i] = old_balances[i] + in_amount
    
        # Invariant after change
        D1: uint256 = self.get_D_mem(new_balances, amp)
        assert D1 > D0
    
        # We need to recalculate the invariant accounting for fees
        # to calculate fair user's share
        D2: uint256 = D1
        if token_supply > 0:
            # Only account for fees if we are not the first to deposit
            for i in range(N_COINS):
                ideal_balance: uint256 = D1 * old_balances[i] / D0
                difference: uint256 = 0
                if ideal_balance > new_balances[i]:
                    difference = ideal_balance - new_balances[i]
                else:
                    difference = new_balances[i] - ideal_balance
                fees[i] = _fee * difference / FEE_DENOMINATOR
                self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR)
                new_balances[i] -= fees[i]
            D2 = self.get_D_mem(new_balances, amp)
        else:
            self.balances = new_balances
    
        # Calculate, how much pool tokens to mint
        mint_amount: uint256 = 0
        if token_supply == 0:
            mint_amount = D1  # Take the dust if there was any
        else:
            mint_amount = token_supply * (D2 - D0) / D0
    
        assert mint_amount >= min_mint_amount, "Slippage screwed you"
    
        # Mint pool tokens
        self.token.mint(msg.sender, mint_amount)
    
        log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount)
    
    
    @view
    @internal
    def get_y(i: int128, j: int128, x: uint256, xp_: uint256[N_COINS]) -> uint256:
        # x in the input is converted to the same price/precision
    
        assert i != j       # dev: same coin
        assert j >= 0       # dev: j below zero
        assert j < N_COINS  # dev: j above N_COINS
    
        # should be unreachable, but good for safety
        assert i >= 0
        assert i < N_COINS
    
        amp: uint256 = self._A()
        D: uint256 = self.get_D(xp_, amp)
        c: uint256 = D
        S_: uint256 = 0
        Ann: uint256 = amp * N_COINS
    
        _x: uint256 = 0
        for _i in range(N_COINS):
            if _i == i:
                _x = x
            elif _i != j:
                _x = xp_[_i]
            else:
                continue
            S_ += _x
            c = c * D / (_x * N_COINS)
        c = c * D / (Ann * N_COINS)
        b: uint256 = S_ + D / Ann  # - D
        y_prev: uint256 = 0
        y: uint256 = D
        for _i in range(255):
            y_prev = y
            y = (y*y + c) / (2 * y + b - D)
            # Equality with the precision of 1
            if y > y_prev:
                if y - y_prev <= 1:
                    break
            else:
                if y_prev - y <= 1:
                    break
        return y
    
    
    @view
    @external
    def get_dy(i: int128, j: int128, dx: uint256) -> uint256:
        # dx and dy in c-units
        rates: uint256[N_COINS] = RATES
        xp: uint256[N_COINS] = self._xp()
    
        x: uint256 = xp[i] + (dx * rates[i] / PRECISION)
        y: uint256 = self.get_y(i, j, x, xp)
        dy: uint256 = (xp[j] - y - 1) * PRECISION / rates[j]
        _fee: uint256 = self.fee * dy / FEE_DENOMINATOR
        return dy - _fee
    
    
    @view
    @external
    def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256:
        # dx and dy in underlying units
        xp: uint256[N_COINS] = self._xp()
        precisions: uint256[N_COINS] = PRECISION_MUL
    
        x: uint256 = xp[i] + dx * precisions[i]
        y: uint256 = self.get_y(i, j, x, xp)
        dy: uint256 = (xp[j] - y - 1) / precisions[j]
        _fee: uint256 = self.fee * dy / FEE_DENOMINATOR
        return dy - _fee
    
    
    
    @external
    @nonreentrant('lock')
    def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256):
        assert not self.is_killed  # dev: is killed
        rates: uint256[N_COINS] = RATES
    
        old_balances: uint256[N_COINS] = self.balances
        xp: uint256[N_COINS] = self._xp_mem(old_balances)
    
        # Handling an unexpected charge of a fee on transfer (USDT, PAXG)
        dx_w_fee: uint256 = dx
        input_coin: address = self.coins[i]
    
        if i == FEE_INDEX:
            dx_w_fee = ERC20(input_coin).balanceOf(self)
    
        # "safeTransferFrom" which works for ERC20s which return bool or not
        _response: Bytes[32] = raw_call(
            input_coin,
            concat(
                method_id("transferFrom(address,address,uint256)"),
                convert(msg.sender, bytes32),
                convert(self, bytes32),
                convert(dx, bytes32),
            ),
            max_outsize=32,
        )  # dev: failed transfer
        if len(_response) > 0:
            assert convert(_response, bool)  # dev: failed transfer
    
        if i == FEE_INDEX:
            dx_w_fee = ERC20(input_coin).balanceOf(self) - dx_w_fee
    
        x: uint256 = xp[i] + dx_w_fee * rates[i] / PRECISION
        y: uint256 = self.get_y(i, j, x, xp)
    
        dy: uint256 = xp[j] - y - 1  # -1 just in case there were some rounding errors
        dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR
    
        # Convert all to real units
        dy = (dy - dy_fee) * PRECISION / rates[j]
        assert dy >= min_dy, "Exchange resulted in fewer coins than expected"
    
        dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR
        dy_admin_fee = dy_admin_fee * PRECISION / rates[j]
    
        # Change balances exactly in same way as we change actual ERC20 coin amounts
        self.balances[i] = old_balances[i] + dx_w_fee
        # When rounding errors happen, we undercharge admin fee in favor of LP
        self.balances[j] = old_balances[j] - dy - dy_admin_fee
    
        # "safeTransfer" which works for ERC20s which return bool or not
        _response = raw_call(
            self.coins[j],
            concat(
                method_id("transfer(address,uint256)"),
                convert(msg.sender, bytes32),
                convert(dy, bytes32),
            ),
            max_outsize=32,
        )  # dev: failed transfer
        if len(_response) > 0:
            assert convert(_response, bool)  # dev: failed transfer
    
        log TokenExchange(msg.sender, i, dx, j, dy)
    
    
    @external
    @nonreentrant('lock')
    def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]):
        total_supply: uint256 = self.token.totalSupply()
        amounts: uint256[N_COINS] = empty(uint256[N_COINS])
        fees: uint256[N_COINS] = empty(uint256[N_COINS])  # Fees are unused but we've got them historically in event
    
        for i in range(N_COINS):
            value: uint256 = self.balances[i] * _amount / total_supply
            assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected"
            self.balances[i] -= value
            amounts[i] = value
    
            # "safeTransfer" which works for ERC20s which return bool or not
            _response: Bytes[32] = raw_call(
                self.coins[i],
                concat(
                    method_id("transfer(address,uint256)"),
                    convert(msg.sender, bytes32),
                    convert(value, bytes32),
                ),
                max_outsize=32,
            )  # dev: failed transfer
            if len(_response) > 0:
                assert convert(_response, bool)  # dev: failed transfer
    
        self.token.burnFrom(msg.sender, _amount)  # dev: insufficient funds
    
        log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount)
    
    
    @external
    @nonreentrant('lock')
    def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256):
        assert not self.is_killed  # dev: is killed
    
        token_supply: uint256 = self.token.totalSupply()
        assert token_supply != 0  # dev: zero total supply
        _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
        _admin_fee: uint256 = self.admin_fee
        amp: uint256 = self._A()
    
        old_balances: uint256[N_COINS] = self.balances
        new_balances: uint256[N_COINS] = old_balances
        D0: uint256 = self.get_D_mem(old_balances, amp)
        for i in range(N_COINS):
            new_balances[i] -= amounts[i]
        D1: uint256 = self.get_D_mem(new_balances, amp)
        fees: uint256[N_COINS] = empty(uint256[N_COINS])
        for i in range(N_COINS):
            ideal_balance: uint256 = D1 * old_balances[i] / D0
            difference: uint256 = 0
            if ideal_balance > new_balances[i]:
                difference = ideal_balance - new_balances[i]
            else:
                difference = new_balances[i] - ideal_balance
            fees[i] = _fee * difference / FEE_DENOMINATOR
            self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR)
            new_balances[i] -= fees[i]
        D2: uint256 = self.get_D_mem(new_balances, amp)
    
        token_amount: uint256 = (D0 - D2) * token_supply / D0
        assert token_amount != 0  # dev: zero tokens burned
        token_amount += 1  # In case of rounding errors - make it unfavorable for the "attacker"
        assert token_amount <= max_burn_amount, "Slippage screwed you"
    
        self.token.burnFrom(msg.sender, token_amount)  # dev: insufficient funds
        for i in range(N_COINS):
            if amounts[i] != 0:
    
                # "safeTransfer" which works for ERC20s which return bool or not
                _response: Bytes[32] = raw_call(
                    self.coins[i],
                    concat(
                        method_id("transfer(address,uint256)"),
                        convert(msg.sender, bytes32),
                        convert(amounts[i], bytes32),
                    ),
                    max_outsize=32,
                )  # dev: failed transfer
                if len(_response) > 0:
                    assert convert(_response, bool)  # dev: failed transfer
    
        log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount)
    
    
    @view
    @internal
    def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256:
        """
        Calculate x[i] if one reduces D from being calculated for xp to D
    
        Done by solving quadratic equation iteratively.
        x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
        x_1**2 + b*x_1 = c
    
        x_1 = (x_1**2 + c) / (2*x_1 + b)
        """
        # x in the input is converted to the same price/precision
    
        assert i >= 0  # dev: i below zero
        assert i < N_COINS  # dev: i above N_COINS
    
        c: uint256 = D
        S_: uint256 = 0
        Ann: uint256 = A_ * N_COINS
    
        _x: uint256 = 0
        for _i in range(N_COINS):
            if _i != i:
                _x = xp[_i]
            else:
                continue
            S_ += _x
            c = c * D / (_x * N_COINS)
        c = c * D / (Ann * N_COINS)
        b: uint256 = S_ + D / Ann
        y_prev: uint256 = 0
        y: uint256 = D
        for _i in range(255):
            y_prev = y
            y = (y*y + c) / (2 * y + b - D)
            # Equality with the precision of 1
            if y > y_prev:
                if y - y_prev <= 1:
                    break
            else:
                if y_prev - y <= 1:
                    break
        return y
    
    
    @view
    @internal
    def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256):
        # First, need to calculate
        # * Get current D
        # * Solve Eqn against y_i for D - _token_amount
        amp: uint256 = self._A()
        _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
        precisions: uint256[N_COINS] = PRECISION_MUL
        total_supply: uint256 = self.token.totalSupply()
    
        xp: uint256[N_COINS] = self._xp()
    
        D0: uint256 = self.get_D(xp, amp)
        D1: uint256 = D0 - _token_amount * D0 / total_supply
        xp_reduced: uint256[N_COINS] = xp
    
        new_y: uint256 = self.get_y_D(amp, i, xp, D1)
        dy_0: uint256 = (xp[i] - new_y) / precisions[i]  # w/o fees
    
        for j in range(N_COINS):
            dx_expected: uint256 = 0
            if j == i:
                dx_expected = xp[j] * D1 / D0 - new_y
            else:
                dx_expected = xp[j] - xp[j] * D1 / D0
            xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR
    
        dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1)
        dy = (dy - 1) / precisions[i]  # Withdraw less to account for rounding errors
    
        return dy, dy_0 - dy
    
    
    @view
    @external
    def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256:
        return self._calc_withdraw_one_coin(_token_amount, i)[0]
    
    
    @external
    @nonreentrant('lock')
    def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256):
        """
        Remove _amount of liquidity all in a form of coin i
        """
        assert not self.is_killed  # dev: is killed
    
        dy: uint256 = 0
        dy_fee: uint256 = 0
        dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i)
        assert dy >= min_amount, "Not enough coins removed"
    
        self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR)
        self.token.burnFrom(msg.sender, _token_amount)  # dev: insufficient funds
    
        # "safeTransfer" which works for ERC20s which return bool or not
        _response: Bytes[32] = raw_call(
            self.coins[i],
            concat(
                method_id("transfer(address,uint256)"),
                convert(msg.sender, bytes32),
                convert(dy, bytes32),
            ),
            max_outsize=32,
        )  # dev: failed transfer
        if len(_response) > 0:
            assert convert(_response, bool)  # dev: failed transfer
    
        log RemoveLiquidityOne(msg.sender, _token_amount, dy)
    
    
    ### Admin functions ###
    @external
    def ramp_A(_future_A: uint256, _future_time: uint256):
        assert msg.sender == self.owner  # dev: only owner
        assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME
        assert _future_time >= block.timestamp + MIN_RAMP_TIME  # dev: insufficient time
    
        _initial_A: uint256 = self._A()
        assert (_future_A > 0) and (_future_A < MAX_A)
        assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * MAX_A_CHANGE)) or\
               ((_future_A < _initial_A) and (_future_A * MAX_A_CHANGE >= _initial_A))
        self.initial_A = _initial_A
        self.future_A = _future_A
        self.initial_A_time = block.timestamp
        self.future_A_time = _future_time
    
        log RampA(_initial_A, _future_A, block.timestamp, _future_time)
    
    
    @external
    def stop_ramp_A():
        assert msg.sender == self.owner  # dev: only owner
    
        current_A: uint256 = self._A()
        self.initial_A = current_A
        self.future_A = current_A
        self.initial_A_time = block.timestamp
        self.future_A_time = block.timestamp
        # now (block.timestamp < t1) is always False, so we return saved A
    
        log StopRampA(current_A, block.timestamp)
    
    
    @external
    def commit_new_fee(new_fee: uint256, new_admin_fee: uint256):
        assert msg.sender == self.owner  # dev: only owner
        assert self.admin_actions_deadline == 0  # dev: active action
        assert new_fee <= MAX_FEE  # dev: fee exceeds maximum
        assert new_admin_fee <= MAX_ADMIN_FEE  # dev: admin fee exceeds maximum
    
        _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
        self.admin_actions_deadline = _deadline
        self.future_fee = new_fee
        self.future_admin_fee = new_admin_fee
    
        log CommitNewFee(_deadline, new_fee, new_admin_fee)
    
    
    @external
    def apply_new_fee():
        assert msg.sender == self.owner  # 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
        _fee: uint256 = self.future_fee
        _admin_fee: uint256 = self.future_admin_fee
        self.fee = _fee
        self.admin_fee = _admin_fee
    
        log NewFee(_fee, _admin_fee)
    
    
    @external
    def revert_new_parameters():
        assert msg.sender == self.owner  # dev: only owner
    
        self.admin_actions_deadline = 0
    
    
    @external
    def commit_transfer_ownership(_owner: address):
        assert msg.sender == self.owner  # dev: only owner
        assert self.transfer_ownership_deadline == 0  # dev: active transfer
    
        _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
        self.transfer_ownership_deadline = _deadline
        self.future_owner = _owner
    
        log CommitNewAdmin(_deadline, _owner)
    
    
    @external
    def apply_transfer_ownership():
        assert msg.sender == self.owner  # dev: only owner
        assert block.timestamp >= self.transfer_ownership_deadline  # dev: insufficient time
        assert self.transfer_ownership_deadline != 0  # dev: no active transfer
    
        self.transfer_ownership_deadline = 0
        _owner: address = self.future_owner
        self.owner = _owner
    
        log NewAdmin(_owner)
    
    
    @external
    def revert_transfer_ownership():
        assert msg.sender == self.owner  # dev: only owner
    
        self.transfer_ownership_deadline = 0
    
    
    @view
    @external
    def admin_balances(i: uint256) -> uint256:
        return ERC20(self.coins[i]).balanceOf(self) - self.balances[i]
    
    
    @external
    def withdraw_admin_fees():
        assert msg.sender == self.owner  # dev: only owner
    
        for i in range(N_COINS):
            c: address = self.coins[i]
            value: uint256 = ERC20(c).balanceOf(self) - self.balances[i]
            if value > 0:
                # "safeTransfer" which works for ERC20s which return bool or not
                _response: Bytes[32] = raw_call(
                    c,
                    concat(
                        method_id("transfer(address,uint256)"),
                        convert(msg.sender, bytes32),
                        convert(value, bytes32),
                    ),
                    max_outsize=32,
                )  # dev: failed transfer
                if len(_response) > 0:
                    assert convert(_response, bool)  # dev: failed transfer
    
    
    @external
    def donate_admin_fees():
        assert msg.sender == self.owner  # dev: only owner
        for i in range(N_COINS):
            self.balances[i] = ERC20(self.coins[i]).balanceOf(self)
    
    
    @external
    def kill_me():
        assert msg.sender == self.owner  # dev: only owner
        assert self.kill_deadline > block.timestamp  # dev: deadline has passed
        self.is_killed = True
    
    
    @external
    def unkill_me():
        assert msg.sender == self.owner  # dev: only owner
        self.is_killed = False

    File 2 of 3: Vyper_contract
    # @version 0.2.4
    # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
    
    from vyper.interfaces import ERC20
    
    implements: ERC20
    
    interface Curve:
        def owner() -> address: view
    
    
    event Transfer:
        _from: indexed(address)
        _to: indexed(address)
        _value: uint256
    
    event Approval:
        _owner: indexed(address)
        _spender: indexed(address)
        _value: uint256
    
    
    name: public(String[64])
    symbol: public(String[32])
    decimals: public(uint256)
    
    # NOTE: By declaring `balanceOf` as public, vyper automatically generates a 'balanceOf()' getter
    #       method to allow access to account balances.
    #       The _KeyType will become a required parameter for the getter and it will return _ValueType.
    #       See: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings
    balanceOf: public(HashMap[address, uint256])
    allowances: HashMap[address, HashMap[address, uint256]]
    total_supply: uint256
    minter: address
    
    
    @external
    def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, _supply: uint256):
        init_supply: uint256 = _supply * 10 ** _decimals
        self.name = _name
        self.symbol = _symbol
        self.decimals = _decimals
        self.balanceOf[msg.sender] = init_supply
        self.total_supply = init_supply
        self.minter = msg.sender
        log Transfer(ZERO_ADDRESS, msg.sender, init_supply)
    
    
    @external
    def set_minter(_minter: address):
        assert msg.sender == self.minter
        self.minter = _minter
    
    
    @external
    def set_name(_name: String[64], _symbol: String[32]):
        assert Curve(self.minter).owner() == msg.sender
        self.name = _name
        self.symbol = _symbol
    
    
    @view
    @external
    def totalSupply() -> uint256:
        """
        @dev Total number of tokens in existence.
        """
        return self.total_supply
    
    
    @view
    @external
    def allowance(_owner : address, _spender : address) -> uint256:
        """
        @dev Function to check the amount of tokens that an owner allowed to a spender.
        @param _owner The address which owns the funds.
        @param _spender The address which will spend the funds.
        @return An uint256 specifying the amount of tokens still available for the spender.
        """
        return self.allowances[_owner][_spender]
    
    
    @external
    def transfer(_to : address, _value : uint256) -> bool:
        """
        @dev Transfer token for a specified address
        @param _to The address to transfer to.
        @param _value The amount to be transferred.
        """
        # NOTE: vyper does not allow underflows
        #       so the following subtraction would revert on insufficient balance
        self.balanceOf[msg.sender] -= _value
        self.balanceOf[_to] += _value
        log Transfer(msg.sender, _to, _value)
        return True
    
    
    @external
    def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
        """
         @dev Transfer tokens from one address to another.
         @param _from address The address which you want to send tokens from
         @param _to address The address which you want to transfer to
         @param _value uint256 the amount of tokens to be transferred
        """
        # NOTE: vyper does not allow underflows
        #       so the following subtraction would revert on insufficient balance
        self.balanceOf[_from] -= _value
        self.balanceOf[_to] += _value
        if msg.sender != self.minter:  # minter is allowed to transfer anything
            # NOTE: vyper does not allow underflows
            # so the following subtraction would revert on insufficient allowance
            self.allowances[_from][msg.sender] -= _value
        log Transfer(_from, _to, _value)
        return True
    
    
    @external
    def approve(_spender : address, _value : uint256) -> bool:
        """
        @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
             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
        @param _spender The address which will spend the funds.
        @param _value The amount of tokens to be spent.
        """
        assert _value == 0 or self.allowances[msg.sender][_spender] == 0
        self.allowances[msg.sender][_spender] = _value
        log Approval(msg.sender, _spender, _value)
        return True
    
    
    @external
    def mint(_to: address, _value: uint256) -> bool:
        """
        @dev Mint an amount of the token and assigns it to an account.
             This encapsulates the modification of balances such that the
             proper events are emitted.
        @param _to The account that will receive the created tokens.
        @param _value The amount that will be created.
        """
        assert msg.sender == self.minter
        assert _to != ZERO_ADDRESS
        self.total_supply += _value
        self.balanceOf[_to] += _value
        log Transfer(ZERO_ADDRESS, _to, _value)
        return True
    
    
    @external
    def burnFrom(_to: address, _value: uint256) -> bool:
        """
        @dev Burn an amount of the token from a given account.
        @param _to The account whose tokens will be burned.
        @param _value The amount that will be burned.
        """
        assert msg.sender == self.minter
        assert _to != ZERO_ADDRESS
    
        self.total_supply -= _value
        self.balanceOf[_to] -= _value
        log Transfer(_to, ZERO_ADDRESS, _value)
    
        return True

    File 3 of 3: TetherToken
    pragma solidity ^0.4.17;
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
            uint256 c = a * b;
            assert(c / a == b);
            return c;
        }
    
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            // assert(b > 0); // Solidity automatically throws when dividing by 0
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            return c;
        }
    
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            assert(b <= a);
            return a - b;
        }
    
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            assert(c >= a);
            return c;
        }
    }
    
    /**
     * @title Ownable
     * @dev The Ownable contract has an owner address, and provides basic authorization control
     * functions, this simplifies the implementation of "user permissions".
     */
    contract Ownable {
        address public owner;
    
        /**
          * @dev The Ownable constructor sets the original `owner` of the contract to the sender
          * account.
          */
        function Ownable() public {
            owner = msg.sender;
        }
    
        /**
          * @dev Throws if called by any account other than the owner.
          */
        modifier onlyOwner() {
            require(msg.sender == owner);
            _;
        }
    
        /**
        * @dev Allows the current owner to transfer control of the contract to a newOwner.
        * @param newOwner The address to transfer ownership to.
        */
        function transferOwnership(address newOwner) public onlyOwner {
            if (newOwner != address(0)) {
                owner = newOwner;
            }
        }
    
    }
    
    /**
     * @title ERC20Basic
     * @dev Simpler version of ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/20
     */
    contract ERC20Basic {
        uint public _totalSupply;
        function totalSupply() public constant returns (uint);
        function balanceOf(address who) public constant returns (uint);
        function transfer(address to, uint value) public;
        event Transfer(address indexed from, address indexed to, uint value);
    }
    
    /**
     * @title ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/20
     */
    contract ERC20 is ERC20Basic {
        function allowance(address owner, address spender) public constant returns (uint);
        function transferFrom(address from, address to, uint value) public;
        function approve(address spender, uint value) public;
        event Approval(address indexed owner, address indexed spender, uint value);
    }
    
    /**
     * @title Basic token
     * @dev Basic version of StandardToken, with no allowances.
     */
    contract BasicToken is Ownable, ERC20Basic {
        using SafeMath for uint;
    
        mapping(address => uint) public balances;
    
        // additional variables for use if transaction fees ever became necessary
        uint public basisPointsRate = 0;
        uint public maximumFee = 0;
    
        /**
        * @dev Fix for the ERC20 short address attack.
        */
        modifier onlyPayloadSize(uint size) {
            require(!(msg.data.length < size + 4));
            _;
        }
    
        /**
        * @dev transfer token for a specified address
        * @param _to The address to transfer to.
        * @param _value The amount to be transferred.
        */
        function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
            uint fee = (_value.mul(basisPointsRate)).div(10000);
            if (fee > maximumFee) {
                fee = maximumFee;
            }
            uint sendAmount = _value.sub(fee);
            balances[msg.sender] = balances[msg.sender].sub(_value);
            balances[_to] = balances[_to].add(sendAmount);
            if (fee > 0) {
                balances[owner] = balances[owner].add(fee);
                Transfer(msg.sender, owner, fee);
            }
            Transfer(msg.sender, _to, sendAmount);
        }
    
        /**
        * @dev Gets the balance of the specified address.
        * @param _owner The address to query the the balance of.
        * @return An uint representing the amount owned by the passed address.
        */
        function balanceOf(address _owner) public constant returns (uint balance) {
            return balances[_owner];
        }
    
    }
    
    /**
     * @title Standard ERC20 token
     *
     * @dev Implementation of the basic standard token.
     * @dev https://github.com/ethereum/EIPs/issues/20
     * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
     */
    contract StandardToken is BasicToken, ERC20 {
    
        mapping (address => mapping (address => uint)) public allowed;
    
        uint public constant MAX_UINT = 2**256 - 1;
    
        /**
        * @dev Transfer tokens from one address to another
        * @param _from address The address which you want to send tokens from
        * @param _to address The address which you want to transfer to
        * @param _value uint the amount of tokens to be transferred
        */
        function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
            var _allowance = allowed[_from][msg.sender];
    
            // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
            // if (_value > _allowance) throw;
    
            uint fee = (_value.mul(basisPointsRate)).div(10000);
            if (fee > maximumFee) {
                fee = maximumFee;
            }
            if (_allowance < MAX_UINT) {
                allowed[_from][msg.sender] = _allowance.sub(_value);
            }
            uint sendAmount = _value.sub(fee);
            balances[_from] = balances[_from].sub(_value);
            balances[_to] = balances[_to].add(sendAmount);
            if (fee > 0) {
                balances[owner] = balances[owner].add(fee);
                Transfer(_from, owner, fee);
            }
            Transfer(_from, _to, sendAmount);
        }
    
        /**
        * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
        * @param _spender The address which will spend the funds.
        * @param _value The amount of tokens to be spent.
        */
        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
    
            // To change the approve amount you first have to reduce the addresses`
            //  allowance to zero by calling `approve(_spender, 0)` if it is not
            //  already 0 to mitigate the race condition described here:
            //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
            require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
    
            allowed[msg.sender][_spender] = _value;
            Approval(msg.sender, _spender, _value);
        }
    
        /**
        * @dev Function to check the amount of tokens than an owner allowed to a spender.
        * @param _owner address The address which owns the funds.
        * @param _spender address The address which will spend the funds.
        * @return A uint specifying the amount of tokens still available for the spender.
        */
        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
            return allowed[_owner][_spender];
        }
    
    }
    
    
    /**
     * @title Pausable
     * @dev Base contract which allows children to implement an emergency stop mechanism.
     */
    contract Pausable is Ownable {
      event Pause();
      event Unpause();
    
      bool public paused = false;
    
    
      /**
       * @dev Modifier to make a function callable only when the contract is not paused.
       */
      modifier whenNotPaused() {
        require(!paused);
        _;
      }
    
      /**
       * @dev Modifier to make a function callable only when the contract is paused.
       */
      modifier whenPaused() {
        require(paused);
        _;
      }
    
      /**
       * @dev called by the owner to pause, triggers stopped state
       */
      function pause() onlyOwner whenNotPaused public {
        paused = true;
        Pause();
      }
    
      /**
       * @dev called by the owner to unpause, returns to normal state
       */
      function unpause() onlyOwner whenPaused public {
        paused = false;
        Unpause();
      }
    }
    
    contract BlackList is Ownable, BasicToken {
    
        /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
        function getBlackListStatus(address _maker) external constant returns (bool) {
            return isBlackListed[_maker];
        }
    
        function getOwner() external constant returns (address) {
            return owner;
        }
    
        mapping (address => bool) public isBlackListed;
        
        function addBlackList (address _evilUser) public onlyOwner {
            isBlackListed[_evilUser] = true;
            AddedBlackList(_evilUser);
        }
    
        function removeBlackList (address _clearedUser) public onlyOwner {
            isBlackListed[_clearedUser] = false;
            RemovedBlackList(_clearedUser);
        }
    
        function destroyBlackFunds (address _blackListedUser) public onlyOwner {
            require(isBlackListed[_blackListedUser]);
            uint dirtyFunds = balanceOf(_blackListedUser);
            balances[_blackListedUser] = 0;
            _totalSupply -= dirtyFunds;
            DestroyedBlackFunds(_blackListedUser, dirtyFunds);
        }
    
        event DestroyedBlackFunds(address _blackListedUser, uint _balance);
    
        event AddedBlackList(address _user);
    
        event RemovedBlackList(address _user);
    
    }
    
    contract UpgradedStandardToken is StandardToken{
        // those methods are called by the legacy contract
        // and they must ensure msg.sender to be the contract address
        function transferByLegacy(address from, address to, uint value) public;
        function transferFromByLegacy(address sender, address from, address spender, uint value) public;
        function approveByLegacy(address from, address spender, uint value) public;
    }
    
    contract TetherToken is Pausable, StandardToken, BlackList {
    
        string public name;
        string public symbol;
        uint public decimals;
        address public upgradedAddress;
        bool public deprecated;
    
        //  The contract can be initialized with a number of tokens
        //  All the tokens are deposited to the owner address
        //
        // @param _balance Initial supply of the contract
        // @param _name Token Name
        // @param _symbol Token symbol
        // @param _decimals Token decimals
        function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
            _totalSupply = _initialSupply;
            name = _name;
            symbol = _symbol;
            decimals = _decimals;
            balances[owner] = _initialSupply;
            deprecated = false;
        }
    
        // Forward ERC20 methods to upgraded contract if this one is deprecated
        function transfer(address _to, uint _value) public whenNotPaused {
            require(!isBlackListed[msg.sender]);
            if (deprecated) {
                return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
            } else {
                return super.transfer(_to, _value);
            }
        }
    
        // Forward ERC20 methods to upgraded contract if this one is deprecated
        function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
            require(!isBlackListed[_from]);
            if (deprecated) {
                return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
            } else {
                return super.transferFrom(_from, _to, _value);
            }
        }
    
        // Forward ERC20 methods to upgraded contract if this one is deprecated
        function balanceOf(address who) public constant returns (uint) {
            if (deprecated) {
                return UpgradedStandardToken(upgradedAddress).balanceOf(who);
            } else {
                return super.balanceOf(who);
            }
        }
    
        // Forward ERC20 methods to upgraded contract if this one is deprecated
        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
            if (deprecated) {
                return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
            } else {
                return super.approve(_spender, _value);
            }
        }
    
        // Forward ERC20 methods to upgraded contract if this one is deprecated
        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
            if (deprecated) {
                return StandardToken(upgradedAddress).allowance(_owner, _spender);
            } else {
                return super.allowance(_owner, _spender);
            }
        }
    
        // deprecate current contract in favour of a new one
        function deprecate(address _upgradedAddress) public onlyOwner {
            deprecated = true;
            upgradedAddress = _upgradedAddress;
            Deprecate(_upgradedAddress);
        }
    
        // deprecate current contract if favour of a new one
        function totalSupply() public constant returns (uint) {
            if (deprecated) {
                return StandardToken(upgradedAddress).totalSupply();
            } else {
                return _totalSupply;
            }
        }
    
        // Issue a new amount of tokens
        // these tokens are deposited into the owner address
        //
        // @param _amount Number of tokens to be issued
        function issue(uint amount) public onlyOwner {
            require(_totalSupply + amount > _totalSupply);
            require(balances[owner] + amount > balances[owner]);
    
            balances[owner] += amount;
            _totalSupply += amount;
            Issue(amount);
        }
    
        // Redeem tokens.
        // These tokens are withdrawn from the owner address
        // if the balance must be enough to cover the redeem
        // or the call will fail.
        // @param _amount Number of tokens to be issued
        function redeem(uint amount) public onlyOwner {
            require(_totalSupply >= amount);
            require(balances[owner] >= amount);
    
            _totalSupply -= amount;
            balances[owner] -= amount;
            Redeem(amount);
        }
    
        function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
            // Ensure transparency by hardcoding limit beyond which fees can never be added
            require(newBasisPoints < 20);
            require(newMaxFee < 50);
    
            basisPointsRate = newBasisPoints;
            maximumFee = newMaxFee.mul(10**decimals);
    
            Params(basisPointsRate, maximumFee);
        }
    
        // Called when new token are issued
        event Issue(uint amount);
    
        // Called when tokens are redeemed
        event Redeem(uint amount);
    
        // Called when contract is deprecated
        event Deprecate(address newAddress);
    
        // Called if contract ever adds fees
        event Params(uint feeBasisPoints, uint maxFee);
    }