Ana içeriğe geç

Bu sayfanın güncellenmesine yardım edin

🌏

Bu sayfanın yeni bir sürümü vardır ancak şu anda yalnızca İngilizce'dir. Son sürümü çevirmemize yardımcı ol.

Sayfayı çevir
İngilizce'yi gör

Burada hata yok!🐛

Bu sayfa tercüme edilmiyor. Bu sayfayı şimdilik kasıtlı olarak İngilizce bıraktık.

Uniswap-v2 Sözleşmesine Genel Bakış

katılık
Orta düzey
✍️Ori Pomerantz
📆1 Mayıs 2021
⏱️51 dakikalık okuma

Giriş

Uniswap v2 herhangi iki ERC-20 token'ı arasında bir takas piyasası oluşturabilir. Bu yazıda bu protokolü uygulayan sözleşmelerin kaynak kodunu inceleyeceğiz ve neden bu şekilde yazıldığını göreceğiz.

Uniswap ne yapar?

Temel olarak iki tür kullanıcı vardır: likidite sağlayıcıları ve ticaret yapanlar.

Likidite sağlayıcıları, havuza takas edilebilecek iki token sağlar (bunlara Token0 ve Token1 diyeceğiz). Karşılığında, havuzun kısmi sahipliğini temsil eden bir likidite token'ı adı verilen üçüncü bir token alırlar.

Ticaret yapanlar, havuza bir tür token gönderir ve (örneğin, Token0 gönderir ve Token1 alır) likidite sağlayıcıları tarafından sağlanan havuzdan diğer token'ı alırlar. Takas oranı, havuzun sahip olduğu Token0'lar ve Token1'lerin göreceli sayısına göre belirlenir. Ayrıca havuz, likidite havuzu için ödül olarak küçük bir yüzde alır.

Likidite sağlayıcıları varlıklarını geri istediklerinde havuz token'larını yakabilir ve ödül payları da dahil olmak üzere token'larını geri alabilirler.

Daha geniş çaplı bir açıklama için buraya tıklayın.

Neden v2? Neden v3 değil?

Bunu yazdığım esnada, Uniswap v3 neredeyse hazır durumda. Ancak bu, orijinalinden çok daha karmaşık bir yükseltmedir. Önce v2'yi öğrenmek ve ardından v3'e geçmek daha kolaydır.

Çekirdek Sözleşmeler ve Çevre Sözleşmeler

Uniswap v2, çekirdek ve çevre olmak üzere iki bileşene ayrılmıştır. Bu ayrım, varlıkları elinde tutan ve bu nedenle güvenli olmak zorunda olan çekirdek sözleşmelerin daha basit ve denetlenmesi daha kolay olmasını sağlar. Ticaret yapanların ihtiyaç duyduğu tüm ekstra işlevsellik daha sonra çevre sözleşmeleriyle sağlanabilir.

Veri ve Kontrol Akışları

Bu, Uniswap'ın üç ana eylemini gerçekleştirdiğinizde gerçekleşen veri ve kontrol akışıdır:

  1. Farklı token'lar arası takas
  2. Piyasaya likidite katın ve eş takası ERC-20 likidite token'ları ile ödüllendirin
  3. ERC-20 likidite token'larını yakın ve eş takasının, ticaret yapan kişilerin takas yapmasını sağlayan ERC-20 token'larını geri alın

Takas

Bu, ticaret yapanlar tarafından kullanılan en yaygın akıştır:

Çağıran

  1. Çevre hesabına takas edilecek tutarda bir ödenek sağlayın.
  2. Çevre sözleşmesinin birçok takas fonksiyonundan birini çağırın (hangisini çağıracağınız, ETH'nin dahil olup olmadığına; tüccarın yatırılacak token miktarını veya geri alınacak token miktarını belirleyip belirlemediğine vb. bağlıdır). Her takas fonksiyonu, geçmesi gereken bir dizi takas olan bir path kabul eder.

Çevre sözleşmesinde (UniswapV2Router02.sol)

  1. Yol boyunca her takasta işlem görmesi gereken miktarları belirleyin.
  2. Yol üzerinde tekrarlar. Yol boyunca her takas için giriş token'ını gönderir ve ardından takasın swap fonksiyonunu çağırır. Çoğu durumda token'lar için hedef adres, yoldaki bir sonraki eş takasıdır. Son takasta, ticaret yapan kişi tarafından sağlanan adrestir.

Çekirdek sözleşmede (UniswapV2Pair.sol)

  1. Çekirdek sözleşmenin dolandırılmadığını ve takastan sonra yeterli likiditeyi koruyabildiğini doğrulayın.
  2. Bilinen rezervlere ek olarak kaç tane ekstra token'ımız olduğunu görün. Bu miktar, takas etmek için aldığımız giriş token'larının sayısıdır.
  3. Çıktı token'larını hedefe gönderin.
  4. Rezerv tutarlarını güncellemek için _update komutunu çağırın

Çevre sözleşmesine geri dönün (UniswapV2Router02.sol)

  1. Gerekli temizleme işlemlerini gerçekleştirin (örneğin, ticaret yapana göndermek için ETH'yi geri almak amacıyla WETH token'larını yakın)

Likidite Ekleyin

Çağıran

  1. Likidite havuzuna eklenecek tutarlarda çevre hesabına bir ödenek sağlayın.
  2. Çevre sözleşmesinin addLiquidity fonksiyonlarından birini çağırın.

Çevre sözleşmesinde (UniswapV2Router02.sol)

  1. Gerekirse yeni bir eş takası oluşturun
  2. Mevcut bir eş takası varsa, eklenecek token miktarını hesaplayın. Bunun her iki token için aynı değer olması gerekiyordu, bu nedenle yeni token'ların mevcut token'lara oranı aynı.
  3. Tutarların kabul edilebilir olup olmadığını kontrol edin (çağıranlar, likidite eklemek istemeyecekleri bir minimum tutar altında belirtebilirler)
  4. Çekirdek sözleşmeyi çağırın.

Çekirdek sözleşmede (UniswapV2Pair.sol)

  1. Likidite token'larını basın ve çağırana gönderin
  2. Rezerv tutarlarını güncellemek için _update'i çağırın

Likiditeyi Kaldır

Çağıran

  1. Çevre hesabına, temeldeki token'lar karşılığında yakılacak likidite token'ı ödeneği sağlayın.
  2. Çevre sözleşmesinin removeLiquidity fonksiyonlarından birini çağırın.

Çevre sözleşmesinde (UniswapV2Router02.sol)

  1. Likidite token'larını eş takasına gönderin

Çekirdek sözleşmede (UniswapV2Pair.sol)

  1. Temeldeki token'ları hedef adrese yakılmış token'larla orantılı olarak gönderin. Örneğin, havuzda 1000 A token'ı, 500 B token'ı ve 90 likidite token'ı varsa ve yakmak için 9 token alırsak, likidite token'ının %10'unu yakıyoruz, böylece kullanıcıya 100 A token'ı ve 50 B token'ı geri gönderiyoruz.
  2. Likidite token'larını yakın
  3. Rezerv tutarlarını güncellemek için _update'i çağırın

Çekirdek Sözleşmeler

Bunlar likiditeyi tutan güvenli sözleşmelerdir.

UniswapV2Pair.sol

Bu sözleşme token'ları takas eden asıl havuzu uygular. Temel Uniswap fonksiyonudur.

1pragma solidity =0.5.16;
2
3import './interfaces/IUniswapV2Pair.sol';
4import './UniswapV2ERC20.sol';
5import './libraries/Math.sol';
6import './libraries/UQ112x112.sol';
7import './interfaces/IERC20.sol';
8import './interfaces/IUniswapV2Factory.sol';
9import './interfaces/IUniswapV2Callee.sol';
10
Tümünü göster
📋 Kopyala

Bunlar, sözleşme bunları uyguladığı (IUniswapV2Pair ve UniswapV2ERC20) veya bunları uygulayan sözleşmeleri çağırdığı için sözleşmenin bilmesi gereken tüm arayüzlerdir.

1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
2
📋 Kopyala

Bu sözleşme, likidite token'ları için ERC-20 fonksiyonlarını sağlayan UniswapV2ERC20'den kalıtıma uğrar.

1 using SafeMath for uint;
2
📋 Kopyala

SafeMath kütüphanesi, taşmaları ve yetersizlikleri önlemek için kullanılır. Bu, aksi takdirde bir değerin -1 olması gerektiği, ancak bunun yerine 2^256-1 olduğu bir durumla karşılaşabileceğimiz için önemlidir.

1 using UQ112x112 for uint224;
2
📋 Kopyala

Havuz sözleşmesindeki birçok hesaplama kesir gerektirir. Ancak, kesirler EVM tarafından desteklenmez. Uniswap'ın bulduğu çözüm, tamsayı kısmı için 112 bit ve kesir için 112 bit olmak üzere 224 bit değerleri kullanmaktır. Yani 1.0 2^112 olarak temsil edilir, 1.5 2^112 + 2^111 vb. olarak temsil edilir.

Bu kütüphane hakkında daha fazla ayrıntı belgenin ilerleyen bölümlerinde bulunabilir.

Değişkenler

1 uint public constant MINIMUM_LIQUIDITY = 10**3;
2
📋 Kopyala

Sıfıra bölme durumlarından kaçınmak için, her zaman var olan (ancak hesap sıfırına ait olan) minimum sayıda likidite token'ı vardır. O sayı MINIMUM_LIQUIDITY, yani bindir.

1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
2
📋 Kopyala

Bu, ERC-20 transfer fonksiyonu için ABI seçicisidir. İki token hesabında ERC-20 token'larını aktarmak için kullanılır.

1 address public factory;
2
📋 Kopyala

Bu havuzu oluşturan fabrika sözleşmesi budur. Her havuz, iki ERC-20 token'ı arasında bir takas, fabrika ise tüm bu havuzları birbirine bağlayan merkezi bir noktadır.

1 address public token0;
2 address public token1;
3
📋 Kopyala

Bu havuz tarafından takas edilebilecek iki tür ERC-20 token'ı için sözleşmelerin adresleri vardır.

1 uint112 private reserve0; // uses single storage slot, accessible via getReserves
2 uint112 private reserve1; // uses single storage slot, accessible via getReserves
3
📋 Kopyala

Havuzun her token türü için sahip olduğu rezervler. İkisinin aynı miktarda değeri temsil ettiğini varsayıyoruz, ve bu nedenle her token0, reserve1/reserve0 token1'i değerindedir.

1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
2
📋 Kopyala

Bir takasın gerçekleştiği son bloğun zaman damgası, zaman içindeki takas oranını izlemek için kullanılır.

Ethereum sözleşmelerinin en büyük gaz giderlerinden biri, sözleşmenin bir çağrısından diğerine devam eden depolamadır. Her depolama hücresi 256 bit uzunluğundadır. Bu yüzden üç değişken olan reserve0, reserve1 ve blokTimestampLast, tek bir depolama değerinin üçünü de içerebileceği bir şekilde tahsis edilir (112+112+32=256).

1 uint public price0CumulativeLast;
2 uint public price1CumulativeLast;
3
📋 Kopyala

Bu değişkenler, her bir token için kümülatif maliyetleri tutar (her biri diğerinin cinsinden). Bir zaman aralığı boyunca ortalama takas oranını hesaplamak için kullanılabilirler.

1 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
2
📋 Kopyala

Eş takasının token0 ve token1 arasındaki takas oranına karar verme yöntemi, işlemler sırasında iki rezervin katını sabit tutmaktır. Bu değer kLast'dir. Bu, bir likidite sağlayıcısı token yatırdığında veya çektiğinde değişir ve %0,3 piyasa ücreti nedeniyle biraz artar.

İşte basit bir örnek. Basitlik adına, tablonun ondalık kısmından sonra yalnızca üç haneye sahip olduğunu ve sayıların doğru olmaması için %0,3 işlem ücretini göz ardı ettiğimizi unutmayın.

Olayreserve0reserve1reserve0 * reserve1Ortalama takas oranı (token1 / token0)
İlk kurulum1,000.0001,000.0001,000,000
Ticaret Yapan A, 50 tane token0'ı 47.619 token1 ile takas eder1,050.000952.3811,000,0000.952
Ticaret Yapan B, 10 tane token0'ı 8.984 token1 ile takas eder1,060.000943.3961,000,0000.898
Ticaret Yapan C, 40 tane token0'ı 34.305 token1 ile takas eder1,100.000909.0901,000,0000.858
Ticaret Yapan D, 109.01 tane token0'ı 100 token1 ile takas eder990.9901,009.0901,000,0000.917
Ticaret Yapan E, 10 tane token0'ı 10.079 token1 ile takas eder1,000.990999.0101,000,0001.008

Ticaret yapanlar daha fazla token0 sağladıkça, arz ve talebe bağlı olarak token1'in göreceli değeri artar ve bunun tersi de aynı şekilde işler.

Kilitleme

1 uint private unlocked = 1;
2
📋 Kopyala

Yeniden giriş istismarı üzerine kurulu bir güvenlik açığı sınıfı bulunmaktadır. Uniswap'ın isteğe bağlı ERC-20 token'larını aktarması gerekir; bu, onları çağıran Uniswap borsasını kötüye kullanmaya çalışabilecek ERC-20 sözleşmelerini çağırmak anlamına gelir. Sözleşmenin bir parçası olarak bir unlocked değişkenine sahip olarak, fonksiyonların çalışırken (aynı işlem içinde) çağrılmasını önleyebiliriz.

1 modifier lock() {
2
📋 Kopyala

Bu fonksiyon bir niteleyicidir, yani normal bir fonksiyonun davranışını bir şekilde değiştirmek için onu paketler.

1 require(unlocked == 1, 'UniswapV2: LOCKED');
2 unlocked = 0;
3
📋 Kopyala

Eğer unlocked bire eşit ise, onu sıfır olarak ayarlayın. Zaten sıfırsa çağrıyı geri alarak başarısız olmasını sağlayın.

1 _;
2
📋 Kopyala

Bir niteleyicide _; orijinal fonksiyon çağrısıdır (tüm parametrelerle birlikte). Burada, fonksiyonun çağrısının yalnızca, çağrıldığında unlocked bir olduğunda ve çalışırken unlocked değerinin sıfır olması durumunda gerçekleşeceği anlamına gelir.

1 unlocked = 1;
2 }
3
📋 Kopyala

Ana fonksiyon geri döndükten sonra kilidi açın.

Diğer fonksiyonlar

1 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
2 _reserve0 = reserve0;
3 _reserve1 = reserve1;
4 _blockTimestampLast = blockTimestampLast;
5 }
6
📋 Kopyala

Bu fonksiyon, çağıranlara takasın mevcut durumunu sağlar. Solidity fonksiyonlarının birden fazla değer döndürebildiğini unutmayın.

1 function _safeTransfer(address token, address to, uint value) private {
2 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
3
📋 Kopyala

Bu dahili fonksiyon, takastaki bir miktar ERC20 token'ını bir başkasına aktarır. SELECTOR, çağırdığımız fonksiyonun transfer(address,uint) olduğunu belirtir (yukarıdaki tanıma bakın).

Token fonksiyonu için bir arayüzü içe aktarmak zorunda kalmamak için, çağrıyı ABI fonksiyonlarından birini kullanarak "manuel olarak" oluştururuz.

1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
2 }
3
📋 Kopyala

Bir ERC-20 transfer çağrısının başarısızlığı bildirebilmesinin iki yolu vardır:

  1. Geri döndürme. Harici bir sözleşmeye yapılan çağrı geri alınırsa, boolean dönüş değeri false olur
  2. Normal şekilde sonlandırın ancak bir sorun bildirin. Bu durumda, dönüş değeri arabelleği sıfır olmayan bir uzunluğa sahiptir ve bir boole değeri olarak kodu çözüldüğünde, false olur

Bu koşullardan herhangi biri gerçekleşirse, geri döndürün.

Olaylar

1 event Mint(address indexed sender, uint amount0, uint amount1);
2 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
3
📋 Kopyala

Bu iki olay, bir likidite sağlayıcısı likidite yatırdığında (Mint) veya onu çektiğinde (Burn) ortaya çıkar. Her iki durumda da, yatırılan veya çekilen token0 ve token1 miktarları ve ayrıca bizi çağıran hesabın (sender) kimliği olayın bir parçasıdır. Çekme durumunda olay, gönderen ile aynı olmayabilecek token'ları alan (to) hedefi de içerir.

1 event Swap(
2 address indexed sender,
3 uint amount0In,
4 uint amount1In,
5 uint amount0Out,
6 uint amount1Out,
7 address indexed to
8 );
9
📋 Kopyala

Bu olay, ticaret yapan kişi bir token'ı diğeriyle takas ettiğinde ortaya çıkar. Yine, gönderen ve hedef aynı olmayabilir. Her token, takasa gönderilebilir veya ondan alınabilir.

1 event Sync(uint112 reserve0, uint112 reserve1);
2
📋 Kopyala

Son olarak, nedenden bağımsız olarak, en son rezerv bilgilerini (ve dolayısıyla takas oranını) sağlamak için token'lar her eklendiğinde veya çekildiğinde Sync gönderilir.

Kurulum Fonksiyonları

Bu fonksiyonların, yeni eş takası kurulduğunda bir kez çağrılması gerekiyor.

1 constructor() public {
2 factory = msg.sender;
3 }
4
📋 Kopyala

Yapıcı, eşleri oluşturan fabrikanın adresini takip etmemizi sağlar. Bu bilgi, initialize ve fabrika ücreti (varsa) için gereklidir

1 // called once by the factory at time of deployment
2 function initialize(address _token0, address _token1) external {
3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
4 token0 = _token0;
5 token1 = _token1;
6 }
7
📋 Kopyala

Bu fonksiyon, fabrikanın (sadece ama sadece fabrikanın) bu eşin takas edeceği iki ERC-20 token'ını belirtmesine olanak tanır.

Dahili Güncelleme Fonksiyonları

_update
1 // update reserves and, on the first call per block, price accumulators
2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
3
📋 Kopyala

Bu fonksiyon, token'lar her yatırıldığında veya çekildiğinde çağrılır.

1 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
2
📋 Kopyala

balance0 veya balance1 (uint256), uint112(-1) (=2^112-1) değerinden yüksekse (böylece uint122'ye dönüştürüldüğünde taşarak 0'a dönüyorsa) taşmaları önlemek için _update'i sürdürmeyi reddedin. 10^18 birime bölünebilen normal bir token ele alındığında bu, her takastaki tüm token'ların ortalama 5.1*10^15 ile kısıtlı olduğu anlamına gelir. Şimdiye kadar bu bir sorun olmadı.

1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);
2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
4
📋 Kopyala

Geçen süre sıfır değilse, bu bloktaki ilk takas işlemi biziz demektir. Bu durumda maliyet biriktiricilerini güncellememiz gerekiyor.

1 // * never overflows, and + overflow is desired
2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
4 }
5
📋 Kopyala

Her maliyet biriktirici, son ücret ve (diğer token'ın rezervi/bu token'ın rezervi) saniye cinsinden geçen sürenin çarpımı ile güncellenir. Ortalama bir fiyat elde etmek için kümülatif fiyatın zaman içinde iki nokta olduğunu okursunuz ve aralarındaki zaman farkına bölersiniz. Örneğin, bu olay dizisini varsayalım:

Olayreserve0reserve1zaman damgasıMarjinal takas oranı (reserve1 / reserve0)price0CumulativeLast
İlk kurulum1,000.0001,000.0005,0001.0000
Ticaret Yapan A, 50 token0 yatırır ve 47.619 token1 geri alır1,050.000952.3815,0200.90720
Ticaret Yapan B, 10 token0 yatırır ve 8.984 token1 geri alır1,060.000943.3965,0300.89020+10*0.907 = 29.07
Ticaret Yapan C 40 token0 yatırır ve 34.305 token1 geri alır1,100.000909.0905,1000.82629.07+70*0.890 = 91.37
Ticaret Yapan D, 100 token1 yatırır ve 109.01 token0 geri alır990.9901,009.0905,1101.01891.37+10*0.826 = 99.63
Trader E, 10 jeton0 yatırır ve 10.079 jeton1 geri alır1,000.990999.0105,1500.99899.63+40*1.1018 = 143.702

5.030 ve 5.150 zaman damgaları arasında Token0'ın ortalama fiyatını hesaplamak istediğimizi varsayalım. price0Cumulative değerindeki fark 143,702-29,07=114,632'dir. Bu, iki dakikalık (120 saniye) ortalamadır. Yani ortalama fiyat 114,632/120 = 0,955'tir.

Bu fiyat hesaplaması, eski rezerv büyüklüklerini bilmemiz gerekmesinin nedenidir.

1 reserve0 = uint112(balance0);
2 reserve1 = uint112(balance1);
3 blockTimestampLast = blockTimestamp;
4 emit Sync(reserve0, reserve1);
5 }
6
📋 Kopyala

Son olarak, global değişkenleri güncelleyin ve bir Sync olayı yayınlayın.

_mintFee
1 // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
3
📋 Kopyala

Uniswap 2.0'da ticaret yapanlar, piyasayı kullanmak için %0,30'luk bir ücret öderler. Bu ücretin çoğu (ticaretin %0,25'i) her zaman likidite sağlayıcılarına gider. Kalan %0,05, likidite sağlayıcılarına veya fabrika tarafından protokol ücreti olarak belirtilen ve Uniswap'a geliştirme çabaları için ödeme yapan bir adrese gidebilir.

Hesaplamaları (ve dolayısıyla gaz maliyetlerini) azaltmak için bu ücret, her işlemde değil, yalnızca havuza likidite eklendiğinde veya havuzdan çıkarıldığında hesaplanır.

1 address feeTo = IUniswapV2Factory(factory).feeTo();
2 feeOn = feeTo != address(0);
3
📋 Kopyala

Fabrikanın ücret hedefini okuyun. Sıfır ise protokol ücreti yoktur ve bu ücreti hesaplamaya gerek yoktur.

1 uint _kLast = kLast; // gas savings
2
📋 Kopyala

kLast durum değişkeni depoda bulunur, bu nedenle sözleşmeye yapılan farklı çağrılar arasında bir değeri olacaktır. Depolamaya erişim, sözleşmeye yapılan fonksiyon çağrısı sona erdiğinde serbest bırakılan geçici belleğe erişimden çok daha pahalıdır, bu nedenle gazdan tasarruf etmek için dahili bir değişken kullanırız.

1 if (feeOn) {
2 if (_kLast != 0) {
3
📋 Kopyala

Likidite sağlayıcıları, likidite token'larının değer kazanmasıyla paylarını alırlar. Ancak protokol ücreti, yeni likidite token'larının basılmasını ve feeTo adresine sağlanmasını gerektirir.

1 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
2 uint rootKLast = Math.sqrt(_kLast);
3 if (rootK > rootKLast) {
4
📋 Kopyala

Protokol ücreti tahsil edilecek yeni likidite varsa. Karekök fonksiyonunu bu makalenin ilerleyen bölümlerinde görebilirsiniz

1 uint numerator = totalSupply.mul(rootK.sub(rootKLast));
2 uint denominator = rootK.mul(5).add(rootKLast);
3 uint liquidity = numerator / denominator;
4
📋 Kopyala

Bu karmaşık ücret hesaplaması, 5. sayfadaki teknik raporda açıklanmaktadır. kLast'ın hesaplandığı zaman ile şimdiki zaman arasında likidite eklenmediğini veya kaldırılmadığını biliyoruz (çünkü bu hesaplamayı her likidite eklendiğinde veya kaldırıldığında, aslında değişmeden önce yapıyoruz), dolayısıyla reserve0 * reserve1 üzerindeki tüm değişiklikler işlem ücretlerinden gelmelidir (onlar olmadan reserve0 * reserve1 değerlerini sabit tutardık).

1 if (liquidity > 0) _mint(feeTo, liquidity);
2 }
3 }
4
📋 Kopyala

Ek likidite token'larını gerçekten oluşturmak ve bunları feeTo öğesine atamak için UniswapV2ERC20._mint fonksiyonunu kullanın.

1 } else if (_kLast != 0) {
2 kLast = 0;
3 }
4 }
5
📋 Kopyala

Herhangi bir ücret yoksa kLast öğesini sıfıra ayarlayın (zaten değilse). Bu sözleşme yazıldığında, ihtiyaç duymadıkları depolama alanını sıfırlayarak sözleşmeleri Ethereum durumunun genel boyutunu küçültmeye teşvik eden bir gaz iadesi özelliği bulunuyordu. Bu kod, mümkün olduğunda o iadeyi alır.

Harici Erişilebilir Fonksiyonlar

Herhangi bir işlem veya sözleşme bu fonksiyonları çağırabilir, ancak bunların çevre sözleşmesinden çağrılacak şekilde tasarlandığını unutmayın. Onları doğrudan çağırırsanız eş takasında hile yapamazsınız ancak bir hata nedeniyle değer kaybedebilirsiniz.

mint
1 // this low-level function should be called from a contract which performs important safety checks
2 function mint(address to) external lock returns (uint liquidity) {
3
📋 Kopyala

Bu fonksiyon, bir likidite sağlayıcısı havuza likidite eklediğinde çağrılır. Ödül olarak ek likidite token'ları basar. Aynı işlemde likiditeyi ekledikten sonra onu çağıran çevre sözleşmesinden (böylece başka hiç kimse meşru sahibinden önce yeni likidite talep eden bir işlem sunamaz).

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
2
📋 Kopyala

Bu, birden çok değer döndüren bir Solidity fonksiyonunun sonuçlarını okumanın yoludur. İhtiyacımız olmadığı için son döndürülen değerleri, blok zaman damgasını atıyoruz.

1 uint balance0 = IERC20(token0).balanceOf(address(this));
2 uint balance1 = IERC20(token1).balanceOf(address(this));
3 uint amount0 = balance0.sub(_reserve0);
4 uint amount1 = balance1.sub(_reserve1);
5
📋 Kopyala

Mevcut bakiyeleri alın ve her bir token türünden ne kadar eklendiğini görün.

1 bool feeOn = _mintFee(_reserve0, _reserve1);
2
📋 Kopyala

Varsa toplanacak protokol ücretlerini hesaplayın ve likidite token'larını buna göre basın. _mintFee parametreleri eski rezerv değerleri olduğundan, ücret yalnızca ücretlerden kaynaklanan havuz değişikliklerine göre doğru bir şekilde hesaplanır.

1 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
2 if (_totalSupply == 0) {
3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
4 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
5
📋 Kopyala

Eğer bu ilk yatırma ise, MINIMUM_LIQUIDITY tane token yaratın ve onları kilitlemek için sıfır adresine gönderin. Asla alınamayacakları için havuz asla tamamen boşaltılamaz (bu bizi bazı yerlerde sıfıra bölmekten kurtarır). MINIMUM_LIQUIDITY değeri bindir ve çoğu ERC-20'nin bir token'ın 10^-18'lik birimlerine tekrar bölündüğünü göz önünde bulundurursak ETH wei'ye bölündüğünden bu bin, tek bir token'ın değerinin 10^-15 kadarıdır. Yüksek bir ücret değil.

İlk yatırma sırasında iki token'ın göreceli değerini bilmiyoruz, bu yüzden sadece miktarları çarpıyoruz ve yatırma işleminin bize her iki token'da da eşit değer sağladığını varsayarak bir karekök alıyoruz.

Arbitrajda değer kaybetmemek için eşit değer sağlamak para yatıran kişinin çıkarına olduğu için buna güvenebiliriz. Diyelim ki iki token'ın değeri aynı, ancak yatıran kişimiz Token0'a göre dört kat daha fazla Token1 yatırdı. Ticaret yapan bir kişi, eş takasının Token0'ın daha değerli olduğunu düşündüğü gerçeğini ondan değer yaratmak için kullanabilir.

Olayreserve0reserve1reserve0 * reserve1Havuzun değeri (reserve0 + reserve1)
İlk kurulum83225640
Ticaret yapan kişi 8 Token0 tokeni yatırır, 16 Token1 alır161625632

Gördüğünüz gibi, ticaret yapan kişi havuzun değerindeki bir düşüşten gelen 8 token kazanarak ona sahip olan yatırım yapan kişiye zarar verdi.

1 } else {
2 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
3
📋 Kopyala

Sonraki her yatırmada, iki varlık arasındaki takas oranını zaten biliyoruz ve likidite sağlayıcılarının her ikisinde de eşit değer sağlamasını bekliyoruz. Vermezlerse, ceza olarak sağladıkları daha düşük değere dayalı olarak onlara likidite token'ları veririz.

İster ilk yatırma ister sonraki bir yatırma olsun, sağladığımız likidite token'larının sayısı reserve0*reserve1'deki değişikliğin kareköküne eşittir ve likidite token'ının değeri değişmez (her iki tür için de eşit değerlere sahip olmayan bir yatırım almadığı sürece böyledir, aksi hâlde "para cezası" dağıtılır). İşte aynı değere sahip iki token'lı, üç iyi yatırma ve bir kötü yatırma bulunan başka bir örnek (yalnızca bir token türünden para yatırma, bu nedenle herhangi bir likidite token'ı üretmez).

Olayreserve0reserve1reserve0 * reserve1Havuz değeri (reserve0 + reserve1)Bu yatırma için basılmış likidite token'larıToplam likidite token'larıher bir likidite token'ının değeri
İlk kurulum8.0008.0006416.000882.000
Her türden dördünü yatırma12.00012.00014424.0004122.000
Her türden ikisini yatırma14.00014.00019628.0002142.000
Eşit olmayan değer yatırma18.00014.00025232.000014~2.286
Arbitrajdan sonra~15.874~15.874252~31.748014~2.267
1 }
2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
3 _mint(to, liquidity);
4
📋 Kopyala

Ek likidite token'larını gerçekten oluşturmak ve bunları doğru hesaba vermek için UniswapV2ERC20._mint fonksiyonunu kullanın.

1
2 _update(balance0, balance1, _reserve0, _reserve1);
3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
4 emit Mint(msg.sender, amount0, amount1);
5 }
6
📋 Kopyala

Durum değişkenlerini (reserve0, reserve1 ve gerekirse kLast) güncelleyin ve uygun olayı yayınlayın.

burn
1 // this low-level function should be called from a contract which performs important safety checks
2 function burn(address to) external lock returns (uint amount0, uint amount1) {
3
📋 Kopyala

Bu fonksiyon, likidite çekildiğinde ve uygun likidite token'larının yakılması gerektiğinde çağrılır. Ayrıca o da bir çevre hesabından çağrılmalıdır.

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
2 address _token0 = token0; // gas savings
3 address _token1 = token1; // gas savings
4 uint balance0 = IERC20(_token0).balanceOf(address(this));
5 uint balance1 = IERC20(_token1).balanceOf(address(this));
6 uint liquidity = balanceOf[address(this)];
7
📋 Kopyala

Çevre sözleşmesi, yakılacak likiditeyi çağrıdan önce bu sözleşmeye aktardı. Bu şekilde ne kadar likidite yakacağımızı biliriz ve yanmasını sağlayabiliriz.

1 bool feeOn = _mintFee(_reserve0, _reserve1);
2 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
3 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
4 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
6
📋 Kopyala

Likidite sağlayıcısı, her iki token'dan eşit değerde alır. Bu şekilde takas oranını değiştirmiyoruz.

1 _burn(address(this), liquidity);
2 _safeTransfer(_token0, to, amount0);
3 _safeTransfer(_token1, to, amount1);
4 balance0 = IERC20(_token0).balanceOf(address(this));
5 balance1 = IERC20(_token1).balanceOf(address(this));
6
7 _update(balance0, balance1, _reserve0, _reserve1);
8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
9 emit Burn(msg.sender, amount0, amount1, to);
10 }
11
12
Tümünü göster
📋 Kopyala

burn fonksiyonunun geri kalanı yukarıdaki mint fonksiyonunun bir yansımasıdır.

swap
1 // this low-level function should be called from a contract which performs important safety checks
2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
3
📋 Kopyala

Bu fonksiyonun bir çevre sözleşmesinden çağrılması gerekir.

1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
4
5 uint balance0;
6 uint balance1;
7 { // scope for _token{0,1}, avoids stack too deep errors
8
📋 Kopyala

Yerel değişkenler ya bellekte ya da çok fazla değilse doğrudan yığında saklanabilir. Yığını kullanmak için sayıyı sınırlayabilirsek daha az gaz kullanırız. Daha detaylı incelemek için sarı kağıt, resmi Ethereum şartnamesinin 26. sayfasındaki 298. denkleme bakınız.

1 address _token0 = token0;
2 address _token1 = token1;
3 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
4 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
6
📋 Kopyala

Bu transfer, tüm koşulların karşılandığından emin olmadan önce transfer ettiğimiz için iyimserdir. Bu, çağrıda daha sonra koşullar karşılanmazsa çağrıdan ve yarattığı değişikliklerden geri döneceğimiz için Ethereum'da sorun olmaz.

1 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
2
📋 Kopyala

Eğer isteniyorsa alıcıyı takas hakkında bilgilendirin.

1 balance0 = IERC20(_token0).balanceOf(address(this));
2 balance1 = IERC20(_token1).balanceOf(address(this));
3 }
4
📋 Kopyala

Mevcut bakiyeleri alın. Çevre sözleşmesi, takas için bizi çağırmadan önce bize token'ları gönderir. Bu, sözleşmenin aldatılmadığını kontrol etmesini kolaylaştırır, bu, çekirdek sözleşmede gerçekleşecek gerçekleşmesi gereken bir kontroldür (çünkü çevre sözleşmemiz dışındaki diğer varlıklar tarafından çağrılabiliriz).

1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
4 { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
8
📋 Kopyala

Bu, takastan zarara uğramayacağımızdan emin olmak için yapılan bir doğruluk testidir. Bir takasın reserve0*reserve1'i azaltması gereken hiçbir durum yoktur. Burası aynı zamanda takasta %0,3'lük bir ücretin gönderilmesini sağladığımız yerdir; K'nin değerini doğruluk testine tabi tutmadan önce, her iki bakiyeyi 1000 ile çarparız ve sonuçtan 3 ile çarpılan miktarları çıkarırız. Bu, bakiyenin K değerini mevcut rezervlerin K değeri ile karşılaştırmadan önce bakiyeden %0,3 (3/1000 = 0,003 = %0,3) düşüldüğü anlamına gelir.

1 }
2
3 _update(balance0, balance1, _reserve0, _reserve1);
4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
5 }
6
📋 Kopyala

reserve0, reserve1'i ve gerekliyse fiyat biriktiricilerini ve tarih bilgisini güncelleyin ve bir olay yayın.

Senkronize Etme veya Fazlasını Alma

Reel bakiyelerin, eş takasının sahip olduğunu düşündüğü rezervlerle uyumsuz olması mümkündür. Sözleşmenin izni olmadan token'ları çekmenin bir yolu yoktur, ancak yatırımlar farklı bir konudur. Bir hesap borsaya mint veya swap çağırmadan token aktarabilir.

Bu durumda iki çözüm var:

  • sync, rezervleri mevcut bakiyelere güncelleyin
  • skim, fazladan miktarı çekin. Token'ları kimin yatırdığını bilmediğimiz için herhangi bir hesabın skim komutunu çağırmasına izin verildiğini unutmayın. Bu bilgi bir olayda yayınlanır, ancak olaylara blok zincirinden erişilemez.
1 // force balances to match reserves
2 function skim(address to) external lock {
3 address _token0 = token0; // gas savings
4 address _token1 = token1; // gas savings
5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
7 }
8
9
10
11 // force reserves to match balances
12 function sync() external lock {
13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
14 }
15}
16
Tümünü göster
📋 Kopyala

UniswapV2Factory.sol

Bu sözleşme eş takaslarını oluşturur.

1pragma solidity =0.5.16;
2
3import './interfaces/IUniswapV2Factory.sol';
4import './UniswapV2Pair.sol';
5
6contract UniswapV2Factory is IUniswapV2Factory {
7 address public feeTo;
8 address public feeToSetter;
9
📋 Kopyala

Bu durum değişkenleri protokol ücretini uygulamak için gereklidir (bkz. teknik rapor, 5. sayfa). feeTo adresi, protokol ücreti için likidite token'larını biriktirir ve feeToSetter, feeTo'u farklı bir adresle değiştirmesine izin verilen adrestir.

1 mapping(address => mapping(address => address)) public getPair;
2 address[] public allPairs;
3
📋 Kopyala

Bu değişkenler; eşleri, iki token türü arasındaki değişimleri takip eder.

İlki olan getPair, takas ettiği iki ERC-20 token'ını temel alan bir eş takası sözleşmesini tanımlayan bir eşleştirmedir. ERC-20 token'ları, onları uygulayan sözleşmelerin adresleri ile tanımlanır, bu nedenle anahtarlar ve değerin tümü adreslerdir. tokenA'dan tokenB'ye dönüştürmenize izin veren eş takasının adresini almak için şunu kullanırsınız: getPair[<tokenA address>][<tokenB address>] (veya tam tersi).

İkinci değişken olan allPairs, bu fabrika tarafından oluşturulan eş takaslarının tüm adreslerini içeren bir dizidir. Ethereum'da bir eşlemenin içeriğini yineleyemezsiniz veya tüm anahtarların bir listesini alamazsınız, bu nedenle bu fabrikanın hangi takasları yönettiğini bilmenin tek yolu bu değişkendir.

Not: Bir eşlemenin tüm anahtarlarını yineleyememenizin nedeni, sözleşme verilerinin depolanmasının pahalı olmasıdır, bu nedenle ne kadar azını kullanırsak ve onu ne kedar az değiştirirsek o kadar iyi olur. Tekrarlamayı destekleyen eşleştirmeler oluşturabilirsiniz, ancak bunlar anahtar listesi için ekstra depolama gerektirirler. Çoğu uygulamada buna ihtiyacınız yoktur.

1 event PairCreated(address indexed token0, address indexed token1, address pair, uint);
2
📋 Kopyala

Bu olay, yeni bir eş takası oluşturulduğunda yayınlanır. Token'ların adreslerini, eş takasının adresini ve fabrika tarafından yönetilen toplam takas sayısını içerir.

1 constructor(address _feeToSetter) public {
2 feeToSetter = _feeToSetter;
3 }
4
📋 Kopyala

Yapıcının yaptığı tek şey feeToSetter'ı belirlemektir. Fabrikalar ücretsiz olarak başlar ve bunu yalnızca feeSetter değiştirebilir.

1 function allPairsLength() external view returns (uint) {
2 return allPairs.length;
3 }
4
📋 Kopyala

Bu fonksiyon, eş takaslarının sayısını döndürür.

1 function createPair(address tokenA, address tokenB) external returns (address pair) {
2
📋 Kopyala

Bu, fabrikanın ana işlevidir, yani iki ERC-20 token'ı arasında bir eş takası yaratmak. Bu fonksiyonu herhangi birinin çağırabileceğini unutmayın. Yeni bir eş takası oluşturmak için Uniswap'ten izin almanız gerekmez.

1 require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
2 (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
3
📋 Kopyala

Zincir dışında önceden hesaplanabilmesi için yeni takasın adresinin deterministik olmasını istiyoruz (bu, katman 2 işlemleri için yararlı olabilir). Bunu yapmak için, onları aldığımız sıraya bakılmaksızın, token adreslerinin tutarlı bir sırasına sahip olmamız gerekir, bu yüzden onları burada sıralarız.

1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
3
📋 Kopyala

Büyük likidite havuzları, daha istikrarlı fiyatlara sahip oldukları için küçük olanlardan daha iyidir. Token çifti başına birden fazla likidite havuzuna sahip olmak istemiyoruz. Hâlihazırda bir değişim takas, aynı çift için başka bir takas oluşturmaya gerek yoktur.

1 bytes memory bytecode = type(UniswapV2Pair).creationCode;
2
📋 Kopyala

Yeni bir sözleşme oluşturmak için onu oluşturan koda ihtiyacımız var (hem yapıcı fonksiyon hem de gerçek sözleşmenin EVM bayt kodunu belleğe yazan kod). Normalde Solidity'de sadece addr = new <name of contract>(<constructor parameters>) kullanırız ve derleyici bizim için her şeyi halleder, ancak deterministik bir sözleşme adresine sahip olmak için CREATE2 opcode'u kullanmamız gerekir. Bu kod yazıldığında opcode henüz Solidity tarafından desteklenmiyordu, bu yüzden kodu manuel olarak almak gerekiyordu. Bu artık bir sorun değil, çünkü Solidity artık CREATE2'yi destekliyor.

1 bytes32 salt = keccak256(abi.encodePacked(token0, token1));
2 assembly {
3 pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
4 }
5
📋 Kopyala

Bir opcode henüz Solidity tarafından desteklenmediğinde onu satır içi derleme kullanarak çağırabiliriz.

1 IUniswapV2Pair(pair).initialize(token0, token1);
2
📋 Kopyala

Yeni takasa hangi iki token'ın takas edildiğini söylemek için initialize işlevini çağırın.

1 getPair[token0][token1] = pair;
2 getPair[token1][token0] = pair; // populate mapping in the reverse direction
3 allPairs.push(pair);
4 emit PairCreated(token0, token1, pair, allPairs.length);
5 }
6
📋 Kopyala

Yeni çift bilgisini durum değişkenlerine kaydedin ve yeni eş takasını dünyaya bildirmek için bir olay yayınlayın.

1 function setFeeTo(address _feeTo) external {
2 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
3 feeTo = _feeTo;
4 }
5
6 function setFeeToSetter(address _feeToSetter) external {
7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
8 feeToSetter = _feeToSetter;
9 }
10}
11
Tümünü göster
📋 Kopyala

Bu iki fonksiyon, feeSetter öğesinin ücret alıcısını (varsa) kontrol etmesine ve feeSetter öğesini yeni bir adresle değiştirmesine olanak tanır.

UniswapV2ERC20.sol

Bu sözleşme, ERC-20 likidite token'ını uygular. OpenWhisk ERC-20 sözleşmesine benzer, bu yüzden sadece permit işlevselliği olan farklı kısmı açıklayacağım.

Ethereum'daki işlemler, gerçek paraya eş değer olan ether'a (ETH) mal olur. ERC-20 token'larınız varsa ancak ETH'niz yoksa işlem gönderemezsiniz, bu nedenle onlarla hiçbir şey yapamazsınız. Bu sorundan kaçınmanın bir yolu meta-işlemlerdir. Token'ların sahibi, bir başkasının jetonları zincirden çekmesine ve interneti kullanarak alıcıya göndermesine izin veren bir işlem imzalar. Daha sonra ETH'ye sahip olan alıcı, token sahibi adına izni gönderir.

1 bytes32 public DOMAIN_SEPARATOR;
2 // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
3 bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
4
📋 Kopyala

Bu hash değeri, işlem türü için tanımlayıcıdır. Burada desteklediğimiz tek şey, bu parametrelerle Permit'dir.

1 mapping(address => uint) public nonces;
2
📋 Kopyala

Bir alıcının dijital imzayı taklit etmesi mümkün değildir. Ancak, aynı işlemi iki kez göndermek önemsizdir. (bu, bir tekrar saldırısı biçimidir). Bunu önlemek için, bir nonce kullanırız. Yeni bir Permit'in nonce değeri son kullanılandan bir fazla değilse, geçersiz olduğunu varsayıyoruz.

1 constructor() public {
2 uint chainId;
3 assembly {
4 chainId := chainid
5 }
6
📋 Kopyala

Bu, zincir tanımlayıcısını almaya yarayan koddur. Yul denilen bir EVM derleme diyalekti kullanır. Yul'un mevcut versiyonunda chainid değil chainid() kullanmanız gerektiğini unutmayın.

1 DOMAIN_SEPARATOR = keccak256(
2 abi.encode(
3 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
4 keccak256(bytes(name)),
5 keccak256(bytes('1')),
6 chainId,
7 address(this)
8 )
9 );
10 }
11
Tümünü göster
📋 Kopyala

EIP-712 için alan adı ayırıcısını hesapla.

1 function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
2
📋 Kopyala

Bu, yetkileri uygulayan fonksiyondur. İlgili alanları ve imza için üç skaler değeri parametre olarak alır (v, r ve s).

1 require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
2
📋 Kopyala

Son teslim tarihinden sonra işlemleri kabul etmeyin.

1 bytes32 digest = keccak256(
2 abi.encodePacked(
3 '\x19\x01',
4 DOMAIN_SEPARATOR,
5 keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
6 )
7 );
8
📋 Kopyala

abi.encodePacked(...) almayı beklediğimiz mesajdır. Nonce değerinin ne olması gerektiğini biliyoruz, bu yüzden onu parametre olarak almamıza gerek yok

Ethereum imza algoritması, imzalamak için 256 bit almayı bekler, bu nedenle keccak256 hash fonksiyonunu kullanırız.

1 address recoveredAddress = ecrecover(digest, v, r, s);
2
📋 Kopyala

Özetten ve imzadan, ecrecover kullanarak imzalayan adresi alabiliriz.

1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
2 _approve(owner, spender, value);
3 }
4
5
📋 Kopyala

Her şey tamamsa bunu bir ERC-20 onayı olarak görün.

Çevre Sözleşmeleri

Çevre sözleşmeler, Uniswap için API'dir (uygulama programı arayüzü). Diğer sözleşmelerden veya merkeziyetsiz uygulamalardan harici çağrılar için kullanılabilirler. Çekirdek sözleşmeleri doğrudan arayabilirsiniz, ancak bu daha karmaşıktır ve bir hata yaparsanız değer kaybedebilirsiniz. Çekirdek sözleşmeler, yalnızca aldatılmadıklarından emin olmak için testler içerir, başkaları için doğruluk testi yapmak için değil. Bunlar, gerektiğinde güncellenebilmeleri için çevrededir.

UniswapV2Router01.sol

Bu sözleşmenin sorunları vardır ve artık kullanılmamalıdır. Neyse ki çevre sözleşmeler durumsuz olduğu ve herhangi bir varlık tutmadıkları için onları kullanımdan kaldırmak ve insanlara bunun yerine UniswapV2Router02 kullanmayı önermek kolaydır.

UniswapV2Router02.sol

Çoğu durumda Uniswap'i bu sözleşme aracılığıyla kullanırsınız. Nasıl kullanacağınızı burada görebilirsiniz.

1pragma solidity =0.6.6;
2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
5
6import './interfaces/IUniswapV2Router02.sol';
7import './libraries/UniswapV2Library.sol';
8import './libraries/SafeMath.sol';
9import './interfaces/IERC20.sol';
10import './interfaces/IWETH.sol';
11
Tümünü göster
📋 Kopyala

Bunların çoğuyla ya daha önce karşılaştık ya da çoğu oldukça açık. IWETH.sol tek istisnadır. Uniswap v2, herhangi bir çift ERC-20 token'ı için takasa izin verir ancak ether'ın (ETH) kendisi bir ERC-20 token'ı değildir. Standarttan önce gelir ve benzersiz mekanizmalar ile aktarılır. ERC-20 token'ları için geçerli olan sözleşmelerde ETH kullanımını etkinleştirmek için insanlar paketlenmiş ether (WETH) sözleşmesini buldu. Bu sözleşmeye ETH gönderirsiniz ve size eş değer miktarda WETH basar. Veya WETH'yi yakabilir ve ETH'yi geri alabilirsiniz.

1contract UniswapV2Router02 is IUniswapV2Router02 {
2 using SafeMath for uint;
3
4 address public immutable override factory;
5 address public immutable override WETH;
6
📋 Kopyala

Yönlendiricinin hangi fabrikayı kullanacağını ve WETH gerektiren işlemler için hangi WETH sözleşmesinin kullanılacağını bilmesi gerekir. Bu değerler değiştirilemez, yani bu sadece yapıcıda ayarlanabilirler. Bu durum kullanıcılara, kimsenin bu değerleri güvenilmez sözleşmelere yönlendirecek şekilde değiştiremeyeceğine dair güven verir.

1 modifier ensure(uint deadline) {
2 require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
3 _;
4 }
5
📋 Kopyala

Bu niteleyici, zaman sınırlı işlemlerin ("mümkünse X'i Y zamanından önce yap") zaman sınırından sonra gerçekleşmemesini sağlar.

1 constructor(address _factory, address _WETH) public {
2 factory = _factory;
3 WETH = _WETH;
4 }
5
📋 Kopyala

Yapıcı sadece değişmez durum değişkenlerini ayarlar.

1 receive() external payable {
2 assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
3 }
4
📋 Kopyala

Bu fonksiyon, WETH sözleşmesinden token'ları tekrar ETH'ye döndürdüğümüzde çağrılır. Sadece kullandığımız WETH sözleşmesi bunu yapmaya yetkilidir.

Likidite Ekleyin

Bu fonksiyonlar, likidite havuzunu artıran eş takasına token'lar ekler.

1
2 // **** ADD LIQUIDITY ****
3 function _addLiquidity(
4
📋 Kopyala

Bu fonksiyon, eş takasına yatırılması gereken A ve B token'larının miktarını hesaplamak için kullanılır.

1 address tokenA,
2 address tokenB,
3
📋 Kopyala

Bunlar, ERC-20 token sözleşmelerinin adresleridir.

1 uint amountADesired,
2 uint amountBDesired,
3
📋 Kopyala

Bunlar, likidite sağlayıcısının yatırmak istediği miktarlardır. Ayrıca yatırılacak maksimum A ve B miktarlarıdır.

1 uint amountAMin,
2 uint amountBMin
3
📋 Kopyala

Bunlar, yatırmak için kabul edilebilir minimum tutarlardır. Bu tutarlar veya daha fazlası ile işlem gerçekleşemezse, geri dönün. Bu özelliği istemiyorsanız, sıfırı belirtmeniz yeterlidir.

Likidite sağlayıcıları, işlemi mevcut takas oranına yakın bir takas oranıyla sınırlamak istedikleri için genelde bir minimum tutar belirtir. Takas oranının çok fazla dalgalanması, temeldeki değerleri değiştiren haberler olduğu anlamına gelebilir ve ne yapacaklarına manuel olarak karar vermek isteyebilirler.

Örneğin, takas oranının bire bir olduğu ve likidite sağlayıcısının şu değerleri belirlediği bir durumu hayal edin:

ParametreDeğer
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

Takas oranı 0,9 ila 1,25 arasında kaldığı sürece işlem gerçekleşir. Takas oranı bu aralığın dışına çıkarsa işlem iptal edilir.

Bu önlemin nedeni; işlemlerin hemen gerçekleşmemesidir. Siz işlemleri gönderirsiniz ve sonunda bir madenci bunları bir bloğa dahil eder (gaz fiyatınız çok düşük değilse böyledir, aksi takdirde aynı nonce değeri üzerine işlem yazmak için daha yüksek bir gaz fiyatı olan başka bir işlem göndermeniz gerekir). Gönderme ve dahil etme arasındaki aralıkta ne olacağını kontrol edemezsiniz.

1 ) internal virtual returns (uint amountA, uint amountB) {
2
📋 Kopyala

Fonksiyon, likidite sağlayıcısının rezervler arasındaki mevcut orana eşit bir orana sahip olması için yatırması gereken tutarları döndürür.

1 // create the pair if it doesn't exist yet
2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);
4 }
5
📋 Kopyala

Bu token çifti için henüz bir takas yoksa onu oluşturun.

1 (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
2
📋 Kopyala

Çiftteki mevcut rezervleri alın.

1 if (reserveA == 0 && reserveB == 0) {
2 (amountA, amountB) = (amountADesired, amountBDesired);
3
📋 Kopyala

Mevcut rezervler boşsa, bu yeni bir eş takasıdır. Yatırılacak tutarlar, likidite sağlayıcısının sağlamak istediği miktarlarla tamamen aynı olmalıdır.

1 } else {
2 uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
3
📋 Kopyala

Eğer miktarların ne olduğunu görmemiz gerekiyorsa, bu fonksiyonu kullanarak en uygun miktarı alırız. Mevcut rezervlerle aynı oranı istiyoruz.

1 if (amountBOptimal <= amountBDesired) {
2 require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
3 (amountA, amountB) = (amountADesired, amountBOptimal);
4
📋 Kopyala

amountBOptimal, likidite sağlayıcısının yatırmak istediği miktardan daha küçükse bu, B token'ının şu anda likidite yatırıcısının düşündüğünden daha değerli olduğu anlamına gelir, bu nedenle daha küçük bir miktar gereklidir.

1 } else {
2 uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
3 assert(amountAOptimal <= amountADesired);
4 require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
5 (amountA, amountB) = (amountAOptimal, amountBDesired);
6
📋 Kopyala

Optimal B miktarı, istenen B miktarından daha fazlaysa bu, B token'larının şu anda likidite yatıran kişinin düşündüğünden daha az değerli olduğu anlamına gelir, bu nedenle daha yüksek bir miktar gereklidir. Ancak istenilen miktar bir maksimum olduğu için bunu yapamıyoruz. Bunun yerine, istenen miktarda B token'ı için en uygun A token'ı sayısını hesaplıyoruz.

Hepsini bir araya getirdiğimizde bu grafiği elde ederiz. Bin A token'ı (mavi çizgi) ve bin B token'ı (kırmızı çizgi) yatırmaya çalıştığınızı varsayalım. X ekseni takas oranıdır, A/B. X=1 ise, değer olarak eşittirler ve her birinden bin tane yatırırsınız. X=2 ise A, B değerinin iki katıdır (her A token'ı için iki B token'ı alırsınız), bu nedenle bin B token'ı, ancak yalnızca 500 A token'ı yatırırsınız. X=0,5 ise, durum tersine çevrilir, bin A token'ı ve beş yüz B token'ı olur.

Çizelge

1 }
2 }
3 }
4
📋 Kopyala

Likiditeyi doğrudan ana sözleşmeye yatırabilirsiniz (UniswapV2Pair::mint kullanarak), ancak ana sözleşme yalnızca kendisinin aldatılmadığını kontrol eder, bu nedenle, işleminizi gönderdiğiniz zaman ile gerçekleştirildiği zaman arasında takas oranı değişirse değer kaybetme riskiyle karşı karşıya kalırsınız. Çevre sözleşmesini kullanırsanız, yatırmanız gereken tutarı hesaplar ve hemen yatırır, böylece takas oranı değişmez ve hiçbir şey kaybetmezsiniz.

1 function addLiquidity(
2 address tokenA,
3 address tokenB,
4 uint amountADesired,
5 uint amountBDesired,
6 uint amountAMin,
7 uint amountBMin,
8 address to,
9 uint deadline
10
Tümünü göster
📋 Kopyala

Bu fonksiyon, likidite yatırma işlemiyle çağrılabilir. Çoğu parametre, yukarıdaki _addLiquidity ile aynıdır. İki istisna bulunur:

. to, likidite sağlayıcısının havuzdaki payını göstermek için basılan yeni likidite token'larını alan adrestir. deadline işlemdeki bir zaman sınırıdır

1 ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
2 (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
3 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
4
📋 Kopyala

Fiili olarak yatırılacak tutarları hesaplıyoruz ve ardından likidite havuzunun adresini buluyoruz. Gazdan tasarruf etmek için bunu fabrikaya sorarak değil, pairFor kütüphane işlevini kullanarak yapıyoruz (aşağıdaki kütüphanelere bakın)

1 TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
2 TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
3
📋 Kopyala

Kullanıcıdan doğru miktarda token'ı eş takasına aktarın.

1 liquidity = IUniswapV2Pair(pair).mint(to);
2 }
3
📋 Kopyala

Karşılığında, havuzun kısmi sahipliği için to adresine likidite token'ları verin. Çekirdek sözleşmenin mint fonksiyonu, sahip olduğu ekstra token sayısını (son likiditenin değiştiği zamana kıyasla) görür ve buna göre likiditeyi basar.

1 function addLiquidityETH(
2 address token,
3 uint amountTokenDesired,
4
📋 Kopyala

Bir likidite sağlayıcısı bir Token/ETH eş takasına likidite sağlamak istediğinde, birkaç farklılık vardır. Sözleşme, likidite sağlayıcısı için ETH'yi paketler. Kullanıcının ne kadar ETH yatırmak istediğini belirtmeye gerek yoktur, çünkü kullanıcı bunları işlemle birlikte gönderir (tutar msg.value içinde mevcuttur).

1 uint amountTokenMin,
2 uint amountETHMin,
3 address to,
4 uint deadline
5 ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
6 (amountToken, amountETH) = _addLiquidity(
7 token,
8 WETH,
9 amountTokenDesired,
10 msg.value,
11 amountTokenMin,
12 amountETHMin
13 );
14 address pair = UniswapV2Library.pairFor(factory, token, WETH);
15 TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
16 IWETH(WETH).deposit{value: amountETH}();
17 assert(IWETH(WETH).transfer(pair, amountETH));
18
Tümünü göster
📋 Kopyala

ETH'yi yatırmak için sözleşme önce onu WETH olarak paketler ve ardından WETH'yi eşe aktarır. Aktarımın bir assert içinde paketlendiğini dikkate alın. Bu, aktarım başarısız olursa bu sözleşme çağrısının da başarısız olduğu ve bu nedenle paketleme işleminin gerçekten gerçekleşmediği anlamına gelir.

1 liquidity = IUniswapV2Pair(pair).mint(to);
2 // refund dust eth, if any
3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
4 }
5
📋 Kopyala

Kullanıcı bize ETH'yi zaten gönderdi, bu nedenle fazladan kalan varsa (çünkü diğer token kullanıcının düşündüğünden daha az değerlidir), bir geri ödeme yapmamız gerekir.

Likiditeyi Kaldırın

Bu işlevler likiditeyi ortadan kaldıracak ve likidite sağlayıcısına geri ödeme yapacaktır.

1 // **** REMOVE LIQUIDITY ****
2 function removeLiquidity(
3 address tokenA,
4 address tokenB,
5 uint liquidity,
6 uint amountAMin,
7 uint amountBMin,
8 address to,
9 uint deadline
10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
11
Tümünü göster
📋 Kopyala

Likidite kaldırmanın en basit hâli. Likidite sağlayıcısının almayı kabul ettiği her bir token'ın minimum miktarı vardır ve bu, son tarihten önce gerçekleşmelidir.

1 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
2 IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
4
📋 Kopyala

Çekirdek sözleşmenin burn işlevi, kullanıcıya token'ları geri ödemeyi gerçekleştirir.

1 (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
2
📋 Kopyala

Bir fonksiyon, sadece birkaç tanesiyle ilgilendiğimiz birçok değer döndürdüğünde, sadece istediğimiz değerleri şu şekilde elde ederiz. Gaz açısından bir değeri okuyup hiç kullanmamaktan biraz daha ucuzdur.

1 (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
2
📋 Kopyala

Tutarları, çekirdek sözleşmenin onları döndürdüğü biçimden (önce alt adres token'ı), kullanıcının beklediği biçime (tokenA ve tokenB'ye karşılık) çevirin.

1 require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
2 require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
3 }
4
📋 Kopyala

Önce aktarımı yapmak ve ardından meşru olduğunu doğrulamak sorun değil, çünkü değilse tüm durum değişikliklerinden geri döneceğiz.

1 function removeLiquidityETH(
2 address token,
3 uint liquidity,
4 uint amountTokenMin,
5 uint amountETHMin,
6 address to,
7 uint deadline
8 ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
9 (amountToken, amountETH) = removeLiquidity(
10 token,
11 WETH,
12 liquidity,
13 amountTokenMin,
14 amountETHMin,
15 address(this),
16 deadline
17 );
18 TransferHelper.safeTransfer(token, to, amountToken);
19 IWETH(WETH).withdraw(amountETH);
20 TransferHelper.safeTransferETH(to, amountETH);
21 }
22
Tümünü göster
📋 Kopyala

ETH için likiditeyi kaldırma, WETH token'larını almamız ve ardından bunları ETH'nin likidite sağlayıcısına geri vermesi için kullanmamız dışında neredeyse aynıdır.

1 function removeLiquidityWithPermit(
2 address tokenA,
3 address tokenB,
4 uint liquidity,
5 uint amountAMin,
6 uint amountBMin,
7 address to,
8 uint deadline,
9 bool approveMax, uint8 v, bytes32 r, bytes32 s
10 ) external virtual override returns (uint amountA, uint amountB) {
11 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
12 uint value = approveMax ? uint(-1) : liquidity;
13 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
14 (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
15 }
16
17
18 function removeLiquidityETHWithPermit(
19 address token,
20 uint liquidity,
21 uint amountTokenMin,
22 uint amountETHMin,
23 address to,
24 uint deadline,
25 bool approveMax, uint8 v, bytes32 r, bytes32 s
26 ) external virtual override returns (uint amountToken, uint amountETH) {
27 address pair = UniswapV2Library.pairFor(factory, token, WETH);
28 uint value = approveMax ? uint(-1) : liquidity;
29 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
30 (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
31 }
32
Tümünü göster
📋 Kopyala

Bu fonksiyonlar, izin mekanizmasını kullanarak, ether'ı olmayan kullanıcıların havuzdan çekilmesine izin vermek için meta-işlemleri iletir.

1
2 // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
3 function removeLiquidityETHSupportingFeeOnTransferTokens(
4 address token,
5 uint liquidity,
6 uint amountTokenMin,
7 uint amountETHMin,
8 address to,
9 uint deadline
10 ) public virtual override ensure(deadline) returns (uint amountETH) {
11 (, amountETH) = removeLiquidity(
12 token,
13 WETH,
14 liquidity,
15 amountTokenMin,
16 amountETHMin,
17 address(this),
18 deadline
19 );
20 TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
21 IWETH(WETH).withdraw(amountETH);
22 TransferHelper.safeTransferETH(to, amountETH);
23 }
24
25
Tümünü göster
📋 Kopyala

Bu fonksiyon, transfer veya depolama ücreti olan token'lar için kullanılabilir. Bir token'ın bu tür ücretleri olduğunda, token'ın ne kadarını geri aldığımızı bize söylemesi için removeLiquidity işlevine güvenemeyiz, bu nedenle önce çekmemiz ve sonra bakiyeyi almamız gerekir.

1
2
3 function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
4 address token,
5 uint liquidity,
6 uint amountTokenMin,
7 uint amountETHMin,
8 address to,
9 uint deadline,
10 bool approveMax, uint8 v, bytes32 r, bytes32 s
11 ) external virtual override returns (uint amountETH) {
12 address pair = UniswapV2Library.pairFor(factory, token, WETH);
13 uint value = approveMax ? uint(-1) : liquidity;
14 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
15 amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
16 token, liquidity, amountTokenMin, amountETHMin, to, deadline
17 );
18 }
19
Tümünü göster
📋 Kopyala

Son fonksiyon, depolama ücretlerini meta işlemlerle birleştirir.

Ticaret

1 // **** SWAP ****
2 // requires the initial amount to have already been sent to the first pair
3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
4
📋 Kopyala

Bu fonksiyon, ticaret yapanların maruz kaldığı fonksiyonlar için gerekli olan dahili işlemleri gerçekleştirir.

1 for (uint i; i < path.length - 1; i++) {
2
📋 Kopyala

Bunu yazdığım esnada 388.160 ERC-20 token'ı bulunmakta. Her bir token çifti için bir eş takası olsaydı, 150 milyardan fazla eş takası olurdu. Tüm zincir şu anda o sayının sadece %0,1'i kadar hesaba sahip. Bunun yerine, takas fonksiyonları bir yol kavramını destekler. Bir ticaret yapan kişi A'yı B'ye, B'yi C'ye ve C'yi D'ye çevirebilir, dolayısıyla doğrudan bir A-D çifti takasına gerek yoktur.

Bu piyasalardaki fiyatlar senkronize olma eğilimindedir, çünkü senkronize olmadıklarında arbitraj için bir fırsat yaratır. Örneğin A, B ve C olmak üzere üç token düşünün. Her çift için bir tane olmak üzere üç eş takası bulunuyor.

  1. Başlangıç durumu
  2. Ticaret yapan bir kişi 24,695 A token'ı satar ve 25,305 B token'ı alır.
  3. Ticaret yapan kişi 25,305 C token'ı karşılığında 24,695 B token'ı satar ve yaklaşık 0,61 B token'ını kâr olarak tutar.
  4. Daha sonra ticaret yapan kişi 25,305 A token'ı için 24,695 C token'ı satar ve yaklaşık 0,61 C token'ını kâr olarak tutar. Ticaret yapan kişi ayrıca 0,61 fazladan A token'ına sahiptir (ticaret yapan kişinin sonunda elde ettiği 25,305 eksi 24,695 orijinal yatırım).
AdımA-B TakasıB-C TakasıA-C Takası
1A:1000 B:1050 A/B=1,05B:1000 C:1050 B/C=1,05A:1050 C:1000 C/A=1,05
2A:1024.695 B:1024.695 A/B=1B:1000 C:1050 B/C=1,05A:1050 C:1000 C/A=1,05
3A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1050 C:1000 C/A=1.05
4A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1024.695 C:1024.695 C/A=1
1 (address input, address output) = (path[i], path[i + 1]);
2 (address token0,) = UniswapV2Library.sortTokens(input, output);
3 uint amountOut = amounts[i + 1];
4
📋 Kopyala

Şu anda işlemekte olduğumuz çifti alın, sıralayın (çift ile kullanım için) ve beklenen çıktı miktarını alın.

1 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
2
📋 Kopyala

Beklenen miktarları alın, eş takasının beklediği şekilde sıralayın.

1 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
2
📋 Kopyala

Bu son takas mı? Eğer öyleyse ticaretten alınan token'ları hedefe gönderin. Değilse, bir sonraki eş takasına gönderin.

1
2 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
3 amount0Out, amount1Out, to, new bytes(0)
4 );
5 }
6 }
7
📋 Kopyala

Token'ları takas etmek için eş takasını gerçekten çağırın. Değişim hakkında bilgi almak için bir geri çağrıya ihtiyacımız yok, bu yüzden o alana herhangi bir bayt göndermiyoruz.

1 function swapExactTokensForTokens(
2
📋 Kopyala

Bu fonksiyon, doğrudan ticaret yapanlar tarafından bir token'ı başka bir token'la değiştirmek için kullanılır.

1 uint amountIn,
2 uint amountOutMin,
3 address[] calldata path,
4
📋 Kopyala

Bu parametre ERC-20 sözleşmelerinin adreslerini içerir. Yukarıda açıklandığı gibi, sahip olduğunuz varlıktan istediğiniz varlığa ulaşmak için birkaç eş takasından geçmeniz gerekebileceği için bu bir dizidir.

Solidity'de bir fonksiyon parametresi ya memory ya da calldata olarak depolanabilir. Fonksiyon, doğrudan bir kullanıcıdan (bir işlem kullanılarak) veya farklı bir sözleşmeden çağrılan sözleşmeye bir giriş noktasıysa, parametrenin değeri doğrudan çağrı verilerinden alınabilir. Yukarıdaki _swap gibi fonksiyon dahili olarak çağrılırsa, parametrelerin memory içinde saklanması gerekir. Çağrılan sözleşmenin bakış açısından calldata salt okunurdur.

uint veya address gibi skaler türlerde depolama seçimini bizim için derleyici halleder, ancak daha uzun ve daha pahalı olan dizilerde kullanılacak depolama türünü belirtiriz.

1 address to,
2 uint deadline
3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
4
📋 Kopyala

Dönen değerler her zaman bellekte döndürülür.

1 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
2 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
3
📋 Kopyala

Her takasta satın alınacak tutarı hesaplayın. Sonuç, ticaret yapanın kabul etmeye istekli olduğu minimum değerden düşükse, işlemden geri dönün.

1 TransferHelper.safeTransferFrom(
2 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
3 );
4 _swap(amounts, path, to);
5 }
6
📋 Kopyala

Son olarak, ilk ERC-20 token'ını ilk eş takası için hesaba aktarın ve _swap'i çağırın. Bunların hepsi aynı işlemde olduğu için eş takası, beklenmeyen token'ların bu transferin bir parçası olduğunu biliyor.

1 function swapTokensForExactTokens(
2 uint amountOut,
3 uint amountInMax,
4 address[] calldata path,
5 address to,
6 uint deadline
7 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
8 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
9 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
10 TransferHelper.safeTransferFrom(
11 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
12 );
13 _swap(amounts, path, to);
14 }
15
Tümünü göster
📋 Kopyala

Önceki fonksiyon olan swapTokensForTokens, bir ticaret yapanın vermek istediği girdi token'larının tam sayısını ve karşılığında almak istediği minimum çıktı token'ları sayısını belirlemesine olanak tanır. Bu fonksiyon ters takası yapar, ticaret yapan bir kişinin istediği çıktı token'larının sayısını ve onlar için ödemek istediği maksimum girdi token'larının sayısını belirlemesine izin verir.

Her iki durumda da, ticaret yapan kişinin önce bu çevre sözleşmesine, onları transfer etmesine izin vermek için bir ödenek vermesi gerekir.

1 function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
2 external
3 virtual
4 override
5 payable
6 ensure(deadline)
7 returns (uint[] memory amounts)
8 {
9 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
10 amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
11 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
12 IWETH(WETH).deposit{value: amounts[0]}();
13 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
14 _swap(amounts, path, to);
15 }
16
17
18 function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
19 external
20 virtual
21 override
22 ensure(deadline)
23 returns (uint[] memory amounts)
24 {
25 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
26 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
27 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
28 TransferHelper.safeTransferFrom(
29 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
30 );
31 _swap(amounts, path, address(this));
32 IWETH(WETH).withdraw(amounts[amounts.length - 1]);
33 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
34 }
35
36
37
38 function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
39 external
40 virtual
41 override
42 ensure(deadline)
43 returns (uint[] memory amounts)
44 {
45 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
46 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
47 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
48 TransferHelper.safeTransferFrom(
49 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
50 );
51 _swap(amounts, path, address(this));
52 IWETH(WETH).withdraw(amounts[amounts.length - 1]);
53 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
54 }
55
56
57 function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
58 external
59 virtual
60 override
61 payable
62 ensure(deadline)
63 returns (uint[] memory amounts)
64 {
65 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
66 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
67 require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
68 IWETH(WETH).deposit{value: amounts[0]}();
69 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
70 _swap(amounts, path, to);
71 // refund dust eth, if any
72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
73 }
74
Tümünü göster
📋 Kopyala

Bu dört varyantın tümü, ETH ve token'lar arasındaki ticareti içerir. Tek fark, ya ticaret yapandan ETH alıp WETH basmak için kullanmamız ya da yoldaki son değişimden WETH alıp yakarak ticaret yapana ortaya çıkan ETH'yi geri göndermemizdir.

1 // **** SWAP (supporting fee-on-transfer tokens) ****
2 // requires the initial amount to have already been sent to the first pair
3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
4
📋 Kopyala

Bu, (bu sorunu) çözmek için aktarım veya depolama ücretleri olan token'ları takas eden dahili fonksiyondur.

1 for (uint i; i < path.length - 1; i++) {
2 (address input, address output) = (path[i], path[i + 1]);
3 (address token0,) = UniswapV2Library.sortTokens(input, output);
4 IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
5 uint amountInput;
6 uint amountOutput;
7 { // scope to avoid stack too deep errors
8 (uint reserve0, uint reserve1,) = pair.getReserves();
9 (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
10 amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
11 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
12
Tümünü göster
📋 Kopyala

Transfer ücretleri nedeniyle, her transferden ne kadar kazandığımızı bize söylemesi için getAmountsOut fonksiyonuna güvenemeyiz (orijinal _swap'u çağırmadan önce yaptığımız gibi). Bunun yerine önce transfer etmemiz ve sonra kaç token aldığımızı görmemiz gerekiyor.

Not: Teoride, tek başına bu işlevi _swap yerine kullanabilirdik ancak bazı durumlarda (örneğin, gerekli minimum değeri karşılamak için sonunda yeterli olmadığı için aktarım geri alınırsa) bu daha fazla gaza mal olur. Transfer ücreti token'ları oldukça nadirdir, bu nedenle onları barındırmamız gerekse de, en az birinden geçtiklerini varsaymak için tüm takaslara gerek yoktur.

1 }
2 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
3 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
4 pair.swap(amount0Out, amount1Out, to, new bytes(0));
5 }
6 }
7
8
9 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
10 uint amountIn,
11 uint amountOutMin,
12 address[] calldata path,
13 address to,
14 uint deadline
15 ) external virtual override ensure(deadline) {
16 TransferHelper.safeTransferFrom(
17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
18 );
19 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
20 _swapSupportingFeeOnTransferTokens(path, to);
21 require(
22 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
23 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
24 );
25 }
26
27
28 function swapExactETHForTokensSupportingFeeOnTransferTokens(
29 uint amountOutMin,
30 address[] calldata path,
31 address to,
32 uint deadline
33 )
34 external
35 virtual
36 override
37 payable
38 ensure(deadline)
39 {
40 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
41 uint amountIn = msg.value;
42 IWETH(WETH).deposit{value: amountIn}();
43 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
44 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
45 _swapSupportingFeeOnTransferTokens(path, to);
46 require(
47 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
48 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
49 );
50 }
51
52
53 function swapExactTokensForETHSupportingFeeOnTransferTokens(
54 uint amountIn,
55 uint amountOutMin,
56 address[] calldata path,
57 address to,
58 uint deadline
59 )
60 external
61 virtual
62 override
63 ensure(deadline)
64 {
65 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
66 TransferHelper.safeTransferFrom(
67 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
68 );
69 _swapSupportingFeeOnTransferTokens(path, address(this));
70 uint amountOut = IERC20(WETH).balanceOf(address(this));
71 require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
72 IWETH(WETH).withdraw(amountOut);
73 TransferHelper.safeTransferETH(to, amountOut);
74 }
75
Tümünü göster
📋 Kopyala

Bunlar, normal token'lar için kullanılanlarla aynı varyantlardır, ancak bunun yerine _swapSupportingFeeOnTransferTokens çağırırlar.

1 // **** LIBRARY FUNCTIONS ****
2 function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
3 return UniswapV2Library.quote(amountA, reserveA, reserveB);
4 }
5
6 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
7 public
8 pure
9 virtual
10 override
11 returns (uint amountOut)
12 {
13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
14 }
15
16 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
17 public
18 pure
19 virtual
20 override
21 returns (uint amountIn)
22 {
23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
24 }
25
26 function getAmountsOut(uint amountIn, address[] memory path)
27 public
28 view
29 virtual
30 override
31 returns (uint[] memory amounts)
32 {
33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);
34 }
35
36 function getAmountsIn(uint amountOut, address[] memory path)
37 public
38 view
39 virtual
40 override
41 returns (uint[] memory amounts)
42 {
43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);
44 }
45}
46
Tümünü göster
📋 Kopyala

Bu fonksiyonlar yalnızca UniswapV2Library fonksiyonlarını çağıran proxy'lerdir.

UniswapV2Migrator.sol

Bu sözleşme, borsaları eski v1'den v2'ye taşımak için kullanıldı. Artık taşındıklarına için geçerli değildir.

Kütüphaneler

SafeMath kütüphanesi iyi belgelenmiştir, dolayısıyla burada belgelemeye gerek yoktur.

Math

Bu kütüphane, normalde Solidity kodunda ihtiyaç duyulmayan bazı matematik fonksiyonlarını içerir, dolayısıyla bunlar dilin bir parçası değildir.

1pragma solidity =0.5.16;
2
3// a library for performing various math operations
4
5library Math {
6 function min(uint x, uint y) internal pure returns (uint z) {
7 z = x < y ? x : y;
8 }
9
10 // babylonian method (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
11 function sqrt(uint y) internal pure returns (uint z) {
12 if (y > 3) {
13 z = y;
14 uint x = y / 2 + 1;
15
Tümünü göster
📋 Kopyala

Karekökten daha yüksek bir tahmin olarak x ile başlayın (1-3'ü özel durumlar olarak ele almamızın nedeni budur).

1 while (x < z) {
2 z = x;
3 x = (y / x + x) / 2;
4
📋 Kopyala

Önceki tahminin ortalamasını ve karekökünü bulmaya çalıştığımız sayının önceki tahmine bölümü ile daha yakın bir tahmin alın. Yeni tahmin, mevcut tahminden daha düşük olmayana kadar tekrarlayın. Daha fazla detay için, buraya bakın.

1 }
2 } else if (y != 0) {
3 z = 1;
4
📋 Kopyala

Sıfırın kareköküne asla ihtiyacımız olmamalı. Bir, iki ve üçün karekökleri kabaca birdir (tamsayıları kullanır, bu yüzden kesri yok sayarız).

1 }
2 }
3}
4
📋 Kopyala

Sabit Nokta Kesirleri (UQ112x112)

Bu kütüphane normalde Ethereum aritmetiğinin parçası olmayan kesirleri işler. Bunu x sayısını x*2^112 olarak şifreleyerek yapar. Bu, orijinal toplama ve çıkarma işlem kodlarını değişiklik yapmadan kullanmamızı sağlar.

1pragma solidity =0.5.16;
2
3// a library for handling binary fixed point numbers (https://wikipedia.org/wiki/Q_(number_format))
4
5// range: [0, 2**112 - 1]
6// resolution: 1 / 2**112
7
8library UQ112x112 {
9 uint224 constant Q112 = 2**112;
10
Tümünü göster
📋 Kopyala

Q112 birin şifrelemesidir.

1 // encode a uint112 as a UQ112x112
2 function encode(uint112 y) internal pure returns (uint224 z) {
3 z = uint224(y) * Q112; // never overflows
4 }
5
📋 Kopyala

Y uint112 olduğundan, en fazla 2^112-1 olabilir. Bu sayı hâlâ UQ112x112 olarak şifrelenebilir.

1 // divide a UQ112x112 by a uint112, returning a UQ112x112
2 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
3 z = x / uint224(y);
4 }
5}
6
📋 Kopyala

Eğer iki UQ112x112 değerini bölersek, sonuç artık 2^112 tarafından çarpılmaz. Bunun yerine payda için bir tamsayı alıyoruz. Çarpma yapmak için benzer bir numara kullanmamız gerekirdi, ancak UQ112x112 değerlerinin çarpımını yapmamıza gerek yok.

UniswapV2Library

Bu kütüphane yalnızca çevre sözleşmeleri tarafından kullanılır

1pragma solidity >=0.5.0;
2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
4
5import "./SafeMath.sol";
6
7library UniswapV2Library {
8 using SafeMath for uint;
9
10 // returns sorted token addresses, used to handle return values from pairs sorted in this order
11 function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
12 require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
13 (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
14 require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
15 }
16
Tümünü göster
📋 Kopyala

İki token'ı adrese göre sıralayın, böylece onlar için eş takasının adresini alabiliriz. Bu, bir yerine iki takasa yol açacak şekilde biri A,B parametreleri için diğeri B,A parametreleri için olmak üzere iki olasılık ortaya çıkaracağı için zorunludur.

1 // calculates the CREATE2 address for a pair without making any external calls
2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
3 (address token0, address token1) = sortTokens(tokenA, tokenB);
4 pair = address(uint(keccak256(abi.encodePacked(
5 hex'ff',
6 factory,
7 keccak256(abi.encodePacked(token0, token1)),
8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
9 ))));
10 }
11
Tümünü göster
📋 Kopyala

Bu fonksiyon, iki token için eş takasının adresini hesaplar. Bu sözleşme CREATE2 opcode kullanılarak oluşturulur, bu yüzden kullandığı parametreleri biliyorsak adresi aynı algoritmayı kullanarak hesaplayabiliriz. Bu, fabrikaya sormaktan çok daha ucuzdur.

1 // fetches and sorts the reserves for a pair
2 function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
3 (address token0,) = sortTokens(tokenA, tokenB);
4 (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
5 (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
6 }
7
📋 Kopyala

Bu fonksiyon, eş takasının sahip olduğu iki token'ın rezervlerini döndürür. Token'ları her iki sırayla alabileceğini ve bunları dahili kullanım için sıralayabileceğini unutmayın.

1 // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
2 function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
3 require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
4 require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
5 amountB = amountA.mul(reserveB) / reserveA;
6 }
7
📋 Kopyala

Bu fonksiyon, herhangi bir ücret yoksa A token'ı karşılığında alacağınız B token'ı miktarını verir. Bu hesaplama, transferin takas oranını değiştirdiğini dikkate alır.

1 // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
3
📋 Kopyala

Yukarıdaki quote işlevi, eş takasını kullanmak için herhangi bir ücret yoksa harika çalışır. Ancak, %0,3'lük bir takas ücreti varsa, gerçekte aldığınız miktar daha düşüktür. Bu fonksiyon, takas ücretinden sonraki tutarı hesaplar.

1
2 require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
3 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
4 uint amountInWithFee = amountIn.mul(997);
5 uint numerator = amountInWithFee.mul(reserveOut);
6 uint denominator = reserveIn.mul(1000).add(amountInWithFee);
7 amountOut = numerator / denominator;
8 }
9
📋 Kopyala

Solidity kesirleri yerel olarak işlemediği için miktarı doğrudan 0,997 ile çarpamayız. Bunun yerine, aynı etkiyi elde etmek için payı 997 ve paydayı 1000 ile çarparız.

1 // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
2 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
3 require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
4 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
5 uint numerator = reserveIn.mul(amountOut).mul(1000);
6 uint denominator = reserveOut.sub(amountOut).mul(997);
7 amountIn = (numerator / denominator).add(1);
8 }
9
📋 Kopyala

Bu fonksiyon kabaca aynı şeyi yapar ancak çıktı miktarını alır ve girdi sağlar.

1
2 // performs chained getAmountOut calculations on any number of pairs
3 function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
4 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
5 amounts = new uint[](path.length);
6 amounts[0] = amountIn;
7 for (uint i; i < path.length - 1; i++) {
8 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
9 amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
10 }
11 }
12
13 // performs chained getAmountIn calculations on any number of pairs
14 function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
15 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
16 amounts = new uint[](path.length);
17 amounts[amounts.length - 1] = amountOut;
18 for (uint i = path.length - 1; i > 0; i--) {
19 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
20 amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
21 }
22 }
23}
24
Tümünü göster
📋 Kopyala

Bu iki fonksiyon, birkaç eş takasından geçmek gerektiğinde değerleri tanımlamayı sağlar.

Transfer Yardımcısı

Bu kütüphane, bir geri döndürmeyi ve bir false değeri döndürmeyi aynı şekilde işlemek için ERC-20 ve Ethereum transferleri kapsamında başarı kontrolleri ekler.

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3pragma solidity >=0.6.0;
4
5// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
6library TransferHelper {
7 function safeApprove(
8 address token,
9 address to,
10 uint256 value
11 ) internal {
12 // bytes4(keccak256(bytes('approve(address,uint256)')));
13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
14
15
Tümünü göster
📋 Kopyala

İki yoldan biriyle farklı bir sözleşme çağırabiliriz:

  • Bir fonksiyon çağrısı oluşturmak için bir arayüz tanımı kullanın
  • Çağrıyı "manuel olarak" oluşturmak için uygulama ikili arayüzünü (ABI) kullanın. Kodun yazarı bunu yapmaya karar vermişti.
1 require(
2 success && (data.length == 0 || abi.decode(data, (bool))),
3 'TransferHelper::safeApprove: approve failed'
4 );
5 }
6
📋 Kopyala

ERC-20 standardından önce oluşturulmuş token'la geriye dönük uyumluluk sağlaması adına, bir ERC-20 çağrısı geri döndürülerek (bu durumda success, false olur) veya başarılı olup bir false değeri döndürerek (bu durumda çıktı verileri vardır ve verinin kodunu bir boole olarak çözerseniz false alırsınız) başarısız olabilir.

1
2
3 function safeTransfer(
4 address token,
5 address to,
6 uint256 value
7 ) internal {
8 // bytes4(keccak256(bytes('transfer(address,uint256)')));
9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
10 require(
11 success && (data.length == 0 || abi.decode(data, (bool))),
12 'TransferHelper::safeTransfer: transfer failed'
13 );
14 }
15
Tümünü göster
📋 Kopyala

Bu fonksiyon, ERC-20'nin transfer işlevselliğini uygular ve bu, bir hesabın farklı bir hesap tarafından sağlanan ödeneği harcamasına izin verir.

1
2 function safeTransferFrom(
3 address token,
4 address from,
5 address to,
6 uint256 value
7 ) internal {
8 // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
10 require(
11 success && (data.length == 0 || abi.decode(data, (bool))),
12 'TransferHelper::transferFrom: transferFrom failed'
13 );
14 }
15
Tümünü göster
📋 Kopyala

Bu fonksiyon, ERC-20'nin transferFrom işlevselliğini uygular ve bu, bir hesabın farklı bir hesap tarafından sağlanan ödeneği harcamasına izin verir.

1
2 function safeTransferETH(address to, uint256 value) internal {
3 (bool success, ) = to.call{value: value}(new bytes(0));
4 require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
5 }
6}
7
📋 Kopyala

Bu fonksiyon, ether'ı bir hesaba aktarır. Farklı bir sözleşmeye yapılan herhangi bir çağrı, ether göndermeyi deneyebilir. Aslında herhangi bir fonksiyonu çağırmamız gerekmediğinden, çağrıyla birlikte herhangi bir veri göndermiyoruz.

Sonuç

Bu yaklaşık 50 sayfalık uzun bir makaledir. Buraya kadar varabildiyseniz tebrikler! Umarım şimdiye kadar gerçek hayatta bir uygulama yazarken (kısa örnek programların aksine) dikkate alınması gereken hususları anlamışsınızdır ve kendi kullanım alanlarınız için sözleşmeler yazabilme konusunda daha iyisinizdir.

Şimdi faydalı bir şeyler yazarak bizi büyüleyin.

Son düzenleme: , Invalid DateTime
Sayfayı düzenle

Bu sayfa yararlı oldu mu?