Uniswap-v2 Sözleşmesine Genel Bakış
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:
- Farklı token'lar arası takas
- Piyasaya likidite katın ve eş takası ERC-20 likidite token'ları ile ödüllendirin
- 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
- Çevre hesabına takas edilecek tutarda bir ödenek sağlayın.
- Ç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)
- Yol boyunca her takasta işlem görmesi gereken miktarları belirleyin.
- 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)
- Çekirdek sözleşmenin dolandırılmadığını ve takastan sonra yeterli likiditeyi koruyabildiğini doğrulayın.
- 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.
- Çıktı token'larını hedefe gönderin.
- Rezerv tutarlarını güncellemek için
_update
komutunu çağırın
Çevre sözleşmesine geri dönün (UniswapV2Router02.sol)
- 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
- Likidite havuzuna eklenecek tutarlarda çevre hesabına bir ödenek sağlayın.
- Çevre sözleşmesinin addLiquidity fonksiyonlarından birini çağırın.
Çevre sözleşmesinde (UniswapV2Router02.sol)
- Gerekirse yeni bir eş takası oluşturun
- 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ı.
- Tutarların kabul edilebilir olup olmadığını kontrol edin (çağıranlar, likidite eklemek istemeyecekleri bir minimum tutar altında belirtebilirler)
- Çekirdek sözleşmeyi çağırın.
Çekirdek sözleşmede (UniswapV2Pair.sol)
- Likidite token'larını basın ve çağırana gönderin
- Rezerv tutarlarını güncellemek için
_update
'i çağırın
Likiditeyi Kaldır
Çağıran
- Çevre hesabına, temeldeki token'lar karşılığında yakılacak likidite token'ı ödeneği sağlayın.
- Çevre sözleşmesinin removeLiquidity fonksiyonlarından birini çağırın.
Çevre sözleşmesinde (UniswapV2Router02.sol)
- Likidite token'larını eş takasına gönderin
Çekirdek sözleşmede (UniswapV2Pair.sol)
- 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.
- Likidite token'larını yakın
- 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;23import './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';10Tümünü gösterKopyala
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 {2Kopyala
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;2Kopyala
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;2Kopyala
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;2Kopyala
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)')));2Kopyala
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;2Kopyala
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;3Kopyala
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 getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReserves3Kopyala
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 getReserves2Kopyala
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;3Kopyala
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 event2Kopyala
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.
Olay | reserve0 | reserve1 | reserve0 * reserve1 | Ortalama takas oranı (token1 / token0) |
---|---|---|---|---|
İlk kurulum | 1,000.000 | 1,000.000 | 1,000,000 | |
Ticaret Yapan A, 50 tane token0'ı 47.619 token1 ile takas eder | 1,050.000 | 952.381 | 1,000,000 | 0.952 |
Ticaret Yapan B, 10 tane token0'ı 8.984 token1 ile takas eder | 1,060.000 | 943.396 | 1,000,000 | 0.898 |
Ticaret Yapan C, 40 tane token0'ı 34.305 token1 ile takas eder | 1,100.000 | 909.090 | 1,000,000 | 0.858 |
Ticaret Yapan D, 109.01 tane token0'ı 100 token1 ile takas eder | 990.990 | 1,009.090 | 1,000,000 | 0.917 |
Ticaret Yapan E, 10 tane token0'ı 10.079 token1 ile takas eder | 1,000.990 | 999.010 | 1,000,000 | 1.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;2Kopyala
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() {2Kopyala
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;3Kopyala
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 _;2Kopyala
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 }3Kopyala
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 }6Kopyala
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));3Kopyala
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 }3Kopyala
Bir ERC-20 transfer çağrısının başarısızlığı bildirebilmesinin iki yolu vardır:
- Geri döndürme. Harici bir sözleşmeye yapılan çağrı geri alınırsa, boolean dönüş değeri
false
olur - 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);3Kopyala
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 to8 );9Kopyala
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);2Kopyala
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 }4Kopyala
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 deployment2 function initialize(address _token0, address _token1) external {3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check4 token0 = _token0;5 token1 = _token1;6 }7Kopyala
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 accumulators2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {3Kopyala
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');2Kopyala
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 desired3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {4Kopyala
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 desired2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;4 }5Kopyala
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:
Olay | reserve0 | reserve1 | zaman damgası | Marjinal takas oranı (reserve1 / reserve0) | price0CumulativeLast |
---|---|---|---|---|---|
İlk kurulum | 1,000.000 | 1,000.000 | 5,000 | 1.000 | 0 |
Ticaret Yapan A, 50 token0 yatırır ve 47.619 token1 geri alır | 1,050.000 | 952.381 | 5,020 | 0.907 | 20 |
Ticaret Yapan B, 10 token0 yatırır ve 8.984 token1 geri alır | 1,060.000 | 943.396 | 5,030 | 0.890 | 20+10*0.907 = 29.07 |
Ticaret Yapan C 40 token0 yatırır ve 34.305 token1 geri alır | 1,100.000 | 909.090 | 5,100 | 0.826 | 29.07+70*0.890 = 91.37 |
Ticaret Yapan D, 100 token1 yatırır ve 109.01 token0 geri alır | 990.990 | 1,009.090 | 5,110 | 1.018 | 91.37+10*0.826 = 99.63 |
Trader E, 10 jeton0 yatırır ve 10.079 jeton1 geri alır | 1,000.990 | 999.010 | 5,150 | 0.998 | 99.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 }6Kopyala
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) {3Kopyala
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);3Kopyala
Fabrikanın ücret hedefini okuyun. Sıfır ise protokol ücreti yoktur ve bu ücreti hesaplamaya gerek yoktur.
1 uint _kLast = kLast; // gas savings2Kopyala
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) {3Kopyala
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) {4Kopyala
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;4Kopyala
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 }4Kopyala
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 }5Kopyala
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 checks2 function mint(address to) external lock returns (uint liquidity) {3Kopyala
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 savings2Kopyala
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);5Kopyala
Mevcut bakiyeleri alın ve her bir token türünden ne kadar eklendiğini görün.
1 bool feeOn = _mintFee(_reserve0, _reserve1);2Kopyala
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 _mintFee2 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 tokens5Kopyala
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.
Olay | reserve0 | reserve1 | reserve0 * reserve1 | Havuzun değeri (reserve0 + reserve1) |
---|---|---|---|---|
İlk kurulum | 8 | 32 | 256 | 40 |
Ticaret yapan kişi 8 Token0 tokeni yatırır, 16 Token1 alır | 16 | 16 | 256 | 32 |
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);3Kopyala
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).
Olay | reserve0 | reserve1 | reserve0 * reserve1 | Havuz 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 kurulum | 8.000 | 8.000 | 64 | 16.000 | 8 | 8 | 2.000 |
Her türden dördünü yatırma | 12.000 | 12.000 | 144 | 24.000 | 4 | 12 | 2.000 |
Her türden ikisini yatırma | 14.000 | 14.000 | 196 | 28.000 | 2 | 14 | 2.000 |
Eşit olmayan değer yatırma | 18.000 | 14.000 | 252 | 32.000 | 0 | 14 | ~2.286 |
Arbitrajdan sonra | ~15.874 | ~15.874 | 252 | ~31.748 | 0 | 14 | ~2.267 |
1 }2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');3 _mint(to, liquidity);4Kopyala
Ek likidite token'larını gerçekten oluşturmak ve bunları doğru hesaba vermek için UniswapV2ERC20._mint
fonksiyonunu kullanın.
12 _update(balance0, balance1, _reserve0, _reserve1);3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date4 emit Mint(msg.sender, amount0, amount1);5 }6Kopyala
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 checks2 function burn(address to) external lock returns (uint amount0, uint amount1) {3Kopyala
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 savings2 address _token0 = token0; // gas savings3 address _token1 = token1; // gas savings4 uint balance0 = IERC20(_token0).balanceOf(address(this));5 uint balance1 = IERC20(_token1).balanceOf(address(this));6 uint liquidity = balanceOf[address(this)];7Kopyala
Ç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 _mintFee3 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution4 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');6Kopyala
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));67 _update(balance0, balance1, _reserve0, _reserve1);8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date9 emit Burn(msg.sender, amount0, amount1, to);10 }1112Tümünü gösterKopyala
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 checks2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {3Kopyala
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 savings3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');45 uint balance0;6 uint balance1;7 { // scope for _token{0,1}, avoids stack too deep errors8Kopyala
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 tokens5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens6Kopyala
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);2Kopyala
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 }4Kopyala
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 errors5 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');8Kopyala
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 }23 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 }6Kopyala
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üncelleyinskim
, fazladan miktarı çekin. Token'ları kimin yatırdığını bilmediğimiz için herhangi bir hesabınskim
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 reserves2 function skim(address to) external lock {3 address _token0 = token0; // gas savings4 address _token1 = token1; // gas savings5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));7 }891011 // force reserves to match balances12 function sync() external lock {13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);14 }15}16Tümünü gösterKopyala
UniswapV2Factory.sol
Bu sözleşme eş takaslarını oluşturur.
1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';56contract UniswapV2Factory is IUniswapV2Factory {7 address public feeTo;8 address public feeToSetter;9Kopyala
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;3Kopyala
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);2Kopyala
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 }4Kopyala
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 }4Kopyala
Bu fonksiyon, eş takaslarının sayısını döndürür.
1 function createPair(address tokenA, address tokenB) external returns (address pair) {2Kopyala
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);3Kopyala
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 sufficient3Kopyala
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;2Kopyala
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 }5Kopyala
Bir opcode henüz Solidity tarafından desteklenmediğinde onu satır içi derleme kullanarak çağırabiliriz.
1 IUniswapV2Pair(pair).initialize(token0, token1);2Kopyala
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 direction3 allPairs.push(pair);4 emit PairCreated(token0, token1, pair, allPairs.length);5 }6Kopyala
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 }56 function setFeeToSetter(address _feeToSetter) external {7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');8 feeToSetter = _feeToSetter;9 }10}11Tümünü gösterKopyala
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;4Kopyala
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;2Kopyala
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 := chainid5 }6Kopyala
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 }11Tümünü gösterKopyala
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 {2Kopyala
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');2Kopyala
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 );8Kopyala
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);2Kopyala
Özetten ve imzadan, ecrecover kullanarak imzalayan adresi alabiliriz.
1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');2 _approve(owner, spender, value);3 }45Kopyala
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;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';56import './interfaces/IUniswapV2Router02.sol';7import './libraries/UniswapV2Library.sol';8import './libraries/SafeMath.sol';9import './interfaces/IERC20.sol';10import './interfaces/IWETH.sol';11Tümünü gösterKopyala
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;34 address public immutable override factory;5 address public immutable override WETH;6Kopyala
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 }5Kopyala
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 }5Kopyala
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 contract3 }4Kopyala
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.
12 // **** ADD LIQUIDITY ****3 function _addLiquidity(4Kopyala
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,3Kopyala
Bunlar, ERC-20 token sözleşmelerinin adresleridir.
1 uint amountADesired,2 uint amountBDesired,3Kopyala
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 amountBMin3Kopyala
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:
Parametre | Değer |
---|---|
amountADesired | 1000 |
amountBDesired | 1000 |
amountAMin | 900 |
amountBMin | 800 |
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) {2Kopyala
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 yet2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);4 }5Kopyala
Bu token çifti için henüz bir takas yoksa onu oluşturun.
1 (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);2Kopyala
Çiftteki mevcut rezervleri alın.
1 if (reserveA == 0 && reserveB == 0) {2 (amountA, amountB) = (amountADesired, amountBDesired);3Kopyala
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);3Kopyala
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);4Kopyala
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);6Kopyala
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.
1 }2 }3 }4Kopyala
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 deadline10Tümünü gösterKopyala
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);4Kopyala
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);3Kopyala
Kullanıcıdan doğru miktarda token'ı eş takasına aktarın.
1 liquidity = IUniswapV2Pair(pair).mint(to);2 }3Kopyala
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,4Kopyala
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 deadline5 ) 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 amountETHMin13 );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));18Tümünü gösterKopyala
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 any3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);4 }5Kopyala
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 deadline10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {11Tümünü gösterKopyala
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 pair3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);4Kopyala
Çekirdek sözleşmenin burn
işlevi, kullanıcıya token'ları geri ödemeyi gerçekleştirir.
1 (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);2Kopyala
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);2Kopyala
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 }4Kopyala
Ö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 deadline8 ) 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 deadline17 );18 TransferHelper.safeTransfer(token, to, amountToken);19 IWETH(WETH).withdraw(amountETH);20 TransferHelper.safeTransferETH(to, amountETH);21 }22Tümünü gösterKopyala
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 s10 ) 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 }161718 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 s26 ) 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 }32Tümünü gösterKopyala
Bu fonksiyonlar, izin mekanizmasını kullanarak, ether'ı olmayan kullanıcıların havuzdan çekilmesine izin vermek için meta-işlemleri iletir.
12 // **** 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 deadline10 ) 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 deadline19 );20 TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));21 IWETH(WETH).withdraw(amountETH);22 TransferHelper.safeTransferETH(to, amountETH);23 }2425Tümünü gösterKopyala
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.
123 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 s11 ) 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, deadline17 );18 }19Tümünü gösterKopyala
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 pair3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {4Kopyala
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++) {2Kopyala
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.
- Başlangıç durumu
- Ticaret yapan bir kişi 24,695 A token'ı satar ve 25,305 B token'ı alır.
- 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.
- 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ım | A-B Takası | B-C Takası | A-C Takası |
---|---|---|---|
1 | A:1000 B:1050 A/B=1,05 | B:1000 C:1050 B/C=1,05 | A:1050 C:1000 C/A=1,05 |
2 | A:1024.695 B:1024.695 A/B=1 | B:1000 C:1050 B/C=1,05 | A:1050 C:1000 C/A=1,05 |
3 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A:1050 C:1000 C/A=1.05 |
4 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A: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];4Kopyala
Ş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));2Kopyala
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;2Kopyala
Bu son takas mı? Eğer öyleyse ticaretten alınan token'ları hedefe gönderin. Değilse, bir sonraki eş takasına gönderin.
12 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(3 amount0Out, amount1Out, to, new bytes(0)4 );5 }6 }7Kopyala
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(2Kopyala
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,4Kopyala
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 deadline3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {4Kopyala
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');3Kopyala
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 }6Kopyala
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 deadline7 ) 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 }15Tümünü gösterKopyala
Ö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 external3 virtual4 override5 payable6 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 }161718 function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)19 external20 virtual21 override22 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 }35363738 function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)39 external40 virtual41 override42 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 }555657 function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)58 external59 virtual60 override61 payable62 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 any72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);73 }74Tümünü gösterKopyala
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 pair3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {4Kopyala
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 errors8 (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);12Tümünü gösterKopyala
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 }789 function swapExactTokensForTokensSupportingFeeOnTransferTokens(10 uint amountIn,11 uint amountOutMin,12 address[] calldata path,13 address to,14 uint deadline15 ) external virtual override ensure(deadline) {16 TransferHelper.safeTransferFrom(17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn18 );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 }262728 function swapExactETHForTokensSupportingFeeOnTransferTokens(29 uint amountOutMin,30 address[] calldata path,31 address to,32 uint deadline33 )34 external35 virtual36 override37 payable38 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 }515253 function swapExactTokensForETHSupportingFeeOnTransferTokens(54 uint amountIn,55 uint amountOutMin,56 address[] calldata path,57 address to,58 uint deadline59 )60 external61 virtual62 override63 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]), amountIn68 );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 }75Tümünü gösterKopyala
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 }56 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)7 public8 pure9 virtual10 override11 returns (uint amountOut)12 {13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);14 }1516 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)17 public18 pure19 virtual20 override21 returns (uint amountIn)22 {23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);24 }2526 function getAmountsOut(uint amountIn, address[] memory path)27 public28 view29 virtual30 override31 returns (uint[] memory amounts)32 {33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);34 }3536 function getAmountsIn(uint amountOut, address[] memory path)37 public38 view39 virtual40 override41 returns (uint[] memory amounts)42 {43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);44 }45}46Tümünü gösterKopyala
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;23// a library for performing various math operations45library Math {6 function min(uint x, uint y) internal pure returns (uint z) {7 z = x < y ? x : y;8 }910 // 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;15Tümünü gösterKopyala
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;4Kopyala
Ö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;4Kopyala
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}4Kopyala
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;23// a library for handling binary fixed point numbers (https://wikipedia.org/wiki/Q_(number_format))45// range: [0, 2**112 - 1]6// resolution: 1 / 2**11278library UQ112x112 {9 uint224 constant Q112 = 2**112;10Tümünü gösterKopyala
Q112
birin şifrelemesidir.
1 // encode a uint112 as a UQ112x1122 function encode(uint112 y) internal pure returns (uint224 z) {3 z = uint224(y) * Q112; // never overflows4 }5Kopyala
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 UQ112x1122 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {3 z = x / uint224(y);4 }5}6Kopyala
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;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';45import "./SafeMath.sol";67library UniswapV2Library {8 using SafeMath for uint;910 // returns sorted token addresses, used to handle return values from pairs sorted in this order11 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 }16Tümünü gösterKopyala
İ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 calls2 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 hash9 ))));10 }11Tümünü gösterKopyala
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 pair2 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 }7Kopyala
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 asset2 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 }7Kopyala
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 asset2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {3Kopyala
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.
12 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 }9Kopyala
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 asset2 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 }9Kopyala
Bu fonksiyon kabaca aynı şeyi yapar ancak çıktı miktarını alır ve girdi sağlar.
12 // performs chained getAmountOut calculations on any number of pairs3 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 }1213 // performs chained getAmountIn calculations on any number of pairs14 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}24Tümünü gösterKopyala
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-later23pragma solidity >=0.6.0;45// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false6library TransferHelper {7 function safeApprove(8 address token,9 address to,10 uint256 value11 ) internal {12 // bytes4(keccak256(bytes('approve(address,uint256)')));13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));1415Tümünü gösterKopyala
İ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 }6Kopyala
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.
123 function safeTransfer(4 address token,5 address to,6 uint256 value7 ) 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 }15Tümünü gösterKopyala
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.
12 function safeTransferFrom(3 address token,4 address from,5 address to,6 uint256 value7 ) 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 }15Tümünü gösterKopyala
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.
12 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}7Kopyala
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.