ETH Price: $2,389.63 (-1.06%)

Transaction Decoder

Block:
13213601 at Sep-12-2021 10:15:40 PM +UTC
Transaction Fee:
0.006768261 ETH $16.17
Gas Used:
132,711 Gas / 51 Gwei

Emitted Events:

137 Vyper_contract.Transfer( _from=[Sender] 0xfe6f367305176f2bbb719b1c7e19fb11b01d9236, _to=0x0000000000000000000000000000000000000000, _value=25168636281008723021 )
138 LinkToken.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000f178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0, 0x000000000000000000000000fe6f367305176f2bbb719b1c7e19fb11b01d9236, 0000000000000000000000000000000000000000000000015d724d2394ce8a27 )
139 Vyper_contract.RemoveLiquidityOne( provider=[Sender] 0xfe6f367305176f2bbb719b1c7e19fb11b01d9236, token_amount=25168636281008723021, coin_amount=25180273281797622311, token_supply=4908778744988658961576693 )

Account State Difference:

  Address   Before After State Difference Code
0x51491077...4EcF986CA
(F2Pool Old)
2,554.944954623432782756 Eth2,554.945174530825622525 Eth0.000219907392839769
0xcee60cFa...3F5656F3a
0xF178C0b5...bA2C623c0
(Curve.fi: LINK/sLINK Pool)
0xfe6F3673...1b01d9236
0.972501516373626762 Eth
Nonce: 53
0.965733255373626762 Eth
Nonce: 54
0.006768261

Execution Trace

Vyper_contract.remove_liquidity_one_coin( _token_amount=25168636281008723021, i=0, _min_amount=25124987252635990804 ) => ( 25180273281797622311 )
  • Vyper_contract.STATICCALL( )
  • Vyper_contract.burnFrom( _to=0xfe6F367305176F2bBB719B1c7e19FB11b01d9236, _value=25168636281008723021 ) => ( True )
  • Null: 0x000...004.CALL( )
  • Null: 0x000...004.00000000( )
  • LinkToken.transfer( _to=0xfe6F367305176F2bBB719B1c7e19FB11b01d9236, _value=25180273281797622311 ) => ( success=True )
  • Null: 0x000...004.00000000( )
    File 1 of 3: Vyper_contract
    # @version 0.2.8
    """
    @title StableSwap
    @author Curve.Fi
    @license Copyright (c) Curve.Fi, 2020 - all rights reserved
    @notice Minimal pool implementation with no lending
    @dev Swaps between LINK and sLINK
    """
    
    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
        token_supply: 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
    
    
    # These constants must be set prior to compiling
    N_COINS: constant(int128) = 2
    
    # fixed constants
    FEE_DENOMINATOR: constant(uint256) = 10 ** 10
    PRECISION: constant(uint256) = 10 ** 18  # The precision to convert to
    
    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
    
    previous_balances: public(uint256[N_COINS])
    block_timestamp_last: public(uint256)
    
    owner: public(address)
    lp_token: public(address)
    
    A_PRECISION: constant(uint256) = 100
    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 * A_PRECISION
        self.future_A = _A * A_PRECISION
        self.fee = _fee
        self.admin_fee = _admin_fee
        self.owner = _owner
        self.kill_deadline = block.timestamp + KILL_DEADLINE_DT
        self.lp_token = _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() / A_PRECISION
    
    
    @view
    @external
    def A_precise() -> uint256:
        return self._A()
    
    
    @internal
    def _update():
        """
        Commits pre-change balances for the previous block
        Can be used to compare against current values for flash loan checks
        """
        if block.timestamp > self.block_timestamp_last:
            self.previous_balances = self.balances
            self.block_timestamp_last = block.timestamp
    
    
    @pure
    @internal
    def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256:
        """
        D invariant calculation in non-overflowing integer operations
        iteratively
    
        A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
    
        Converging solution:
        D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1)
        """
        S: uint256 = 0
        Dprev: uint256 = 0
    
        for _x in _xp:
            S += _x
        if S == 0:
            return 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 / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P)
            # Equality with the precision of 1
            if D > Dprev:
                if D - Dprev <= 1:
                    return D
            else:
                if Dprev - D <= 1:
                    return D
        # convergence typically occurs in 4 rounds or less, this should be unreachable!
        # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
        raise
    
    
    @view
    @internal
    def _get_D_mem(_balances: uint256[N_COINS], _amp: uint256) -> uint256:
        return self._get_D(_balances, _amp)
    
    
    @view
    @external
    def get_virtual_price() -> uint256:
        """
        @notice The current virtual price of the pool LP token
        @dev Useful for calculating profits
        @return LP token virtual price normalized to 1e18
        """
        D: uint256 = self._get_D(self.balances, 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 = ERC20(self.lp_token).totalSupply()
        return D * PRECISION / token_supply
    
    
    @view
    @external
    def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256:
        """
        @notice Calculate addition or reduction in token supply from a deposit or withdrawal
        @dev This calculation accounts for slippage, but not fees.
             Needed to prevent front-running, not for precise calculations!
        @param _amounts Amount of each coin being deposited
        @param _is_deposit set True for deposits, False for withdrawals
        @return Expected amount of LP tokens received
        """
        amp: uint256 = self._A()
        balances: uint256[N_COINS] = self.balances
        D0: uint256 = self._get_D_mem(balances, amp)
        for i in range(N_COINS):
            if _is_deposit:
                balances[i] += _amounts[i]
            else:
                balances[i] -= _amounts[i]
        D1: uint256 = self._get_D_mem(balances, amp)
        token_amount: uint256 = CurveToken(self.lp_token).totalSupply()
        diff: uint256 = 0
        if _is_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) -> uint256:
        """
        @notice Deposit coins into the pool
        @param _amounts List of amounts of coins to deposit
        @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
        @return Amount of LP tokens received by depositing
        """
        self._update()
        assert not self.is_killed  # dev: is killed
    
        amp: uint256 = self._A()
        old_balances: uint256[N_COINS] = self.balances
    
        # Initial invariant
        D0: uint256 = self._get_D_mem(old_balances, amp)
    
        lp_token: address = self.lp_token
        token_supply: uint256 = CurveToken(lp_token).totalSupply()
        new_balances: uint256[N_COINS] = old_balances
        for i in range(N_COINS):
            if token_supply == 0:
                assert _amounts[i] > 0  # dev: initial deposit requires all coins
            # balances store amounts of c-tokens
            new_balances[i] += _amounts[i]
    
        # 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
        fees: uint256[N_COINS] = empty(uint256[N_COINS])
        mint_amount: uint256 = 0
        if token_supply > 0:
            # Only account for fees if we are not the first to deposit
            fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
            admin_fee: uint256 = self.admin_fee
            for i in range(N_COINS):
                ideal_balance: uint256 = D1 * old_balances[i] / D0
                difference: uint256 = 0
                new_balance: uint256 = new_balances[i]
                if ideal_balance > new_balance:
                    difference = ideal_balance - new_balance
                else:
                    difference = new_balance - ideal_balance
                fees[i] = fee * difference / FEE_DENOMINATOR
                self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR)
                new_balances[i] -= fees[i]
            D2 = self._get_D_mem(new_balances, amp)
            mint_amount = token_supply * (D2 - D0) / D0
        else:
            self.balances = new_balances
            mint_amount = D1  # Take the dust if there was any
        assert mint_amount >= _min_mint_amount, "Slippage screwed you"
    
        # Take coins from the sender
        for i in range(N_COINS):
            if _amounts[i] > 0:
                # "safeTransferFrom" which works for ERC20s which return bool or not
                _response: Bytes[32] = raw_call(
                    self.coins[i],
                    concat(
                        method_id("transferFrom(address,address,uint256)"),
                        convert(msg.sender, bytes32),
                        convert(self, bytes32),
                        convert(_amounts[i], bytes32),
                    ),
                    max_outsize=32,
                )
                if len(_response) > 0:
                    assert convert(_response, bool)  # dev: failed transfer
                # end "safeTransferFrom"
    
        # Mint pool tokens
        CurveToken(lp_token).mint(msg.sender, mint_amount)
    
        log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount)
    
        return mint_amount
    
    
    @view
    @internal
    def _get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256:
        """
        Calculate x[j] if one makes x[i] = x
    
        Done by solving quadratic equation iteratively.
        x_1**2 + x_1 * (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 != 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
    
        A: uint256 = self._A()
        D: uint256 = self._get_D(_xp, A)
        Ann: uint256 = A * N_COINS
        c: uint256 = D
        S: uint256 = 0
        _x: uint256 = 0
        y_prev: 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 * A_PRECISION / (Ann * N_COINS)
        b: uint256 = S + D * A_PRECISION / Ann  # - D
        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:
                    return y
            else:
                if y_prev - y <= 1:
                    return y
        raise
    
    
    @view
    @external
    def get_dy(i: int128, j: int128, _dx: uint256) -> uint256:
        xp: uint256[N_COINS] = self.balances
    
        x: uint256 = xp[i] + _dx
        y: uint256 = self._get_y(i, j, x, xp)
        dy: uint256 = xp[j] - y - 1
        fee: uint256 = self.fee * dy / FEE_DENOMINATOR
        return dy - fee
    
    
    @external
    @nonreentrant('lock')
    def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256:
        """
        @notice Perform an exchange between two coins
        @dev Index values can be found via the `coins` public getter method
        @param i Index value for the coin to send
        @param j Index valie of the coin to recieve
        @param _dx Amount of `i` being exchanged
        @param _min_dy Minimum amount of `j` to receive
        @return Actual amount of `j` received
        """
        assert not self.is_killed  # dev: is killed
        self._update()
    
        old_balances: uint256[N_COINS] = self.balances
        xp: uint256[N_COINS] = old_balances
    
        x: uint256 = xp[i] + _dx
        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_fee
        assert dy >= _min_dy, "Exchange resulted in fewer coins than expected"
    
        dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR
    
        # Change balances exactly in same way as we change actual ERC20 coin amounts
        self.balances[i] = old_balances[i] + _dx
        # When rounding errors happen, we undercharge admin fee in favor of LP
        self.balances[j] = old_balances[j] - dy - dy_admin_fee
    
        _response: Bytes[32] = raw_call(
            self.coins[i],
            concat(
                method_id("transferFrom(address,address,uint256)"),
                convert(msg.sender, bytes32),
                convert(self, bytes32),
                convert(_dx, bytes32),
            ),
            max_outsize=32,
        )
        if len(_response) > 0:
            assert convert(_response, bool)
    
        _response = raw_call(
            self.coins[j],
            concat(
                method_id("transfer(address,uint256)"),
                convert(msg.sender, bytes32),
                convert(dy, bytes32),
            ),
            max_outsize=32,
        )
        if len(_response) > 0:
            assert convert(_response, bool)
    
        log TokenExchange(msg.sender, i, _dx, j, dy)
    
        return dy
    
    
    @external
    @nonreentrant('lock')
    def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_COINS]) -> uint256[N_COINS]:
        """
        @notice Withdraw coins from the pool
        @dev Withdrawal amounts are based on current deposit ratios
        @param _amount Quantity of LP tokens to burn in the withdrawal
        @param _min_amounts Minimum amounts of underlying coins to receive
        @return List of amounts of coins that were withdrawn
        """
        self._update()
        lp_token: address = self.lp_token
        total_supply: uint256 = CurveToken(lp_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):
            old_balance: uint256 = self.balances[i]
            value: uint256 = old_balance * _amount / total_supply
            assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected"
            self.balances[i] = old_balance - value
            amounts[i] = value
            _response: Bytes[32] = raw_call(
                self.coins[i],
                concat(
                    method_id("transfer(address,uint256)"),
                    convert(msg.sender, bytes32),
                    convert(value, bytes32),
                ),
                max_outsize=32,
            )
            if len(_response) > 0:
                assert convert(_response, bool)
    
        CurveToken(lp_token).burnFrom(msg.sender, _amount)  # dev: insufficient funds
    
        log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount)
    
        return amounts
    
    
    @external
    @nonreentrant('lock')
    def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uint256) -> uint256:
        """
        @notice Withdraw coins from the pool in an imbalanced amount
        @param _amounts List of amounts of underlying coins to withdraw
        @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal
        @return Actual amount of the LP token burned in the withdrawal
        """
        assert not self.is_killed  # dev: is killed
        self._update()
    
        amp: uint256 = self._A()
        old_balances: uint256[N_COINS] = self.balances
        D0: uint256 = self._get_D_mem(old_balances, amp)
        new_balances: uint256[N_COINS] = old_balances
        for i in range(N_COINS):
            new_balances[i] -= _amounts[i]
        D1: uint256 = self._get_D_mem(new_balances, amp)
    
        fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
        admin_fee: uint256 = self.admin_fee
        fees: uint256[N_COINS] = empty(uint256[N_COINS])
        for i in range(N_COINS):
            new_balance: uint256 = new_balances[i]
            ideal_balance: uint256 = D1 * old_balances[i] / D0
            difference: uint256 = 0
            if ideal_balance > new_balance:
                difference = ideal_balance - new_balance
            else:
                difference = new_balance - ideal_balance
            fees[i] = fee * difference / FEE_DENOMINATOR
            self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR)
            new_balances[i] = new_balance - fees[i]
        D2: uint256 = self._get_D_mem(new_balances, amp)
    
        lp_token: address = self.lp_token
        token_supply: uint256 = CurveToken(lp_token).totalSupply()
        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"
    
        CurveToken(lp_token).burnFrom(msg.sender, token_amount)  # dev: insufficient funds
        for i in range(N_COINS):
            if _amounts[i] != 0:
                _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,
                )
                if len(_response) > 0:
                    assert convert(_response, bool)
    
        log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount)
    
        return token_amount
    
    
    @pure
    @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 + x_1 * (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
    
        Ann: uint256 = A * N_COINS
        c: uint256 = D
        S: uint256 = 0
        _x: uint256 = 0
        y_prev: 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 * A_PRECISION / (Ann * N_COINS)
        b: uint256 = S + D * A_PRECISION / Ann
        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:
                    return y
            else:
                if y_prev - y <= 1:
                    return y
        raise
    
    
    @view
    @internal
    def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256, uint256):
        # First, need to calculate
        # * Get current D
        # * Solve Eqn against y_i for D - _token_amount
        amp: uint256 = self._A()
        xp: uint256[N_COINS] = self.balances
        D0: uint256 = self._get_D(xp, amp)
    
        total_supply: uint256 = CurveToken(self.lp_token).totalSupply()
        D1: uint256 = D0 - _token_amount * D0 / total_supply
        new_y: uint256 = self._get_y_D(amp, i, xp, D1)
        xp_reduced: uint256[N_COINS] = xp
        fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
        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 -= 1  # Withdraw less to account for rounding errors
        dy_0: uint256 = xp[i] - new_y  # w/o fees
    
        return dy, dy_0 - dy, total_supply
    
    
    @view
    @external
    def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256:
        """
        @notice Calculate the amount received when withdrawing a single coin
        @param _token_amount Amount of LP tokens to burn in the withdrawal
        @param i Index value of the coin to withdraw
        @return Amount of coin received
        """
        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) -> uint256:
        """
        @notice Withdraw a single coin from the pool
        @param _token_amount Amount of LP tokens to burn in the withdrawal
        @param i Index value of the coin to withdraw
        @param _min_amount Minimum amount of coin to receive
        @return Amount of coin received
        """
        assert not self.is_killed  # dev: is killed
        self._update()
    
        dy: uint256 = 0
        dy_fee: uint256 = 0
        total_supply: uint256 = 0
        dy, dy_fee, total_supply = 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)
        CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount)  # dev: insufficient funds
    
        _response: Bytes[32] = raw_call(
            self.coins[i],
            concat(
                method_id("transfer(address,uint256)"),
                convert(msg.sender, bytes32),
                convert(dy, bytes32),
            ),
            max_outsize=32,
        )
        if len(_response) > 0:
            assert convert(_response, bool)
    
        log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount)
    
        return 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()
        future_A_p: uint256 = _future_A * A_PRECISION
    
        assert _future_A > 0 and _future_A < MAX_A
        if future_A_p < initial_A:
            assert future_A_p * MAX_A_CHANGE >= initial_A
        else:
            assert future_A_p <= initial_A * MAX_A_CHANGE
    
        self.initial_A = initial_A
        self.future_A = future_A_p
        self.initial_A_time = block.timestamp
        self.future_A_time = _future_time
    
        log RampA(initial_A, future_A_p, 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):
            coin: address = self.coins[i]
            value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i]
            if value > 0:
                _response: Bytes[32] = raw_call(
                    coin,
                    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)
    
    
    @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.0
    """
    @title Curve LP Token
    @author Curve.Fi
    @notice Base implementation for an LP token provided for
            supplying liquidity to `StableSwap`
    @dev Follows the ERC-20 token standard as defined at
         https://eips.ethereum.org/EIPS/eip-20
    """
    
    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])
    
    balanceOf: public(HashMap[address, uint256])
    allowance: public(HashMap[address, HashMap[address, uint256]])
    totalSupply: public(uint256)
    
    minter: public(address)
    
    
    @external
    def __init__(_name: String[64], _symbol: String[32]):
        self.name = _name
        self.symbol = _symbol
        self.minter = msg.sender
        log Transfer(ZERO_ADDRESS, msg.sender, 0)
    
    
    @view
    @external
    def decimals() -> uint256:
        """
        @notice Get the number of decimals for this token
        @dev Implemented as a view method to reduce gas costs
        @return uint256 decimal places
        """
        return 18
    
    
    @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
        """
        self.balanceOf[_from] -= _value
        self.balanceOf[_to] += _value
    
        _allowance: uint256 = self.allowance[_from][msg.sender]
        if _allowance != MAX_UINT256:
            self.allowance[_from][msg.sender] = _allowance - _value
    
        log Transfer(_from, _to, _value)
        return True
    
    
    @external
    def approve(_spender : address, _value : uint256) -> bool:
        """
        @notice Approve the passed address to transfer the specified amount of
                tokens on behalf of msg.sender
        @dev Beware that changing an allowance via this method brings the risk
             that someone may use both the old and new allowance by unfortunate
             transaction ordering. This may be mitigated with the use of
             {increaseAllowance} and {decreaseAllowance}.
             https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        @param _spender The address which will transfer the funds
        @param _value The amount of tokens that may be transferred
        @return bool success
        """
        self.allowance[msg.sender][_spender] = _value
    
        log Approval(msg.sender, _spender, _value)
        return True
    
    
    @external
    def increaseAllowance(_spender: address, _added_value: uint256) -> bool:
        """
        @notice Increase the allowance granted to `_spender` by the caller
        @dev This is alternative to {approve} that can be used as a mitigation for
             the potential race condition
        @param _spender The address which will transfer the funds
        @param _added_value The amount of to increase the allowance
        @return bool success
        """
        allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value
        self.allowance[msg.sender][_spender] = allowance
    
        log Approval(msg.sender, _spender, allowance)
        return True
    
    
    @external
    def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool:
        """
        @notice Decrease the allowance granted to `_spender` by the caller
        @dev This is alternative to {approve} that can be used as a mitigation for
             the potential race condition
        @param _spender The address which will transfer the funds
        @param _subtracted_value The amount of to decrease the allowance
        @return bool success
        """
        allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value
        self.allowance[msg.sender][_spender] = allowance
    
        log Approval(msg.sender, _spender, allowance)
        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
    
        self.totalSupply += _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
    
        self.totalSupply -= _value
        self.balanceOf[_to] -= _value
    
        log Transfer(_to, ZERO_ADDRESS, _value)
        return True
    
    
    @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

    File 3 of 3: LinkToken
    pragma solidity ^0.4.16;
    
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
      function mul(uint256 a, uint256 b) internal constant returns (uint256) {
        uint256 c = a * b;
        assert(a == 0 || c / a == b);
        return c;
      }
    
      function div(uint256 a, uint256 b) internal constant 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 constant returns (uint256) {
        assert(b <= a);
        return a - b;
      }
    
      function add(uint256 a, uint256 b) internal constant returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
      }
    }
    
    
    /**
     * @title ERC20Basic
     * @dev Simpler version of ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/179
     */
    contract ERC20Basic {
      uint256 public totalSupply;
      function balanceOf(address who) constant returns (uint256);
      function transfer(address to, uint256 value) returns (bool);
      event Transfer(address indexed from, address indexed to, uint256 value);
    }
    /**
     * @title ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/20
     */
    contract ERC20 is ERC20Basic {
      function allowance(address owner, address spender) constant returns (uint256);
      function transferFrom(address from, address to, uint256 value) returns (bool);
      function approve(address spender, uint256 value) returns (bool);
      event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    
    contract ERC677 is ERC20 {
      function transferAndCall(address to, uint value, bytes data) returns (bool success);
    
      event Transfer(address indexed from, address indexed to, uint value, bytes data);
    }
    
    contract ERC677Receiver {
      function onTokenTransfer(address _sender, uint _value, bytes _data);
    }
    
    /**
     * @title Basic token
     * @dev Basic version of StandardToken, with no allowances. 
     */
    contract BasicToken is ERC20Basic {
      using SafeMath for uint256;
    
      mapping(address => uint256) balances;
    
      /**
      * @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, uint256 _value) returns (bool) {
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msg.sender, _to, _value);
        return true;
      }
    
      /**
      * @dev Gets the balance of the specified address.
      * @param _owner The address to query the the balance of. 
      * @return An uint256 representing the amount owned by the passed address.
      */
      function balanceOf(address _owner) constant returns (uint256 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 on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
     */
    contract StandardToken is ERC20, BasicToken {
    
      mapping (address => mapping (address => uint256)) allowed;
    
    
      /**
       * @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
       */
      function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
        var _allowance = allowed[_from][msg.sender];
    
        // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
        // require (_value <= _allowance);
    
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = _allowance.sub(_value);
        Transfer(_from, _to, _value);
        return true;
      }
    
      /**
       * @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, uint256 _value) returns (bool) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
      }
    
      /**
       * @dev Function to check the amount of tokens that 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 uint256 specifying the amount of tokens still available for the spender.
       */
      function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
      }
      
        /*
       * approve should be called when allowed[_spender] == 0. To increment
       * allowed value is better to use this function to avoid 2 calls (and wait until 
       * the first transaction is mined)
       * From MonolithDAO Token.sol
       */
      function increaseApproval (address _spender, uint _addedValue) 
        returns (bool success) {
        allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
      function decreaseApproval (address _spender, uint _subtractedValue) 
        returns (bool success) {
        uint oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue > oldValue) {
          allowed[msg.sender][_spender] = 0;
        } else {
          allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
    }
    
    contract ERC677Token is ERC677 {
    
      /**
      * @dev transfer token to a contract address with additional data if the recipient is a contact.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      * @param _data The extra data to be passed to the receiving contract.
      */
      function transferAndCall(address _to, uint _value, bytes _data)
        public
        returns (bool success)
      {
        super.transfer(_to, _value);
        Transfer(msg.sender, _to, _value, _data);
        if (isContract(_to)) {
          contractFallback(_to, _value, _data);
        }
        return true;
      }
    
    
      // PRIVATE
    
      function contractFallback(address _to, uint _value, bytes _data)
        private
      {
        ERC677Receiver receiver = ERC677Receiver(_to);
        receiver.onTokenTransfer(msg.sender, _value, _data);
      }
    
      function isContract(address _addr)
        private
        returns (bool hasCode)
      {
        uint length;
        assembly { length := extcodesize(_addr) }
        return length > 0;
      }
    
    }
    
    contract LinkToken is StandardToken, ERC677Token {
    
      uint public constant totalSupply = 10**27;
      string public constant name = 'ChainLink Token';
      uint8 public constant decimals = 18;
      string public constant symbol = 'LINK';
    
      function LinkToken()
        public
      {
        balances[msg.sender] = totalSupply;
      }
    
      /**
      * @dev transfer token to a specified address with additional data if the recipient is a contract.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      * @param _data The extra data to be passed to the receiving contract.
      */
      function transferAndCall(address _to, uint _value, bytes _data)
        public
        validRecipient(_to)
        returns (bool success)
      {
        return super.transferAndCall(_to, _value, _data);
      }
    
      /**
      * @dev transfer token to a specified address.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      */
      function transfer(address _to, uint _value)
        public
        validRecipient(_to)
        returns (bool success)
      {
        return super.transfer(_to, _value);
      }
    
      /**
       * @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, uint256 _value)
        public
        validRecipient(_spender)
        returns (bool)
      {
        return super.approve(_spender,  _value);
      }
    
      /**
       * @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
       */
      function transferFrom(address _from, address _to, uint256 _value)
        public
        validRecipient(_to)
        returns (bool)
      {
        return super.transferFrom(_from, _to, _value);
      }
    
    
      // MODIFIERS
    
      modifier validRecipient(address _recipient) {
        require(_recipient != address(0) && _recipient != address(this));
        _;
      }
    
    }