1. Anasayfa
  2. 100 Günde Solidity

🧵 #100GündeSolidity 096 : Stable Swap AMM

🧵 #100GündeSolidity 096 : Stable Swap AMM
Stable Swap AMM
0

Stable Swap AMM

Stable Swap AMM, merkezi olmayan borsalarda kullanılan bir otomatik piyasa yapıcıdır (AMM). Bu AMM, özellikle stabilcoin ticareti yapmak isteyen kullanıcılara hizmet vermektedir. Stable Swap AMM, kullanıcılara likidite havuzlarına erişim sağlayarak, düşük slippage (fiyat kayması) ile işlem yapmalarına olanak tanır.

Stable Swap AMM, özellikle volatil kripto para piyasalarında stabilcoinlerin fiyatlarındaki dalgalanmaları minimize etmek için kullanılır. Bu sayede kullanıcılar, özellikle işlem yaptıkları stabilcoinlerin fiyatlarının sabit kalması için tasarlanmış olan bir AMM kullanarak işlem yapmanın avantajlarından yararlanabilirler.

AMM (Automated Market Maker) nedir ve nasıl çalışır?

Automated Market Maker (AMM), merkezi olmayan borsalarda kullanılan bir piyasa yapıcıdır. AMM’ler, borsadaki likiditeyi sağlamak için kullanıcıların yatırdığı varlıkların belirli bir oranda karşılığı olan likidite havuzları oluştururlar.

AMM’ler, kullanıcıların yatırdığı varlıkların değerlerine göre otomatik olarak belirledikleri bir formül kullanarak alım-satım fiyatlarını belirlerler. Bu formül, genellikle Constant Product (Sabit Ürün) formülü olarak adlandırılır ve iki varlığın havuzdaki oranını sabit tutar. Bu nedenle, bir varlığın fiyatı yükseldiğinde, diğer varlığın miktarı azalır ve böylece alım-satım fiyatı değişir.

AMM’ler, tüm işlemleri otomatik olarak gerçekleştirdiği için, işlem ücretleri de düşüktür ve likidite havuzlarına katkıda bulunan kullanıcılara likidite madenciliği veya diğer adlar altında ödüller sunarlar. AMM’ler, merkezi borsalara kıyasla daha şeffaf, daha güvenli ve kullanıcı dostudur.

Stable Swap AMM’nin avantajları & dezavantajları nelerdir?

Stable Swap AMM’nin avantajları şunlardır:

  1. Likidite Havuzları: Stable Swap AMM, kullanıcıların likidite havuzlarına katılmalarına izin verir ve bu sayede düşük slippage (fiyat kayması) ile işlem yapmalarını sağlar.
  2. Sabit Fiyat: Stable Swap AMM, stabilcoinlerin fiyatlarının sabit kalmasını sağlar. Bu sayede kullanıcılar, fiyat dalgalanmalarından minimum seviyede etkilenirler.
  3. Düşük Ücretler: AMM’ler, işlemleri otomatik olarak gerçekleştirdiği için işlem ücretleri de merkezi borsalara göre daha düşüktür.
  4. Şeffaflık: AMM’ler, işlemlerin tümü için blok zincirinde bir kayıt tutarlar ve bu nedenle daha şeffaf ve güvenlidirler.

Stable Swap AMM’nin dezavantajları şunlardır:

  1. Yüksek Karmaşıklık: AMM’lerin oluşturulması ve işletilmesi oldukça karmaşıktır. Bu nedenle, kullanıcıların işlem yapmak için ekstra araştırma yapmaları gerekebilir.
  2. İşlem Yavaşlığı: AMM’lerin işlem yapma hızı, merkezi borsalara kıyasla daha yavaş olabilir.
  3. İşlem Limitleri: AMM’ler, likidite havuzlarında yeterli likidite olmadığı durumlarda, kullanıcıların işlem limitlerini düşürmek zorunda kalabilirler.
  4. Fiyat Kaymaları: AMM’ler, likidite havuzlarında yeterli likidite olmadığı durumlarda, fiyat kaymalarına neden olabilirler. Bu nedenle, kullanıcılar düşük likidite dönemlerinde fiyat kaymalarına dikkat etmelidirler.

Hangi projeler Stable Swap AMM kullanıyor?

Stable Swap AMM, merkezi olmayan borsalarda sıklıkla kullanılan bir otomatik piyasa yapıcıdır. Birçok projede kullanılmaktadır. Örneğin:

  1. Curve Finance: Curve, stabilcoinler arasında işlem yapmak için Stable Swap AMM kullanır ve özellikle DAI, USDC, USDT ve TUSD için likidite havuzları sağlar.
  2. Saddle Finance: Saddle, DEX’lerde stabilcoinler arasında işlem yapmak için Stable Swap AMM kullanır. Yatırımcılar, DAI, USDC, USDT, BUSD, FRAX, alUSD ve sUSD arasında işlem yapabilirler.
  3. Swerve Finance: Swerve, DEX’lerdeki likidite havuzlarından daha fazla ödeme yapmak isteyen kullanıcılar için tasarlanmıştır. USDT, USDC, TUSD ve DAI arasında işlem yapmak için Stable Swap AMM kullanır.
  4. Mstable: Mstable, farklı stabilcoinleri arasında işlem yapmak için Stable Swap AMM kullanır. Kullanıcılar, DAI, USDC, USDT ve BUSD arasında işlem yapabilirler.

Bu projeler, stabilcoin ticareti yapmak isteyen kullanıcılar için Stable Swap AMM kullanarak işlem yapmalarını sağlar ve likidite havuzlarına katılım sağlayarak likidite madenciliği yapmalarına olanak tanır.

Stable Swap AMM’nin geleceği hakkında ne düşünülüyor?

Stable Swap AMM, merkezi olmayan finans (DeFi) alanında oldukça popüler hale geldi ve gelecekte de büyük bir potansiyele sahip olduğu düşünülüyor. İşte Stable Swap AMM’nin geleceği hakkında bazı düşünceler:

  1. Artan Kullanım: Stable Swap AMM, DeFi ekosisteminde kullanımı giderek artan bir teknolojidir. Daha fazla projenin ve borsanın Stable Swap AMM kullanmaya başlaması bekleniyor.
  2. Daha Fazla Varlık: Stable Swap AMM, stabilcoinler için kullanıldığı gibi, gelecekte diğer varlık türleri için de kullanılabilir hale gelebilir. Bu, daha fazla likidite havuzu oluşturarak kullanıcılara daha fazla seçenek sunacaktır.
  3. Gelişmiş Özellikler: Stable Swap AMM, birçok farklı parametreyi değiştirebilen bir teknolojidir. Gelecekte, bu özelliklerin daha da geliştirilmesi ve iyileştirilmesi bekleniyor.
  4. Yeni Uygulamalar: Stable Swap AMM, DeFi’de kullanılan birçok uygulamanın temelini oluşturuyor. Bu teknolojinin kullanımı arttıkça, yeni ve yaratıcı uygulamaların ortaya çıkması muhtemeldir.
  5. Daha Fazla Rekabet: Stable Swap AMM’nin popüler hale gelmesiyle birlikte, benzer teknolojilere sahip diğer AMM’lerin ortaya çıkması bekleniyor. Bu, daha fazla rekabetin ortaya çıkmasına ve teknolojinin daha da gelişmesine yol açabilir.

Sonuç olarak, Stable Swap AMM’nin geleceği oldukça parlak görünüyor. Bu teknoloji, merkezi olmayan finans ekosisteminin bir parçası haline gelmiş durumda ve gelecekte de bu alanın gelişimine katkıda bulunmaya devam edecek gibi görünüyor.

Akıllı Sözleşme Analizi

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

/*
Invariant - price of trade and amount of liquidity are determined by this equation

An^n sum(x_i) + D = ADn^n + D^(n + 1) / (n^n prod(x_i))

Topics
0. Newton's method x_(n + 1) = x_n - f(x_n) / f'(x_n)
1. Invariant
2. Swap
   - Calculate Y
   - Calculate D
3. Get virtual price
4. Add liquidity
   - Imbalance fee
5. Remove liquidity
6. Remove liquidity one token
   - Calculate withdraw one token
   - getYD
TODO: test?
*/

library Math {
    function abs(uint x, uint y) internal pure returns (uint) {
        return x >= y ? x - y : y - x;
    }
}

contract StableSwap {
    // Number of tokens
    uint private constant N = 3;
    // Amplification coefficient multiplied by N^(N - 1)
    // Higher value makes the curve more flat
    // Lower value makes the curve more like constant product AMM
    uint private constant A = 1000 * (N ** (N - 1));
    // 0.03%
    uint private constant SWAP_FEE = 300;
    // Liquidity fee is derived from 2 constraints
    // 1. Fee is 0 for adding / removing liquidity that results in a balanced pool
    // 2. Swapping in a balanced pool is like adding and then removing liquidity
    //    from a balanced pool
    // swap fee = add liquidity fee + remove liquidity fee
    uint private constant LIQUIDITY_FEE = (SWAP_FEE * N) / (4 * (N - 1));
    uint private constant FEE_DENOMINATOR = 1e6;

    address[N] public tokens;
    // Normalize each token to 18 decimals
    // Example - DAI (18 decimals), USDC (6 decimals), USDT (6 decimals)
    uint[N] private multipliers = [1, 1e12, 1e12];
    uint[N] public balances;

    // 1 share = 1e18, 18 decimals
    uint private constant DECIMALS = 18;
    uint public totalSupply;
    mapping(address => uint) public balanceOf;

    function _mint(address _to, uint _amount) private {
        balanceOf[_to] += _amount;
        totalSupply += _amount;
    }

    function _burn(address _from, uint _amount) private {
        balanceOf[_from] -= _amount;
        totalSupply -= _amount;
    }

    // Return precision-adjusted balances, adjusted to 18 decimals
    function _xp() private view returns (uint[N] memory xp) {
        for (uint i; i < N; ++i) {
            xp[i] = balances[i] * multipliers[i];
        }
    }

    /**
     * @notice Calculate D, sum of balances in a perfectly balanced pool
     * If balances of x_0, x_1, ... x_(n-1) then sum(x_i) = D
     * @param xp Precision-adjusted balances
     * @return D
     */
    function _getD(uint[N] memory xp) private pure returns (uint) {
        /*
        Newton's method to compute D
        -----------------------------
        f(D) = ADn^n + D^(n + 1) / (n^n prod(x_i)) - An^n sum(x_i) - D 
        f'(D) = An^n + (n + 1) D^n / (n^n prod(x_i)) - 1

                     (as + np)D_n
        D_(n+1) = -----------------------
                  (a - 1)D_n + (n + 1)p

        a = An^n
        s = sum(x_i)
        p = (D_n)^(n + 1) / (n^n prod(x_i))
        */
        uint a = A * N; // An^n

        uint s; // x_0 + x_1 + ... + x_(n-1)
        for (uint i; i < N; ++i) {
            s += xp[i];
        }

        // Newton's method
        // Initial guess, d <= s
        uint d = s;
        uint d_prev;
        for (uint i; i < 255; ++i) {
            // p = D^(n + 1) / (n^n * x_0 * ... * x_(n-1))
            uint p = d;
            for (uint j; j < N; ++j) {
                p = (p * d) / (N * xp[j]);
            }
            d_prev = d;
            d = ((a * s + N * p) * d) / ((a - 1) * d + (N + 1) * p);

            if (Math.abs(d, d_prev) <= 1) {
                return d;
            }
        }
        revert("D didn't converge");
    }

    /**
     * @notice Calculate the new balance of token j given the new balance of token i
     * @param i Index of token in
     * @param j Index of token out
     * @param x New balance of token i
     * @param xp Current precision-adjusted balances
     */
    function _getY(
        uint i,
        uint j,
        uint x,
        uint[N] memory xp
    ) private pure returns (uint) {
        /*
        Newton's method to compute y
        -----------------------------
        y = x_j

        f(y) = y^2 + y(b - D) - c

                    y_n^2 + c
        y_(n+1) = --------------
                   2y_n + b - D

        where
        s = sum(x_k), k != j
        p = prod(x_k), k != j
        b = s + D / (An^n)
        c = D^(n + 1) / (n^n * p * An^n)
        */
        uint a = A * N;
        uint d = _getD(xp);
        uint s;
        uint c = d;

        uint _x;
        for (uint k; k < N; ++k) {
            if (k == i) {
                _x = x;
            } else if (k == j) {
                continue;
            } else {
                _x = xp[k];
            }

            s += _x;
            c = (c * d) / (N * _x);
        }
        c = (c * d) / (N * a);
        uint b = s + d / a;

        // Newton's method
        uint y_prev;
        // Initial guess, y <= d
        uint y = d;
        for (uint _i; _i < 255; ++_i) {
            y_prev = y;
            y = (y * y + c) / (2 * y + b - d);
            if (Math.abs(y, y_prev) <= 1) {
                return y;
            }
        }
        revert("y didn't converge");
    }

    /**
     * @notice Calculate the new balance of token i given precision-adjusted
     * balances xp and liquidity d
     * @dev Equation is calculate y is same as _getY
     * @param i Index of token to calculate the new balance
     * @param xp Precision-adjusted balances
     * @param d Liquidity d
     * @return New balance of token i
     */
    function _getYD(uint i, uint[N] memory xp, uint d) private pure returns (uint) {
        uint a = A * N;
        uint s;
        uint c = d;

        uint _x;
        for (uint k; k < N; ++k) {
            if (k != i) {
                _x = xp[k];
            } else {
                continue;
            }

            s += _x;
            c = (c * d) / (N * _x);
        }
        c = (c * d) / (N * a);
        uint b = s + d / a;

        // Newton's method
        uint y_prev;
        // Initial guess, y <= d
        uint y = d;
        for (uint _i; _i < 255; ++_i) {
            y_prev = y;
            y = (y * y + c) / (2 * y + b - d);
            if (Math.abs(y, y_prev) <= 1) {
                return y;
            }
        }
        revert("y didn't converge");
    }

    // Estimate value of 1 share
    // How many tokens is one share worth?
    function getVirtualPrice() external view returns (uint) {
        uint d = _getD(_xp());
        uint _totalSupply = totalSupply;
        if (_totalSupply > 0) {
            return (d * 10 ** DECIMALS) / _totalSupply;
        }
        return 0;
    }

    /**
     * @notice Swap dx amount of token i for token j
     * @param i Index of token in
     * @param j Index of token out
     * @param dx Token in amount
     * @param minDy Minimum token out
     */
    function swap(uint i, uint j, uint dx, uint minDy) external returns (uint dy) {
        require(i != j, "i = j");

        IERC20(tokens[i]).transferFrom(msg.sender, address(this), dx);

        // Calculate dy
        uint[N] memory xp = _xp();
        uint x = xp[i] + dx * multipliers[i];

        uint y0 = xp[j];
        uint y1 = _getY(i, j, x, xp);
        // y0 must be >= y1, since x has increased
        // -1 to round down
        dy = (y0 - y1 - 1) / multipliers[j];

        // Subtract fee from dy
        uint fee = (dy * SWAP_FEE) / FEE_DENOMINATOR;
        dy -= fee;
        require(dy >= minDy, "dy < min");

        balances[i] += dx;
        balances[j] -= dy;

        IERC20(tokens[j]).transfer(msg.sender, dy);
    }

    function addLiquidity(
        uint[N] calldata amounts,
        uint minShares
    ) external returns (uint shares) {
        // calculate current liquidity d0
        uint _totalSupply = totalSupply;
        uint d0;
        uint[N] memory old_xs = _xp();
        if (_totalSupply > 0) {
            d0 = _getD(old_xs);
        }

        // Transfer tokens in
        uint[N] memory new_xs;
        for (uint i; i < N; ++i) {
            uint amount = amounts[i];
            if (amount > 0) {
                IERC20(tokens[i]).transferFrom(msg.sender, address(this), amount);
                new_xs[i] = old_xs[i] + amount * multipliers[i];
            } else {
                new_xs[i] = old_xs[i];
            }
        }

        // Calculate new liquidity d1
        uint d1 = _getD(new_xs);
        require(d1 > d0, "liquidity didn't increase");

        // Reccalcuate D accounting for fee on imbalance
        uint d2;
        if (_totalSupply > 0) {
            for (uint i; i < N; ++i) {
                // TODO: why old_xs[i] * d1 / d0? why not d1 / N?
                uint idealBalance = (old_xs[i] * d1) / d0;
                uint diff = Math.abs(new_xs[i], idealBalance);
                new_xs[i] -= (LIQUIDITY_FEE * diff) / FEE_DENOMINATOR;
            }

            d2 = _getD(new_xs);
        } else {
            d2 = d1;
        }

        // Update balances
        for (uint i; i < N; ++i) {
            balances[i] += amounts[i];
        }

        // Shares to mint = (d2 - d0) / d0 * total supply
        // d1 >= d2 >= d0
        if (_totalSupply > 0) {
            shares = ((d2 - d0) * _totalSupply) / d0;
        } else {
            shares = d2;
        }
        require(shares >= minShares, "shares < min");
        _mint(msg.sender, shares);
    }

    function removeLiquidity(
        uint shares,
        uint[N] calldata minAmountsOut
    ) external returns (uint[N] memory amountsOut) {
        uint _totalSupply = totalSupply;

        for (uint i; i < N; ++i) {
            uint amountOut = (balances[i] * shares) / _totalSupply;
            require(amountOut >= minAmountsOut[i], "out < min");

            balances[i] -= amountOut;
            amountsOut[i] = amountOut;

            IERC20(tokens[i]).transfer(msg.sender, amountOut);
        }

        _burn(msg.sender, shares);
    }

    /**
     * @notice Calculate amount of token i to receive for shares
     * @param shares Shares to burn
     * @param i Index of token to withdraw
     * @return dy Amount of token i to receive
     *         fee Fee for withdraw. Fee already included in dy
     */
    function _calcWithdrawOneToken(
        uint shares,
        uint i
    ) private view returns (uint dy, uint fee) {
        uint _totalSupply = totalSupply;
        uint[N] memory xp = _xp();

        // Calculate d0 and d1
        uint d0 = _getD(xp);
        uint d1 = d0 - (d0 * shares) / _totalSupply;

        // Calculate reduction in y if D = d1
        uint y0 = _getYD(i, xp, d1);
        // d1 <= d0 so y must be <= xp[i]
        uint dy0 = (xp[i] - y0) / multipliers[i];

        // Calculate imbalance fee, update xp with fees
        uint dx;
        for (uint j; j < N; ++j) {
            if (j == i) {
                dx = (xp[j] * d1) / d0 - y0;
            } else {
                // d1 / d0 <= 1
                dx = xp[j] - (xp[j] * d1) / d0;
            }
            xp[j] -= (LIQUIDITY_FEE * dx) / FEE_DENOMINATOR;
        }

        // Recalculate y with xp including imbalance fees
        uint y1 = _getYD(i, xp, d1);
        // - 1 to round down
        dy = (xp[i] - y1 - 1) / multipliers[i];
        fee = dy0 - dy;
    }

    function calcWithdrawOneToken(
        uint shares,
        uint i
    ) external view returns (uint dy, uint fee) {
        return _calcWithdrawOneToken(shares, i);
    }

    /**
     * @notice Withdraw liquidity in token i
     * @param shares Shares to burn
     * @param i Token to withdraw
     * @param minAmountOut Minimum amount of token i that must be withdrawn
     */
    function removeLiquidityOneToken(
        uint shares,
        uint i,
        uint minAmountOut
    ) external returns (uint amountOut) {
        (amountOut, ) = _calcWithdrawOneToken(shares, i);
        require(amountOut >= minAmountOut, "out < min");

        balances[i] -= amountOut;
        _burn(msg.sender, shares);

        IERC20(tokens[i]).transfer(msg.sender, amountOut);
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint);

    function balanceOf(address account) external view returns (uint);

    function transfer(address recipient, uint amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint amount);
    event Approval(address indexed owner, address indexed spender, uint amount);
}

Bu akıllı sözleşme bir StableSwap AMM (otomatik piyasa yapıcısı) contract örneğidir.

Sözleşme, N token çifti arasında sabit oranlarla takas yapılabilen ve likidite sağlayıcılarına sağlam bir getiri sağlayan bir merkezi olmayan borsa protokolüdür. İki tür işlem yapılabilir: token takası ve likidite sağlama.

Sözleşme sabit bir değere sahip olan “Invariant”ı sağlar. Bu “Invariant”, N tokenin likidite havuzunda tutulduğu zaman, takas işlemlerinde işlem ücretleri ve likidite miktarı belirleyen bir denklemdir.

Sözleşme ayrıca aşağıdaki fonksiyonları da içerir:

  • Swap: Kullanıcılara N token çifti arasında takas yapma işlemi yapar. İşlem ücreti alınır ve likidite havuzuna eklenir.
  • Get virtual price: Her bir token için sanal fiyatı hesaplar.
  • Add liquidity: Kullanıcılar, bir veya daha fazla tokeni havuza ekleyerek likidite sağlayabilirler.
  • Remove liquidity: Likidite sağlayıcısı, havuzdan tüm likiditesini geri çekebilir.
  • Remove liquidity one token: Likidite sağlayıcısı, yalnızca belirli bir tokeni havuzdan geri çekebilir.

Sözleşme değişkenleri, sabitler, fonksiyonlar ve hesaplama adımları yorumlanabilir.

İşlevsel Açıdan İnceleme:

Sözleşme, bir iş ilişkisini düzenlemek amacıyla hazırlanmış bir belgedir. İşlevsel açıdan incelendiğinde, sözleşmenin amacı, taraflar arasındaki hak ve yükümlülükleri belirlemektir. Bu açıdan, sözleşmenin işlevi, tarafların karşılıklı olarak anlaşmaya varacakları hükümleri belirlemek ve bu hükümlere uygun olarak hareket etmek olacaktır.

Süreç Açısından İnceleme:

Sözleşme, bir iş ilişkisini düzenlemek için bir sürecin parçasıdır. Süreç açısından incelendiğinde, sözleşmenin hazırlanması, tarafların bir araya gelmesi, müzakerelerin yapılması, karşılıklı olarak hükümlerin belirlenmesi, belgenin imzalanması ve ardından yürürlüğe girmesi aşamalarından oluşur. Bu süreç, sözleşmenin doğru ve adil bir şekilde hazırlanmasını ve uygulanmasını sağlar.

Güvenlik Açısından İnceleme:

Sözleşmenin güvenlik açısından incelenmesi, sözleşmenin tarafların haklarını korumaya yönelik hükümler içermesi gerektiğini ifade eder. Sözleşmenin güvenlik açısından önemli olan unsurları, tarafların sorumlulukları, işlem yapılacak mal veya hizmetin özellikleri, ödeme koşulları ve uyuşmazlık çözüm mekanizmalarıdır. Bu unsurların belirlenmesi, tarafların işbirliği içinde çalışmasını ve uyuşmazlıkların çözülmesini kolaylaştırır.

Genel olarak, sözleşme işlevsel, süreçsel ve güvenlik açılarından incelendiğinde, iş ilişkilerini düzenleyen ve taraflar arasındaki hak ve yükümlülükleri belirleyen önemli bir belgedir. Tarafların sözleşme hükümlerine uygun hareket etmeleri, işbirliği içinde çalışmaları ve uyuşmazlıkların çözümü için adil ve etkili mekanizmalar belirlemeleri önemlidir.

solidity101 - Solidity, 2015 yılında Christian Reitwiessner liderliğinde piyasaya sürülen, büyük harf kullanımına göre ikinci en büyük kripto para piyasası olan Ethereum tarafından oluşturulan yepyeni bir programlama dilidir.

Yazarın Profili
İlginizi Çekebilir

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir