Ana içeriğe geç

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

solidity
Orta düzey
Ori Pomerantz
1 Mayıs 2021
51 dakikalık okuma minute read

Giriş

Uniswap v2(opens in a new tab) herhangi iki ERC-20 token'ı arasında bir takas piyasası oluşturabilir. Bu yazıda bu protokolü uygulayan sözleşmelerin kaynak kodunu inceleyecek 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 jeton sağlar (bunlara Jeton0 ve Jeton1 diyeceğiz). Karşılığında, havuzun kısmi sahipliğini temsil eden ve likidite jetonu adı verilen üçüncü bir jeton alırlar.

Ticaret yapanlar, havuza bir tür jeton gönderir ve likidite sağlayıcıları tarafından sağlanan havuzdan diğer jetonu alır (örneğin, Jeton0 gönderir ve Jeton1 alır). Takas oranı, havuzun sahip olduğu Jeton0'lar ve Jeton1'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 jetonlarını yakabilir ve ödül payları da dahil olmak üzere jetonlarını geri alabilir.

Daha geniş çaplı bir açıklama için buraya tıklayın(opens in a new tab).

Neden v2? Neden v3 değil?

Uniswap v3(opens in a new tab), v2'den çok daha karmaşık bir yükseltmedir. Önce v2'yi öğrenip 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 jeton miktarını hesaplayın. Yeni jetonların mevcut jetonlara oranının aynı olması için bunun her iki jeton için aynı değer olması gerekir.
  3. Tutarların kabul edilebilir olup olmadığını kontrol edin (çağıranlar, altında likidite eklemek istemeyecekleri bir minimum tutar belirtebilir)
  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 jetonları hedef adrese yakılmış jetonlarla 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 jetonları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(opens in a new tab), jetonları 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';
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 {
Kopyala

Bu sözleşme, likidite jetonları için ERC-20 fonksiyonlarını sağlayan UniswapV2ERC20'den devralır.

1 using SafeMath for uint;
Kopyala

SafeMath kütüphanesi(opens in a new tab), taşmaları ve yetersizlikleri önlemek için kullanılır. Bu, 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;
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;
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 jetonu mevcuttur. O sayı MINIMUM_LIQUIDITY, yani bindir.

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

Bu, ERC-20 transfer fonksiyonu için ABI seçicisidir. Bu, ERC-20 jetonlarını iki jeton hesabına aktarmak için kullanılır.

1 address public factory;
Kopyala

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

1 address public token0;
2 address public token1;
Kopyala

Bu havuz tarafından takas edilebilecek iki tür ERC-20 jetonu için sözleşme adresleri mevcuttur.

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

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

1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
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;
Kopyala

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

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

Eş takasının jeton0 ile jeton1 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ı jeton 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;
Kopyala

Yeniden giriş istismarı(opens in a new tab) ü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 olduğumuzda fonksiyonların çalışırken (aynı işlem içinde) çağrılmasını önleyebiliriz.

1 modifier lock() {
Kopyala

Bu fonksiyon bir niteleyicidir(opens in a new tab), yani normal bir fonksiyonun davranışını bir şekilde değiştirmek için onu paketler.

1 require(unlocked == 1, 'UniswapV2: LOCKED');
2 unlocked = 0;
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 _;
Kopyala

Bir niteleyicide _; orijinal fonksiyon çağrısıdır (tüm parametrelerle birlikte). Burada, fonksiyon ç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 }
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 }
Kopyala

Bu fonksiyon, çağıranlara takasın mevcut durumunu sağlar. Solidity fonksiyonlarının birden fazla değer döndürebildiğini(opens in a new tab) 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));
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).

Jeton fonksiyonu için bir arayüzü içe aktarmak zorunda kalmak istemiyorsak çağrıyı ABI fonksiyonlarından(opens in a new tab) birini kullanarak "manuel olarak" oluştururuz.

1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
2 }
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);
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önderici ile aynı olmayabilecek jetonları 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 );
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);
Kopyala

Son olarak, nedenden bağımsız olarak en son rezerv bilgilerini (ve dolayısıyla takas oranını) sağlamak için jetonlar 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 }
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 }
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 {
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');
Kopyala

Balance0 veya balance1 (uint256), uint112(-1) (=2^112-1) değerinden yüksekse (böylece uint112'ye dönüştürüldüğünde taşar ve 0'a geri döner) 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 jetonları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) {
Kopyala

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

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 }
Kopyala

Her maliyet biriktirici, son ücret ve (diğer jetonun rezervi/bu jetonun rezervi) saniye cinsinden geçen sürenin çarpımı ile güncellenir. Ortalama bir fiyat elde etmek için kümülatif fiyatı zaman içinde iki noktada 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 }
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) {
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 likidite havuza eklendiğinde veya havuzdan çıkarıldığında hesaplanır.

1 address feeTo = IUniswapV2Factory(factory).feeTo();
2 feeOn = feeTo != address(0);
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
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) {
Kopyala

Likidite sağlayıcıları, likidite token'larının değer kazanmasıyla paylarını alırlar. Ancak protokol ücreti, yeni likidite jetonları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) {
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;
Kopyala

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

1 if (liquidity > 0) _mint(feeTo, liquidity);
2 }
3 }
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 }
Kopyala

Herhangi bir ücret yoksa kLast öğesini sıfıra ayarlayın (zaten değilse). Bu sözleşme yazıldığında, sözleşmeleri ihtiyaç duymadıkları depolama alanını sıfırlayarak Ethereum durumunun genel boyutunu küçültmeye teşvik eden bir gaz iadesi özelliği(opens in a new tab) 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) {
Kopyala

Bu fonksiyon, bir likidite sağlayıcısı havuza likidite eklediğinde çağrılır. Ödül olarak ek likidite jetonları 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 gönderemez) çağrılmalıdır.

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
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);
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);
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
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 geri 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 jetonun 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 jetonun 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 jetonun değeri aynı, ancak yatıran kişimiz Jeton0'a göre dört kat daha fazla Jeton1 yatırdı. Bir tacir, eş takasının Jeton0'ın daha değerli olduğunu düşünmesi 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);
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 jetonlu, üç iyi yatırma ve bir kötü yatırma bulunan başka bir örnek (yalnızca bir jeton türünün yatırılması, bu nedenle herhangi bir likidite jetonu ü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);
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 }
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) {
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)];
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');
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
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 {
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
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(opens in a new tab) 26. sayfasındaki 298. denkleme bakın.

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
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);
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 }
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');
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 }
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 jeton aktarabilir.

Bu durumda iki çözüm var:

  • sync, rezervleri mevcut bakiyelere güncelleyin
  • skim, fazladan miktarı çekin. Jetonları 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}
Tümünü göster
Kopyala

UniswapV2Factory.sol

Bu sözleşme(opens in a new tab) 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;
Kopyala

Bu durum değişkenleri protokol ücretini uygulamak için gereklidir (bkz. teknik rapor(opens in a new tab), 5. sayfa). feeTo adresi, protokol ücreti için likidite jetonlarını biriktirir ve feeToSetter, feeTo'un farklı bir adresle değiştirilmesine olanak tanıyan adrestir.

1 mapping(address => mapping(address => address)) public getPair;
2 address[] public allPairs;
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 jetonunu temel alan eş takası sözleşmesini tanımlayan bir eşleştirmedir. ERC-20 jetonları, 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 kadar az değiştirirsek o kadar iyidir. Yinelemeyi destekleyen eşleştirmeler(opens in a new tab) oluşturabilirsiniz, ancak bunlar anahtar listesi için ekstra depolama gerektirir. Çoğu uygulamada buna ihtiyacınız yoktur.

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

Bu olay, yeni bir eş takası oluşturulduğunda yayınlanır. Jetonları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 }
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 }
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) {
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 takas çifti 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);
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 yapabilmek için onları aldığımız sıraya bakmaksızın jeton adreslerinin tutarlı bir sırasına sahip olmamız gerekir, bu yüzden de 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
Kopyala

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

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

Yeni bir sözleşme oluşturmak için onu oluşturan koda ihtiyacımız vardır (hem oluşturucu fonksiyon hem de gerçek sözleşmenin EVM bit 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 işlem kodunu(opens in a new tab) kullanmamız gerekir. Bu kod yazıldığında işlem kodu henüz Solidity tarafından desteklenmediği için kodu manuel olarak almak gerekiyordu. Bu artık bir sorun değil, çünkü Solidity artık CREATE2'yi destekliyor(opens in a new tab).

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

Bir opcode henüz Solidity tarafından desteklenmediğinde onu satır içi derleme(opens in a new tab) kullanarak çağırabiliriz.

1 IUniswapV2Pair(pair).initialize(token0, token1);
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 }
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}
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(opens in a new tab), ERC-20 likidite jetonunu uygular. Bu sözleşme OpenZeppelin 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 jetonlarınız varsa ancak ETH'niz yoksa işlem gönderemez, yani onlarla hiçbir şey yapamazsınız. Bu sorundan kaçınmanın bir yolu meta-işlemlerdir(opens in a new tab). Jetonların sahibi, bir başkasının jetonları zincirden çekmesine ve de interneti kullanarak alıcıya göndermesine izin veren bir işlemi 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;
Kopyala

Bu hash değeri, işlem türü için tanımlayıcıdır(opens in a new tab). Bu parametrelerle burada desteklediğimiz tek şey Permit'tir.

1 mapping(address => uint) public nonces;
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ı(opens in a new tab) biçimidir). Bunu önlemek için, bir nonce(opens in a new tab) kullanırız. Yeni bir Permit'in nonce değeri son kullanılandan bir fazla değilse, geçersiz olduğunu varsayarız.

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

Bu, zincir tanımlayıcısını(opens in a new tab) almaya yarayan koddur. Yul(opens in a new tab) denilen bir EVM derleme biçemi 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 }
Tümünü göster
Kopyala

EIP-712 için alan adı ayırıcısını(opens in a new tab) hesapla.

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

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

1 require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
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 );
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 yoktur.

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);
Kopyala

Özetten ve imzadan, ecrecover(opens in a new tab) kullanarak onu imzalayan adresi alabiliriz.

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

Her şey tamamsa bunu bir ERC-20 onayı(opens in a new tab) 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 çağırabilirsiniz ancak bu daha karmaşıktır ve bir hata yaparsanız değer kaybedebilirsiniz. Çekirdek sözleşmeler, başkaları için doğruluk testi yapmaya değil yalnızca bu kişilerin aldatılmadıklarından emin olmaya yönelik testler içerir. Bunlar, gerektiğinde güncellenebilmeleri için çevrededir.

UniswapV2Router01.sol

Bu sözleşmenin(opens in a new tab) sorunları vardır ve artık kullanılmamalıdır(opens in a new tab). Neyse ki çevre sözleşmeleri 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(opens in a new tab) aracılığıyla kullanırsınız. Nasıl kullanacağınızı burada(opens in a new tab) 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';
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 jetonu için takasa izin verir ancak ether'in (ETH) kendisi bir ERC-20 jetonu değildir. Standarttan öncesine tarihlidir ve benzersiz mekanizmalar ile aktarılır. ERC-20 jetonları için geçerli olan sözleşmelerde ETH kullanımını etkinleştirmek için insanlar paketlenmiş ether (WETH)(opens in a new tab) sözleşmesini bulmuştur. 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;
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(opens in a new tab), yani sadece oluşturucuda ayarlanabilir. 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 }
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 }
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 }
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 yapmak için yetkilidir.

Likidite Ekleyin

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

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

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

1 address tokenA,
2 address tokenB,
Kopyala

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

1 uint amountADesired,
2 uint amountBDesired,
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ını belirtir.

1 uint amountAMin,
2 uint amountBMin
Kopyala

Bunlar, yatırmak için kabul edilebilir minimum tutarlardır. Bu tutarlar veya daha fazlası ile gerçekleşemezse, işlemi geri alı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 olmaması, onları göndermeniz ve sonunda bir madencinin bunları bir bloğa dahil etmesidir (gaz fiyatınız çok düşükse bu durumda, aynı nonce'un üzerine yazmak için daha yüksek bir gaz fiyatı ile başka bir işlem göndermeniz gerekir). Gönderme ile dahil etme arasındaki aralıkta ne olacağını kontrol edemezsiniz.

1 ) internal virtual returns (uint amountA, uint amountB) {
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 }
Kopyala

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

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

Çiftteki mevcut rezervleri alın.

1 if (reserveA == 0 && reserveB == 0) {
2 (amountA, amountB) = (amountADesired, amountBDesired);
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);
Kopyala

Eğer miktarların ne olduğunu görmemiz gerekiyorsa, bu fonksiyonu(opens in a new tab) kullanarak en uygun miktarı buluruz. Mevcut rezervlerle aynı oranı istiyoruz.

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

Eğer amountBOptimal, likidite sağlayıcısının yatırmak istediği miktardan daha küçükse, bu, B jetonunun ş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);
Kopyala

Optimal B miktarı, istenen B miktarından daha fazlaysa bu durum, B jetonlarının şu anda likidite yatıran kişinin düşündüğünden daha az değerli olduğu anlamına gelir; bu nedenle de 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 jetonu (mavi çizgi) ve bin B jetonu (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. A x=2 ise B değerinin iki katıdır (her A jetonu için iki B jetonu alırsınız), bu nedenle bin B jetonu ile ancak 500 A jetonu yatırırsınız. X=0,5 ise, durum tersine çevrilir, bin A token'ı ve beş yüz B token'ı olur.

Çizelge

Likiditeyi doğrudan ana sözleşmeye yatırabilirsiniz (UniswapV2Pair::mint(opens in a new tab) 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, bu sözleşme yatırmanız gereken tutarı hesaplar ve hemen yatırır; 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
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);
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 yaparız (aşağıdaki kütüphanelere bakın)

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

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

1 liquidity = IUniswapV2Pair(pair).mint(to);
2 }
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 jeton 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,
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 (miktarmsg.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));
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. Transferin bir assert içinde paketlendiğini dikkate alın. Bu, transfer 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 }
Kopyala

Kullanıcı bize ETH'yi zaten gönderdi, bu nedenle fazladan kalan varsa (çünkü diğer jeton 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) {
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 jeton için bir 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);
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);
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);
Kopyala

Tutarları, çekirdek sözleşmenin onları döndürdüğü biçimden (önce alt adres jetonu), 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 }
Kopyala

Önce aktarımı yapmak ve ardından yasal olduğunu doğrulamak sorun değildir, çünkü yasal değilse tüm durum değişikliklerini geri alacağız.

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 }
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 }
Tümünü göster
Kopyala

Bu fonksiyonlar, izin mekanizmasını kullanarak, ether'i 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
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 }
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 {
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++) {
Kopyala

Bunu yazdığım esnada 388.160 ERC-20 token'ı(opens in a new tab) bulunmakta. Her bir jeton ç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 sahiptir(opens in a new tab). Bunun yerine, bir yol kavramını takas fonksiyonları destekler. Bir tacir 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 oluşur. Örneğin A, B ve C olmak üzere üç jeton 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. Tacir, 25,305 C jetonu karşılığında 24,695 B jetonu satar ve yaklaşık 0,61 B jetonunu kâr olarak tutar.
  4. Tacir daha sonra 25,305 A jetonu için 24,695 C jetonu satar ve yaklaşık 0,61 C jetonunu kâr olarak tutar. Tacir ayrıca fazladan 0,61 A jetonuna sahiptir (tacirin sonunda elde ettiği 25,305 eksi 24,695 orijinal yatırımdır).
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];
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));
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;
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 }
Kopyala

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

1 function swapExactTokensForTokens(
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,
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, çağrılan sözleşmeye doğrudan bir kullanıcıdan (bir işlem kullanılarak) veya farklı bir sözleşmeden giriş noktasıysa, parametrenin değeri doğrudan çağrı verilerinden alınabilir. Yukarıdaki _swap gibi bir 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 halletse de, 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) {
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');
Kopyala

Her takasta satın alınacak tutarı hesaplayın. Sonuç, tacirin kabul etmeye istekli olduğu minimum değerden düşükse, işlemi geri alı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 }
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ı aktarımda olduğu için eş takası, beklenmeyen jetonların bu transferin bir parçası olduğunu bilir.

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 }
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 gerçekleştirir, tacirin istediği çıktı jetonlarının ve onlar için ödemek istediği maksimum girdi jetonlarının sayısını belirlemesine olanak tanır.

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 }
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 {
Kopyala

Bu, (bu sorunu(opens in a new tab)) çözmek için aktarım veya depolama ücretleri olan jetonları 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);
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'ı çağırmadan önce yaptığımız gibi). Bunun yerine önce transfer yapmamız ve sonra kaç jeton aldığımızı görmemiz gerekir.

Not: Teoride, tek başına bu işlevi _swap yerine kullanabilirdik ancak bazı durumlarda (örneğin, sonunda gerekli minimum değeri karşılamak için yeterli olmadığından transfer geri alınırsa) bu daha fazla gaza mal olur. Transfer ücreti jetonları oldukça nadirdir, bu nedenle onları barındırmamız gerekse de, en az bir takastan 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 }
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}
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(opens in a new tab) 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;
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;
Kopyala

Daha yakın bir tahmin elde etmek için önceki tahmin ile karekökünü bulmaya çalıştığımız sayının önceki tahmine bölünmüş halinin ortalaması alınır. Yeni tahmin, mevcut tahminden daha düşük olmayana kadar tekrarlayın. Daha fazla detay için buraya bakın(opens in a new tab).

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

Sıfırın kareköküne asla ihtiyacımız olmamalı. Bir, iki ve üçün karekökleri kabaca birdir (tam sayıları kullandığımız için kesirleri yok sayarız).

1 }
2 }
3}
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 kodlayarak 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;
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 }
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}
Kopyala

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

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 }
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, aksi durumda biri A,B parametreleri, diğeri B,A parametreleri olmak üzere iki olasılığımız olacağı ve dolayısıyla bir yerine iki takas gerekeceği 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 }
Tümünü göster
Kopyala

Bu fonksiyon, iki token için eş takasının adresini hesaplar. Bu sözleşme, CREATE2 opcode(opens in a new tab) kullanılarak oluşturulur, bu yüzden kullandığı parametreleri biliyorsak aynı algoritmayı kullanarak adresi 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 }
Kopyala

Bu fonksiyon, eş takasının sahip olduğu iki token'ın rezervlerini döndürür. Jetonları her iki sıradan biriyle 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 }
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ştirmesini 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) {
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 }
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, 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 }
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}
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(opens in a new tab), ERC-20 ve Ethereum transfer işlemleri ile ilgili başarı kontrolleri ekleyerek bir geri alım ile yanlış değer dönüşünün aynı şekilde işlenmesini sağlar.

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
Tümünü göster
Kopyala

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

1 require(
2 success && (data.length == 0 || abi.decode(data, (bool))),
3 'TransferHelper::safeApprove: approve failed'
4 );
5 }
Kopyala

Bir ERC-20 çağrısı, ERC-20 standardından önce oluşturulmuş jetonla geriye dönük uyumluluk sağlamak adına ya 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 mantıksal 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 }
Tümünü göster
Kopyala

Bu fonksiyon, ERC-20'nin transfer işlevselliğini(opens in a new tab) 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 }
Tümünü göster
Kopyala

Bu fonksiyon, ERC-20'nin transferFrom işlevselliğini(opens in a new tab) 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}
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öndermeyiz.

Sonuç

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

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

Son düzenleme: @kaanmetu(opens in a new tab), 2 Nisan 2024

Bu rehber yararlı oldu mu?