Uniswap-v2 Sözleşmesine Genel Bakış
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:
- 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 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.
- Tutarların kabul edilebilir olup olmadığını kontrol edin (çağıranlar, altında likidite eklemek istemeyecekleri bir minimum tutar belirtebilir)
- Ç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 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.
- Likidite jetonları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(opens in a new tab), jetonları 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';Tü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 {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 getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReservesKopyala
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 getReservesKopyala
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 eventKopyala
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.
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;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:
- 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);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 to8 );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 deployment2 function initialize(address _token0, address _token1) external {3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check4 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 accumulators2 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 desired3 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 desired2 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:
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 }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 savingsKopyala
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 checks2 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 savingsKopyala
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 _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 tokensKopyala
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.
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);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).
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);Kopyala
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 }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 checks2 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 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)];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 _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');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));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 }11Tü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 {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 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 errorsKopyala
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 tokens5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokensKopyala
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 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');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 }23 _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üncelleyinskim
, fazladan miktarı çekin. Jetonları 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}Tümünü gösterKopyala
UniswapV2Factory.sol
Bu sözleşme(opens in a new tab) 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;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 sufficientKopyala
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 direction3 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 }56 function setFeeToSetter(address _feeToSetter) external {7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');8 feeToSetter = _feeToSetter;9 }10}Tü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(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 := chainid5 }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österKopyala
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 }4Kopyala
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;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';Tü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 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;34 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 contract3 }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.
12 // **** 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 amountBMinKopyala
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:
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 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 yet2 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.
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 deadlineTü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);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 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));Tü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. 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 any3 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 deadline10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {Tü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 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 pair3 (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 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 }Tü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 }Tümünü gösterKopyala
Bu fonksiyonlar, izin mekanizmasını kullanarak, ether'i 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 }24Tü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 }Tü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 {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.
- Başlangıç durumu
- Ticaret yapan bir kişi 24,695 A token'ı satar ve 25,305 B token'ı alır.
- Tacir, 25,305 C jetonu karşılığında 24,695 B jetonu satar ve yaklaşık 0,61 B jetonunu kâr olarak tutar.
- 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ı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];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.
12 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 deadline3 ) 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 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 }Tü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 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 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 }Tü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 {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 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);Tü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
'ı ç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 }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 }Tü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}Tü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(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;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;Tü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;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;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;Tü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 }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 UQ112x1122 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;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 }Tümünü gösterKopyala
İ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 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 }Tümünü gösterKopyala
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 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 }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 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 }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 asset2 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.
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 }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 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 }Kopyala
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}Tü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(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-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));14Tü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)(opens in a new tab) 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 }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.
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 }Tümünü gösterKopyala
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.
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 }Tümünü gösterKopyala
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.
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}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: @wackerow(opens in a new tab), 2 Nisan 2024