ETH Price: $2,149.35 (+8.08%)

Transaction Decoder

Block:
12062944 at Mar-18-2021 01:30:18 PM +UTC
Transaction Fee:
0.0098428616 ETH $21.16
Gas Used:
48,785 Gas / 201.76 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x1caB9d6A...102537F5b
1.844790097014158709 Eth
Nonce: 15
1.834947235414158709 Eth
Nonce: 16
0.0098428616
(Ethermine)
1,020.581889802963495313 Eth1,020.591732664563495313 Eth0.0098428616

Execution Trace

Vyper_contract.add_liquidity( uamounts=[581000000000000000000, 0, 0, 0], min_mint_amount=528412303739270703785 )
  • Dai.transferFrom( src=0x1caB9d6A4a4f79d12bC7139B91d81ba102537F5b, dst=0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3, wad=581000000000000000000 )
    File 1 of 2: Vyper_contract
    # A "zap" to deposit/withdraw Curve contract without too many transactions
    # (c) Curve.Fi, 2020
    from vyper.interfaces import ERC20
    
    # External Contracts
    contract yERC20:
        def totalSupply() -> uint256: constant
        def allowance(_owner: address, _spender: address) -> uint256: constant
        def transfer(_to: address, _value: uint256) -> bool: modifying
        def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying
        def approve(_spender: address, _value: uint256) -> bool: modifying
        def name() -> string[64]: constant
        def symbol() -> string[32]: constant
        def decimals() -> uint256: constant
        def balanceOf(arg0: address) -> uint256: constant
        def deposit(depositAmount: uint256): modifying
        def withdraw(withdrawTokens: uint256): modifying
        def getPricePerFullShare() -> uint256: constant
    
    
    
    
    # Tether transfer-only ABI
    contract USDT:
        def transfer(_to: address, _value: uint256): modifying
        def transferFrom(_from: address, _to: address, _value: uint256): modifying
    
    
    contract Curve:
        def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256): modifying
        def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]): modifying
        def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256): modifying
        def balances(i: int128) -> uint256: constant
        def A() -> uint256: constant
        def fee() -> uint256: constant
        def owner() -> address: constant
    
    
    N_COINS: constant(int128) = 4
    TETHERED: constant(bool[N_COINS]) = [False, False, True, False]
    ZERO256: constant(uint256) = 0  # This hack is really bad XXX
    ZEROS: constant(uint256[N_COINS]) = [ZERO256, ZERO256, ZERO256, ZERO256]  # <- change
    LENDING_PRECISION: constant(uint256) = 10 ** 18
    PRECISION: constant(uint256) = 10 ** 18
    PRECISION_MUL: constant(uint256[N_COINS]) = [convert(1, uint256), convert(1000000000000, uint256), convert(1000000000000, uint256), convert(1, uint256)]
    FEE_DENOMINATOR: constant(uint256) = 10 ** 10
    FEE_IMPRECISION: constant(uint256) = 25 * 10 ** 8  # % of the fee
    
    coins: public(address[N_COINS])
    underlying_coins: public(address[N_COINS])
    curve: public(address)
    token: public(address)
    
    
    @public
    def __init__(_coins: address[N_COINS], _underlying_coins: address[N_COINS],
                 _curve: address, _token: address):
        self.coins = _coins
        self.underlying_coins = _underlying_coins
        self.curve = _curve
        self.token = _token
    
    
    @public
    @nonreentrant('lock')
    def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256):
        tethered: bool[N_COINS] = TETHERED
        amounts: uint256[N_COINS] = ZEROS
    
        for i in range(N_COINS):
            uamount: uint256 = uamounts[i]
    
            if uamount > 0:
                # Transfer the underlying coin from owner
                if tethered[i]:
                    USDT(self.underlying_coins[i]).transferFrom(
                        msg.sender, self, uamount)
                else:
                    assert_modifiable(ERC20(self.underlying_coins[i])\
                        .transferFrom(msg.sender, self, uamount))
    
                # Mint if needed
                ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount)
                yERC20(self.coins[i]).deposit(uamount)
                amounts[i] = yERC20(self.coins[i]).balanceOf(self)
                ERC20(self.coins[i]).approve(self.curve, amounts[i])
    
        Curve(self.curve).add_liquidity(amounts, min_mint_amount)
    
        tokens: uint256 = ERC20(self.token).balanceOf(self)
        assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens))
    
    
    @private
    def _send_all(_addr: address, min_uamounts: uint256[N_COINS], one: int128):
        tethered: bool[N_COINS] = TETHERED
    
        for i in range(N_COINS):
            if (one < 0) or (i == one):
                _coin: address = self.coins[i]
                _balance: uint256 = yERC20(_coin).balanceOf(self)
                if _balance == 0:  # Do nothing for 0 coins
                    continue
                yERC20(_coin).withdraw(_balance)
    
                _ucoin: address = self.underlying_coins[i]
                _uamount: uint256 = ERC20(_ucoin).balanceOf(self)
                assert _uamount >= min_uamounts[i], "Not enough coins withdrawn"
    
                if tethered[i]:
                    USDT(_ucoin).transfer(_addr, _uamount)
                else:
                    assert_modifiable(ERC20(_ucoin).transfer(_addr, _uamount))
    
    
    @public
    @nonreentrant('lock')
    def remove_liquidity(_amount: uint256, min_uamounts: uint256[N_COINS]):
        zeros: uint256[N_COINS] = ZEROS
    
        assert_modifiable(ERC20(self.token).transferFrom(msg.sender, self, _amount))
        Curve(self.curve).remove_liquidity(_amount, zeros)
    
        self._send_all(msg.sender, min_uamounts, -1)
    
    
    @public
    @nonreentrant('lock')
    def remove_liquidity_imbalance(uamounts: uint256[N_COINS], max_burn_amount: uint256):
        """
        Get max_burn_amount in, remove requested liquidity and transfer back what is left
        """
        tethered: bool[N_COINS] = TETHERED
        _token: address = self.token
    
        amounts: uint256[N_COINS] = uamounts
        for i in range(N_COINS):
            if amounts[i] > 0:
                rate: uint256 = yERC20(self.coins[i]).getPricePerFullShare()
                amounts[i] = amounts[i] * LENDING_PRECISION / rate
    
        # Transfrer max tokens in
        _tokens: uint256 = ERC20(_token).balanceOf(msg.sender)
        if _tokens > max_burn_amount:
            _tokens = max_burn_amount
        assert_modifiable(ERC20(_token).transferFrom(msg.sender, self, _tokens))
    
        Curve(self.curve).remove_liquidity_imbalance(amounts, max_burn_amount)
    
        # Transfer unused tokens back
        _tokens = ERC20(_token).balanceOf(self)
        assert_modifiable(ERC20(_token).transfer(msg.sender, _tokens))
    
        # Unwrap and transfer all the coins we've got
        self._send_all(msg.sender, ZEROS, -1)
    
    
    @private
    @constant
    def _xp_mem(rates: uint256[N_COINS], _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
    
    
    @private
    @constant
    def get_D(A: uint256, xp: uint256[N_COINS]) -> uint256:
        S: uint256 = 0
        for _x in xp:
            S += _x
        if S == 0:
            return 0
    
        Dprev: uint256 = 0
        D: uint256 = S
        Ann: uint256 = A * N_COINS
        for _i in range(255):
            D_P: uint256 = D
            for _x in xp:
                D_P = D_P * D / (_x * N_COINS + 1)  # +1 is to prevent /0
            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
    
    
    @private
    @constant
    def get_y(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) and (i < 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
    
    
    @private
    @constant
    def _calc_withdraw_one_coin(_token_amount: uint256, i: int128, rates: uint256[N_COINS]) -> uint256:
        # First, need to calculate
        # * Get current D
        # * Solve Eqn against y_i for D - _token_amount
        crv: address = self.curve
        A: uint256 = Curve(crv).A()
        fee: uint256 = Curve(crv).fee() * N_COINS / (4 * (N_COINS - 1))
        fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR  # Overcharge to account for imprecision
        precisions: uint256[N_COINS] = PRECISION_MUL
        total_supply: uint256 = ERC20(self.token).totalSupply()
    
        xp: uint256[N_COINS] = PRECISION_MUL
        S: uint256 = 0
        for j in range(N_COINS):
            xp[j] *= Curve(crv).balances(j)
            xp[j] = xp[j] * rates[j] / LENDING_PRECISION
            S += xp[j]
    
        D0: uint256 = self.get_D(A, xp)
        D1: uint256 = D0 - _token_amount * D0 / total_supply
        xp_reduced: uint256[N_COINS] = xp
    
        # xp = xp - fee * | xp * D1 / D0 - (xp - S * dD / D0 * (0, ... 1, ..0))|
        for j in range(N_COINS):
            dx_expected: uint256 = 0
            b_ideal: uint256 = xp[j] * D1 / D0
            b_expected: uint256 = xp[j]
            if j == i:
                b_expected -= S * (D0 - D1) / D0
            if b_ideal >= b_expected:
                dx_expected += (b_ideal - b_expected)
            else:
                dx_expected += (b_expected - b_ideal)
            xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR
    
        dy: uint256 = xp_reduced[i] - self.get_y(A, i, xp_reduced, D1)
        dy = dy / precisions[i]
    
        return dy
    
    
    @public
    @constant
    def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256:
        rates: uint256[N_COINS] = ZEROS
    
        for j in range(N_COINS):
            rates[j] = yERC20(self.coins[j]).getPricePerFullShare()
    
        return self._calc_withdraw_one_coin(_token_amount, i, rates)
    
    
    @public
    @nonreentrant('lock')
    def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_uamount: uint256, donate_dust: bool = False):
        """
        Remove _amount of liquidity all in a form of coin i
        """
        rates: uint256[N_COINS] = ZEROS
        _token: address = self.token
    
        for j in range(N_COINS):
            rates[j] = yERC20(self.coins[j]).getPricePerFullShare()
    
        dy: uint256 = self._calc_withdraw_one_coin(_token_amount, i, rates)
        assert dy >= min_uamount, "Not enough coins removed"
    
        assert_modifiable(
            ERC20(self.token).transferFrom(msg.sender, self, _token_amount))
    
        amounts: uint256[N_COINS] = ZEROS
        amounts[i] = dy * LENDING_PRECISION / rates[i]
        token_amount_before: uint256 = ERC20(_token).balanceOf(self)
        Curve(self.curve).remove_liquidity_imbalance(amounts, _token_amount)
    
        # Unwrap and transfer all the coins we've got
        self._send_all(msg.sender, ZEROS, i)
    
        if not donate_dust:
            # Transfer unused tokens back
            token_amount_after: uint256 = ERC20(_token).balanceOf(self)
            if token_amount_after > token_amount_before:
                assert_modifiable(ERC20(_token).transfer(
                    msg.sender, token_amount_after - token_amount_before)
                )
    
    
    @public
    @nonreentrant('lock')
    def withdraw_donated_dust():
        owner: address = Curve(self.curve).owner()
        assert msg.sender == owner
    
        _token: address = self.token
        assert_modifiable(
            ERC20(_token).transfer(owner, ERC20(_token).balanceOf(self)))

    File 2 of 2: Dai
    // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
    pragma solidity =0.5.12;
    
    ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    
    // You should have received a copy of the GNU General Public License
    // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    /* pragma solidity 0.5.12; */
    
    contract LibNote {
        event LogNote(
            bytes4   indexed  sig,
            address  indexed  usr,
            bytes32  indexed  arg1,
            bytes32  indexed  arg2,
            bytes             data
        ) anonymous;
    
        modifier note {
            _;
            assembly {
                // log an 'anonymous' event with a constant 6 words of calldata
                // and four indexed topics: selector, caller, arg1 and arg2
                let mark := msize                         // end of memory ensures zero
                mstore(0x40, add(mark, 288))              // update free memory pointer
                mstore(mark, 0x20)                        // bytes type data offset
                mstore(add(mark, 0x20), 224)              // bytes size (padded)
                calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                log4(mark, 288,                           // calldata
                     shl(224, shr(224, calldataload(0))), // msg.sig
                     caller,                              // msg.sender
                     calldataload(4),                     // arg1
                     calldataload(36)                     // arg2
                    )
            }
        }
    }
    
    ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
    // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
    
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Affero General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Affero General Public License for more details.
    //
    // You should have received a copy of the GNU Affero General Public License
    // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    /* pragma solidity 0.5.12; */
    
    /* import "./lib.sol"; */
    
    contract Dai is LibNote {
        // --- Auth ---
        mapping (address => uint) public wards;
        function rely(address guy) external note auth { wards[guy] = 1; }
        function deny(address guy) external note auth { wards[guy] = 0; }
        modifier auth {
            require(wards[msg.sender] == 1, "Dai/not-authorized");
            _;
        }
    
        // --- ERC20 Data ---
        string  public constant name     = "Dai Stablecoin";
        string  public constant symbol   = "DAI";
        string  public constant version  = "1";
        uint8   public constant decimals = 18;
        uint256 public totalSupply;
    
        mapping (address => uint)                      public balanceOf;
        mapping (address => mapping (address => uint)) public allowance;
        mapping (address => uint)                      public nonces;
    
        event Approval(address indexed src, address indexed guy, uint wad);
        event Transfer(address indexed src, address indexed dst, uint wad);
    
        // --- Math ---
        function add(uint x, uint y) internal pure returns (uint z) {
            require((z = x + y) >= x);
        }
        function sub(uint x, uint y) internal pure returns (uint z) {
            require((z = x - y) <= x);
        }
    
        // --- EIP712 niceties ---
        bytes32 public DOMAIN_SEPARATOR;
        // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
        bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
    
        constructor(uint256 chainId_) public {
            wards[msg.sender] = 1;
            DOMAIN_SEPARATOR = keccak256(abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name)),
                keccak256(bytes(version)),
                chainId_,
                address(this)
            ));
        }
    
        // --- Token ---
        function transfer(address dst, uint wad) external returns (bool) {
            return transferFrom(msg.sender, dst, wad);
        }
        function transferFrom(address src, address dst, uint wad)
            public returns (bool)
        {
            require(balanceOf[src] >= wad, "Dai/insufficient-balance");
            if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");
                allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
            }
            balanceOf[src] = sub(balanceOf[src], wad);
            balanceOf[dst] = add(balanceOf[dst], wad);
            emit Transfer(src, dst, wad);
            return true;
        }
        function mint(address usr, uint wad) external auth {
            balanceOf[usr] = add(balanceOf[usr], wad);
            totalSupply    = add(totalSupply, wad);
            emit Transfer(address(0), usr, wad);
        }
        function burn(address usr, uint wad) external {
            require(balanceOf[usr] >= wad, "Dai/insufficient-balance");
            if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
                require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");
                allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
            }
            balanceOf[usr] = sub(balanceOf[usr], wad);
            totalSupply    = sub(totalSupply, wad);
            emit Transfer(usr, address(0), wad);
        }
        function approve(address usr, uint wad) external returns (bool) {
            allowance[msg.sender][usr] = wad;
            emit Approval(msg.sender, usr, wad);
            return true;
        }
    
        // --- Alias ---
        function push(address usr, uint wad) external {
            transferFrom(msg.sender, usr, wad);
        }
        function pull(address usr, uint wad) external {
            transferFrom(usr, msg.sender, wad);
        }
        function move(address src, address dst, uint wad) external {
            transferFrom(src, dst, wad);
        }
    
        // --- Approve by signature ---
        function permit(address holder, address spender, uint256 nonce, uint256 expiry,
                        bool allowed, uint8 v, bytes32 r, bytes32 s) external
        {
            bytes32 digest =
                keccak256(abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR,
                    keccak256(abi.encode(PERMIT_TYPEHASH,
                                         holder,
                                         spender,
                                         nonce,
                                         expiry,
                                         allowed))
            ));
    
            require(holder != address(0), "Dai/invalid-address-0");
            require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
            require(expiry == 0 || now <= expiry, "Dai/permit-expired");
            require(nonce == nonces[holder]++, "Dai/invalid-nonce");
            uint wad = allowed ? uint(-1) : 0;
            allowance[holder][spender] = wad;
            emit Approval(holder, spender, wad);
        }
    }