Panduan Lengkap Kontrak Uniswap-v2
Pendahuluan
Uniswap v2(opens in a new tab) dapat membuat pasar bursa antara dua token ERC-20 mana pun. Dalam artikel ini, kita akan memeriksa kode sumber untuk kontrak-kontrak yang menerapkan protokol ini dan melihat alasan kontrak-kontrak tersebut ditulis dengan cara ini.
Apa Fungsi Uniswap?
Pada dasarnya, ada dua tipe pengguna: penyedia likuiditas dan pedagang.
Penyedia likuiditas menyediakan pool dengan dua token yang dapat dipertukarkan (kita akan memanggilnya Token0 dan Token1). Sebagai hasilnya, token-token tersebut menerima token ketiga yang menyatakan sebagian kepemilikan dari pool yang disebut token likuiditas.
Pedagang mengirimkan satu jenis token ke pool dan menerima token lainnya (contohnya, mengirimkan Token0 dan menerima Token1) dari pool yang disediakan oleh penyedia likuiditas. Nilai tukar ditentukan oleh jumlah relatif dari Token0 dan Token1 yang dimiliki oleh pool. Selain itu, pool mengambil persentase kecil sebagai imbalan untuk pool likuiditas.
Ketika penyedia likuiditas menginginkan kembali aset mereka, mereka dapat membakar token pool dan menerima kembali token mereka, termasuk bagian imbalan mereka.
Klik di sini untuk deskripsi lengkapnya(opens in a new tab).
Mengapa v2? Mengapa bukan v3?
Waktu saya menulis ini, Uniswap v3(opens in a new tab) hampir siap. Namun, peningkatan tersebut jauh lebih rumit dari versi aslinya. Lebih mudah untuk terlebih dahulu mempelajari v2 dan kemudian beralih ke v3.
Kontrak Inti vs Kontrak Perifer
Uniswap v2 terbagi menjadi dua komponen, inti dan perifer. Pembagian ini membuat kontrak inti, yang menampung aset dan oleh karena itu harus aman, menjadi lebih sederhana dan mudah untuk diaudit. Semua fungsionalitas ekstra yang diperlukan oleh para pedagang kemudian dapat disediakan oleh kontrak perifer.
Alur Data dan Pengendalian
Ini adalah alur data dan pengendalian yang terjadi ketika Anda melakukan ketiga aksi utama Uniswap:
- Menukar antara token-token yang berbeda
- Menambahkan likuiditas ke pasar dan mendapatkan imbalan dengan bursa pasangan token likuiditas ERC-20
- Membakar token likuiditas ERC-20 dan mendapatkan kembali token ERC-20 yang diperbolehkan bursa pasangan untuk ditukarkan oleh para pedagang
Menukar
Alur ini paling umum, yang digunakan oleh para pedagang:
Pemanggil
- Menyediakan akun perifer dengan tunjangan dalam jumlah yang dapat ditukarkan.
- Memanggil salah satu dari banyak fungsi penukaran kontrak perifer (yang bergantung pada apakah ETH dilibatkan atau tidak, apakah pedagang menentukan jumlah token yang akan disetorkan atau jumlah token yang akan didapatkan kembali, dll). Setiap fungsi penukaran menerima
jalur
, larik bursa yang akan dilewati.
Dalam kontrak perifer (UniswapV2Router02.sol)
- Mengenali jumlah yang perlu diperdagangkan pada setiap bursa sepanjang jalur.
- Mengulang di sepanjang jalur. Untuk setiap bursa di sepanjang jalur, mengirimkan token input dan kemudian memanggil fungsi
penukaran
bursa. Dalam kebanyakan kasus, alamat tujuan token adalah bursa pasangan berikutnya dalam jalur. Dalam bursa terakhir, ada alamat yang disediakan oleh pedagang.
Dalam kontrak inti (UniswapV2Pair.sol)
- Verifikasi bahwa kontrak inti tidak dicurangi dan dapat mempertahankan likuiditas yang memadai setelah penukaran.
- Melihat seberapa banyak token tambahan yang kita miliki selain dari cadangan yang diketahui. Jumlah tersebut adalah jumlah token input yang kita terima untuk dipertukarkan.
- Mengirimkan token output ke tujuan.
- Memanggil
_update
untuk memperbarui jumlah cadangan
Kembali dalam kontrak perifer (UniswapV2Router02.sol)
- Melakukan pembersihan mana pun yang diperlukan (contohnya, membakar token WETH untuk mendapatkan kembali ETH yang akan dikirimkan ke pedagang)
Menambah Likuiditas
Pemanggil
- Menyediakan akun perifer dengan tunjangan dalam jumlah yang akan ditambahkan ke pool likuiditas.
- Memanggil salah satu dari fungsi addLiquidity kontrak perifer.
Dalam kontrak perifer (UniswapV2Router02.sol)
- Membuat bursa pasangan baru jika diperlukan
- Jika ada bursa pasangan yang telah ada, hitung jumlah token yang akan ditambahkan. Seharusnya nilai yang sama untuk kedua token, sehingga rasionya sama untuk token baru dan token yang telah ada.
- Memeriksa jika jumlahnya dapat diterima (pemanggil dapat menentukan jumlah minimum di bawah yang tidak akan mereka tambahkan ke likuiditas)
- Memanggil kontrak inti.
Dalam kontrak inti (UniswapV2Pair.sol)
- Mencetak token likuiditas dan mengirimkannya ke pemanggil
- Memanggil
_update
untuk memperbarui jumlah cadangan
Menghapus Likuiditas
Pemanggil
- Menyediakan akun perifer dengan tunjangan dari token likuiditas yang akan dibakar di bursa untuk token yang mendasarinya.
- Memanggil salah satu dari fungsi removeLiquidity kontrak perifer.
Dalam kontrak perifer (UniswapV2Router02.sol)
- Mengirimkan token likuiditas ke bursa pasangan
Dalam kontrak inti (UniswapV2Pair.sol)
- Mengirimkan ke alamat tujuan token yang mendasarinya sesuai proporsi terhadap token yang akan dibakar. Contohnya, jika ada 1000 token A dalam pool, 500 token B, dan 90 token likuiditas, dan kita menerima 9 token untuk dibakar, kita membakar 10% dari token likuiditas sehingga kita mengirimkan kembali ke pengguna 100 token A dan 50 token B.
- Membakar token likuiditas
- Memanggil
_update
untuk memperbarui jumlah cadangan
Kontrak Inti
Ini adalah kontrak aman yang menampung likuiditas.
UniswapV2Pair.sol
Kontrak ini(opens in a new tab) menerapkan pool sebenarnya yang mempertukarkan token. Ini adalah fungsionalitas Uniswap inti.
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';Tampilkan semuaSalin
Semua antarmuka ini perlu diketahui oleh kontrak, baik karena kontrak menerapkan fungsionalitas tersebut (IUniswapV2Pair
dan UniswapV2ERC20
) atau karena memanggil kontrak yang menerapkannya.
1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {Salin
Kontrak ini mewarisi dari UniswapV2ERC20
, yang menyediakan fungsi ERC-20 untuk token likuiditas.
1 using SafeMath for uint;Salin
Pustaka SafeMath(opens in a new tab) digunakan untuk menghindari overflow dan underflow. Hal ini penting karena jika tidak, kita dapat terjebak dalam situasi di mana nilai seharusnya -1
, tetapi malah menjadi 2^256-1
.
1 using UQ112x112 for uint224;Salin
Banyak perhitungan dalam kontrak pool yang memerlukan pecahan. Namun, pecahan tidak didukung oleh EVM. Solusi yang ditemukan Uniswap adalah menggunakan nilai 224 bita, dengan 112 bita untuk bagian bilangan bulat, dan 112 bita untuk pecahannya. Jadi, 1,0
dinyatakan sebagai 2^112
, 1,5
dinyatakan sebagai 2^112 + 2^111
, dll.
Rincian selengkapnya tentang pustaka ini tersedia nanti dalam dokumennya.
Variabel
1 uint public constant MINIMUM_LIQUIDITY = 10**3;Salin
Untuk menghindari kasus pembagian dengan nol, ada jumlah minimum dari token likuiditas yang selalu ada (tetapi dimiliki oleh akun kosong). Jumlah itu adalah MINIMUM_LIQUIDITY, seribu.
1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));Salin
Ini adalah pemilih ABI untuk fungsi transfer ERC-20. Digunakan untuk mentransfer token ERC-20 dalam akun kedua token.
1 address public factory;Salin
Ini adalah kontrak pabrik yang membuat pool. Setiap pool adalah bursa antara dua token ERC-20, pabriknya adalah titik pusat yang menghubungkan semua pool.
1 address public token0;2 address public token1;Salin
Ada alamat kontrak untuk kedua jenis token ERC-20 yang dapat dipertukarkan dalam pool.
1 uint112 private reserve0; // uses single storage slot, accessible via getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReservesSalin
Cadangan yang dimiliki pool untuk setiap jenis token. Kami menganggap bahwa keduanya menyatakan jumlah nilai yang sama, dan oleh karena itu setiap token0 bernilai sama dengan reserve1/reserve0 token1.
1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReservesSalin
Stempel waktu untuk blok terakhir tempat terjadinya bursa, yang digunakan untuk menelusuri nilai tukar di sepanjang waktu.
Salah satu dari pengeluaran gas terbesar dari kontrak Ethereum adalah penyimpanan, yang tetap ada dari satu panggilan kontrak ke panggilan lainnya. Setiap sel penyimpanan memiliki panjang 256 bita. So three variables, reserve0, reserve1, and blockTimestampLast, are allocated in such a way a single storage value can include all three of them (112+112+32=256).
1 uint public price0CumulativeLast;2 uint public price1CumulativeLast;Salin
Variabel-variabel tersebut menampung biaya kumulatif untuk setiap token (masing-masing berkaitan satu sama lain). Variabel-variabel tersebut dapat digunakan untuk menghitung rata-rata nilai tukar selama satu periode waktu.
1 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity eventSalin
Cara bursa pasangan menentukan nilai tukar antara token0 dan token1 adalah dengan menjaga beberapa dari dua cadangan bernilai tetap selama perdagangan berlangsung. kLast
adalah nilai ini. Nilainya berubah ketika penyedia likuiditas menyetor atau menarik token, dan nilainya bertambah sedikit karena 0,3% biaya pasar.
Berikut adalah contoh sederhana. Perhatikan bahwa demi mempermudah, tabel hanya memiliki tiga digit setelah poin desimal, dan kita mengabaikan 0,3% biaya perdagangan sehingga jumlahnya tidak akurat.
Aksi | reserve0 | reserve1 | reserve0 * reserve1 | Rata-rata nilai tukar (token1 / token0) |
---|---|---|---|---|
Pengaturan awal | 1.000,000 | 1.000,000 | 1.000.000 | |
Pedagang A menukar 50 token0 untuk 47,619 token1 | 1.050,000 | 952,381 | 1.000.000 | 0,952 |
Pedagang B menukar 10 token0 untuk 8,984 token1 | 1.060,000 | 943,396 | 1.000.000 | 0,898 |
Pedagang C menukar 40 token0 untuk 34,305 token1 | 1.100,000 | 909,090 | 1.000.000 | 0,858 |
Pedangan D menukar 100 token1 untuk 109,01 token0 | 990,990 | 1.009,090 | 1.000.000 | 0,917 |
Pedagang E menukar 10 token0 untuk 10,079 token1 | 1.000,990 | 999,010 | 1.000.000 | 1,008 |
Karena pedagang menyediakan lebih banyak token0, nilai relatif token1 meningkat, dan sebaliknya, didasarkan pada persediaan dan permintaan.
Penguncian
1 uint private unlocked = 1;Salin
Ada kelas kerentanan keamanan yang didasarkan pada penyalahgunaan masuk kembali(opens in a new tab). Uniswap perlu mentransfer token ERC-20 arbitrari, yang berarti memanggil kontrak ERC-20 yang dapat berusaha menyalahgunakan pasar Uniswap yang memanggilnya. Dengan memiliki variabel-variabel terbuka
sebagai bagian dari kontrak, kita dapat mencegah fungsi dipanggil ketika variabel-variabel dijalankan (dalam transaksi yang sama).
1 modifier lock() {Salin
Fungsi ini adalah pemodifikasi(opens in a new tab), fungsi yang memperbesar nilai fungsi normal untuk mengubah perilakunya dengan beberapa cara.
1 require(unlocked == 1, 'UniswapV2: LOCKED');2 unlocked = 0;Salin
Jika terbuka
sama dengan satu, tetapkan menjadi nol. Jika bernilai nol, balikkan panggilannya, buat menjadi gagal.
1 _;Salin
Dalam pemodifikasi _;
adalah panggilan fungsi asli (dengan semua parameternya). Di sini, artinya panggilan fungsi hanya terjadi jika terbuka
adalah satu ketika dipanggil, dan ketika menjalankan nilai terbuka
adalah nol.
1 unlocked = 1;2 }Salin
Setelah fungsi utama kembali, lepaskan pengunciannya.
Fungsi lainnya
1 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {2 _reserve0 = reserve0;3 _reserve1 = reserve1;4 _blockTimestampLast = blockTimestampLast;5 }Salin
Fungsi ini menyediakan pemanggil dengan status bursa saat ini. Perhatikan bahwa fungsi Solidity dapat mengembalikan beberapa nilai(opens in a new tab).
1 function _safeTransfer(address token, address to, uint value) private {2 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));Salin
Fungsi internal ini mentransfer sejumlah token ERC20 dari bursa ke orang lain. SELECTOR
specifies that the function we are calling is transfer(address,uint)
(see definition above).
Untuk menghindari keharusan mengimpor antarmuka untuk fungsi token, kami "secara manual" melakukan panggilan dengan menggunakan salah satu fungsi ABI(opens in a new tab).
1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');2 }Salin
Ada dua cara di mana panggilan transfer ERC-20 dapat melaporkan kegagalan:
- Balikkan. If a call to an external contract reverts, then the boolean return value is
false
- Berakhir secara normal tetapi melaporkan kegagalan. Dalam kasus ini, penyangga nilai pengembalian memiliki panjang bukan nol, dan ketika dibaca sandinya sebagai nilai boolean, itu
salah
Jika salah satu kondisi ini terjadi, balikkan.
Aksi
1 event Mint(address indexed sender, uint amount0, uint amount1);2 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);Salin
Kedua aksi ini dipancarkan ketika penyedia likuiditas menyetor likuiditas (Cetak
) atau menariknya (Bakar
). Dalam salah satu kasus, jumlah token0 dan token1 yang disetor atau ditarik adalah bagian dari aksi, serta identitas akun yang memanggil kita (pengirim
). Dalam kasus penarikan, aksi juga mencakup target yang menerima token (ke
), yang mungkin tidak sama dengan pengirim.
1 event Swap(2 address indexed sender,3 uint amount0In,4 uint amount1In,5 uint amount0Out,6 uint amount1Out,7 address indexed to8 );Salin
Aksi ini dipancarkan ketika pedagang menukar satu token dengan token lainnya. Sekali lagi, pengirim dan tujuannya mungkin tidak sama. Setiap token mungkin dikirim ke bursa, atau diterima dari bursa.
1 event Sync(uint112 reserve0, uint112 reserve1);Salin
Akhirnya, Sinkronisasi
dipancarkan setiap kali token ditambahkan atau ditarik, terlepas dari alasannya, untuk menyediakan informasi cadangan terkini (dan oleh karena itu berupa nilai tukar).
Fungsi Pengaturan
Fungsi ini seharusnya dipanggil setelah bursa pasangan baru diatur.
1 constructor() public {2 factory = msg.sender;3 }Salin
Konstruktor memastikan kita akan terus menelusuri alamat dari pabrik yang membuat pasangan. Informasi ini diperlukan untuk menginisialisasi
dan untuk biaya pabrik (jika ada)
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 }Salin
Fungsi ini membuat pabrik (dan hanya pabrik) dapat menentukan kedua token ERC-20 yang akan dipertukarkan oleh pasangan ini.
Fungsi Pembaruan Internal
_update
1 // update reserves and, on the first call per block, price accumulators2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {Salin
Fungsi ini dipanggil setiap kali token disetor atau ditarik.
1 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');Salin
If either balance0 or balance1 (uint256) is higher than uint112(-1) (=2^112-1) (so it overflows & wraps back to 0 when converted to uint112) refuse to continue the _update to prevent overflows. With a normal token that can be subdivided into 10^18 units, this means each exchange is limited to about 5.1*10^15 of each tokens. Sejauh ini, itu bukanlah masalah.
1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {Salin
Jika waktu berlalu bukan nol, artinya kita adalah transaksi bursa pertama di blok ini. Dalam kasus ini, kita perlu memperbarui pengumpul biaya.
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 }Salin
Setiap pengumpul biaya diperbarui dengan biaya terkini (cadangan dari token lain/cadangan dari token ini) dikalikan dengan waktu yang berlalu dalam detik. Untuk mendapatkan harga rata-rata, Anda membaca harga kumulatif adalah dua poin dalam sekali waktu, dan membaginya dengan perbedaan waktu di antaranya. Contohnya, asumsikan urutan aksi ini:
Aksi | reserve0 | reserve1 | stempel waktu | Nilai tukar marginal (reserve1 / reserve0) | price0CumulativeLast |
---|---|---|---|---|---|
Pengaturan awal | 1.000,000 | 1.000,000 | 5.000 | 1,000 | 0 |
Pedagang A menyetor 50 token0 dan mendapatkan kembali 47,619 token1 | 1.050,000 | 952,381 | 5.020 | 0,907 | 20 |
Pedagang B menyetor 10 token0 dan mendapatkan kembali 8,984 token1 | 1.060,000 | 943,396 | 5.030 | 0,890 | 20+10*0,907 = 29,07 |
Pedagang C menyetor 40 token0 dan mendapatkan kembali 34,305 token1 | 1.100,000 | 909,090 | 5.100 | 0,826 | 29,07+70*0,890 = 91,37 |
Pedagang D menyetor 100 token1 dan mendapatkan kembali 109,01 token0 | 990,990 | 1.009,090 | 5.110 | 1,018 | 91,37+10*0,826 = 99,63 |
Pedagang E menyetor 10 token0 dan mendapatkan kembali 10,079 token1 | 1.000,990 | 999,010 | 5.150 | 0,998 | 99,63+40*1,1018 = 143,702 |
Anggaplah kita ingin menghitung harga rata-rata dari Token0 di antara stempel waktu 5.030 dan 5.150. Perbedaan dalam nilai dari price0Cumulative
adalah 143,702-29,07=114,632. Ini adalah rata-rata secara keseluruhan dua menit (120 detik). Jadi, harga rata-ratanya adalah 114,632/120 = 0,955.
Perhitungan harga ini merupakan alasan kita perlu mengetahui ukuran cadangan lamanya.
1 reserve0 = uint112(balance0);2 reserve1 = uint112(balance1);3 blockTimestampLast = blockTimestamp;4 emit Sync(reserve0, reserve1);5 }Salin
Akhirnya, perbarui variabel global dan pancarkan aksi Sinkronisasi
.
_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) {Salin
Dalam Uniswap 2.0, pedagang membayar 0,30% biaya untuk menggunakan pasar. Sebagian besar biaya itu (0,25% dari perdagangan) selalu beralih ke penyedia likuiditas. Sisa 0,05% dapat beralih ke penyedia likuiditas atau ke alamat yang ditentukan oleh pabrik sebagai biaya protokol, yang membayar Uniswap untuk upaya pengembangan mereka.
Untuk mengurangi perhitungan (dan oleh karena itu biaya gas), biaya ini hanya dihitung ketika likuiditas ditambahkan atau dihapuskan dari pool, dibandingkan pada setiap transaksi.
1 address feeTo = IUniswapV2Factory(factory).feeTo();2 feeOn = feeTo != address(0);Salin
Baca biaya tujuan pabrik. Jika nilainya nol, maka tidak ada biaya protokol dan tidak perlu menghitung biaya tersebut.
1 uint _kLast = kLast; // gas savingsSalin
Variabel status kLast
terletak dalam penyimpanan, sehingga akan memiliki nilai di antara panggilan kontrak yang berbeda. Akses ke penyimpanan jauh lebih mahal dibandingkan akses ke memori tidak stabil yang dirilis ketika panggilan fungsi kontrak berakhir, sehingga kita menggunakan variabel internal untuk menghemat gas.
1 if (feeOn) {2 if (_kLast != 0) {Salin
Para penyedia likuiditas mendapatkan potongan mereka cukup dengan kenaikan token likuiditas mereka. Namun, biaya protokol memerlukan token likuiditas baru untuk dicetak dan disediakan ke alamat feeTo
.
1 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));2 uint rootKLast = Math.sqrt(_kLast);3 if (rootK > rootKLast) {Salin
Jika ada likuiditas baru untuk mengumpulkan biaya protokol. Anda dapat melihat fungsi akar pangkat nanti dalam artikel ini
1 uint numerator = totalSupply.mul(rootK.sub(rootKLast));2 uint denominator = rootK.mul(5).add(rootKLast);3 uint liquidity = numerator / denominator;Salin
Perhitungan biaya yang rumit ini dijelaskan dalam laporan resmi(opens in a new tab) pada halaman 5. Kita mengetahui bahwa antara waktu kLast
dihitung dan saat ini tidak ada likuiditas yang ditambahkan atau dihapus (karena kita menjalankan perhitungan ini setiap kali likuiditas ditambahkan atau dikurangi, sebelum benar-benar berubah), sehingga perubahan mana pun dalam reserve0 * reserve1
harus berasal dari biaya transaksi (tanpa biaya tersebut, kita akan membuat reserve0 * reserve1
menjadi konstan).
1 if (liquidity > 0) _mint(feeTo, liquidity);2 }3 }Salin
Gunakan fungsi UniswapV2ERC20._mint
untuk benar-benar membuat token-token likuiditas tambahan dan tugaskan mereka ke feeTo
.
1 } else if (_kLast != 0) {2 kLast = 0;3 }4 }Salin
Jika tidak ada biaya, tetapkan kLast
menjadi nol (jika belum menjadi nol). Ketika kontrak ini ditulis terdapat fitur pengembalian dana gas(opens in a new tab) yang mendorong kontrak untuk mengurangi ukuran keseluruhan status Ethereum dengan mengosongkan penyimpanan yang tidak diperlukan. Kode ini mendapatkan pengembalian dana tersebut jika memungkinkan.
Fungsi yang Dapat Diakses secara Eksternal
Perhatikan bahwa ketika transaksi atau kontrak mana pun dapat memanggil fungsi-fungsi ini, fungsi-fungsi tersebut dirancang untuk dipanggil dari kontrak perifer. Jika Anda memanggil fungsi-fungsi tersebut secara langsung, Anda tidak akan dapat mencurangi bursa pasangan, tetapi Anda dapat kehilangan nilai karena melakukan kesalahan.
cetak
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) {Salin
Fungsi ini dipanggil ketika penyedia likuiditas menambahkan likuiditas ke pool. Fungsi ini mencetak token likuiditas tambahan sebagai imbalan. Fungsi ini seharusnya dipanggil dari kontrak perifer yang memanggilnya setelah menambahkan likuiditas dalam transaksi yang sama (sehingga tidak seorang pun akan dapat mengirimkan transaksi yang mengklaim likuiditas baru sebelum pemilik yang sah).
1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savingsSalin
Inilah cara membaca hasil dari fungsi Solidity yang mengembalikan beberapa nilai. Kita membuang nilai terakhir yang dikembalikan, stempel waktu blok, karena kita tidak memerlukannya.
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);Salin
Dapatkan saldo saat ini dan lihat seberapa banyak yang ditambahkan untuk setiap jenis token.
1 bool feeOn = _mintFee(_reserve0, _reserve1);Salin
Hitung biaya protokol yang akan dikumpulkan, jika ada, dan cetak token likuiditas yang sesuai. Karena parameter untuk _mintFee
adalah nilai cadangan lama, biaya dihitung secara akurat hanya didasarkan pada perubahan pool karena biaya.
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 tokensSalin
Jika ini adalah setoran pertama, buat token-token MINIMUM_LIQUIDITY
dan kirimkan ke alamat nol untuk membukanya. Token-token tersebut tidak pernah dapat ditebus, artinya pool tidak akan pernah dikosongkan seluruhnya (menghindarkan kita dari pembagian dengan nol di beberapa tempat). Nilai dari MINIMUM_LIQUIDITY
adalah seribu, yang mempertimbangkan sebagian besar ERC-20 dibagi lagi menjadi unit-unit dari token ke 10^-18, seperti ETH dibagi menjadi wei, adalah 10^-15 untuk nilai dari token tunggal. Bukan biaya yang besar.
Pada saat setoran pertama, kita tidak mengetahui nilai relatif dari kedua token, sehingga kita hanya mengalikan jumlahnya dan mengambil akar pangkat, dengan asumsi bahwa setoran menyediakan nilai yang sama dalam kedua token.
Kita dapat mempercayainya karena diperhatikan penyetor untuk menyediakan nilai yang sama, untuk menghindari kehilangan nilai terhadap arbitrase. Anggap bahwa nilai dari kedua token sama, tetapi penyetor kita menyetor empat kali Token1 yang lebih banyak dari Token0. Pedagang dapat menggunakan fakta bahwa bursa pasangan menganggap Token0 lebih berharga untuk diesktrak nilainya.
Aksi | reserve0 | reserve1 | reserve0 * reserve1 | Nilai dari pool (reserve0 + reserve1) |
---|---|---|---|---|
Pengaturan awal | 8 | 32 | 256 | 40 |
Pedagang menyetor 8 token Token0, mendapatkan kembali 16 Token1 | 16 | 16 | 256 | 32 |
Seperti yang Anda lihat, pedagang mendapatkan 8 token tambahan, yang didapatkan dari pengurangan nilai pool, yang merugikan penyetor yang memilikinya.
1 } else {2 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);Salin
Dengan setiap setoran berikutnya, kita telah mengetahui nilai tukar antara kedua aset, dan kita mengharapkan penyedia likuiditas untuk memberikan nilai yang sama pada kedua aset. Jika tidak, kita memberikan token likuiditas didasarkan pada nilai yang lebih kecil yang mereka berikan sebagai hukuman.
Baik merupakan setoran awal maupun setoran berikutnya, jumlah token likuiditas yang kita sediakan sama dengan akar kuadrat dari perubahan dalam reserve0*reserve1
dan nilai token likuiditas tidak berubah (kecuali jika kita mendapatkan setoran yang tidak memiliki nilai yang sama untuk kedua jenis token, yang dalam kasus ini "denda" dibagikan). Berikut adalah contoh lain dengan dua token yang memiliki nilai sama, dengan tiga setoran yang baik dan satu setoran yang buruk (setoran hanya dari satu jenis token, sehingga tidak menghasilkan token likuiditas mana pun).
Aksi | reserve0 | reserve1 | reserve0 * reserve1 | Nilai pool (reserve0 + reserve1) | Token likuiditas yang dicetak untuk setoran ini | Total token likuiditas | nilai dari setiap token likuiditas |
---|---|---|---|---|---|---|---|
Pengaturan awal | 8,000 | 8,000 | 64 | 16,000 | 8 | 8 | 2,000 |
Menyetor empat dari setiap jenis | 12,000 | 12,000 | 144 | 24,000 | 4 | 12 | 2,000 |
Menyetor dua dari setiap jenis | 14,000 | 14,000 | 196 | 28,000 | 2 | 14 | 2,000 |
Nilai setoran tidak sama | 18,000 | 14,000 | 252 | 32,000 | 0 | 14 | ~2,286 |
Setelah arbitrase | ~15,874 | ~15,874 | 252 | ~31,748 | 0 | 14 | ~2,267 |
1 }2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');3 _mint(to, liquidity);Salin
Gunakan fungsi UniswapV2ERC20._mint
untuk benar-benar membuat token likuiditas tambahan dan memberikannya ke akun yang benar.
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 }Salin
Perbarui variabel status (reserve0
, reserve1
, dan jika diperlukan kLast
) dan pancarkan aksi yang sesuai.
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) {Salin
Fungsi ini dipanggil ketika likuiditas ditarik dan token likuiditas yang sesuai harus dibakar. Selain itu, seharusnya dipanggil dari akun perifer.
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)];Salin
Kontrak perifer mentransfer likuiditas yang akan dibakar ke kontrak ini sebelum panggilan. Dengan cara itu, kita mengetahui seberapa banyak likuiditas yang akan dibakar, dan kita dapat memastikan telah dibakar.
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');Salin
Penyedia likuiditas menerima nilai yang sama dari kedua token. Dengan cara ini, kita tidak mengubah nilai tukar.
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 }11Tampilkan semuaSalin
Selebihnya dari fungsi bakar
adalah gambar cermin dari fungsi cetak
di atas.
menukar
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 {Salin
Fungsi ini juga diperuntukkan untuk dipanggil dari kontrak perifer.
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 errorsSalin
Variabel lokal dapat disimpan dalam memori atau, jika tidak ada banyak ruang penyimpanan, secara langsung pada tumpukan. Jika kita dapat membatasi jumlahnya, sehingga kita akan menggunakan tumpukan, kita menggunakan lebih sedikit gas. Untuk rincian selengkapnya, lihat laporan resmi kuning, spesifikasi Ethereum formal(opens in a new tab), hal. 26, persamaan 298.
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 tokensSalin
Transfer ini bersifat optimistik, karena kita mentransfer sebelum kita yakin bahwa semua kondisi terpenuhi. Ini OKE di Ethereum karena jika kondisinya tidak terpenuhi nantinya dalam panggilan, kita membalikkannya bersamaan dengan perubahan apa pun yang dibuat.
1 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);Salin
Beri tahu penerima tentang penukaran jika diminta.
1 balance0 = IERC20(_token0).balanceOf(address(this));2 balance1 = IERC20(_token1).balanceOf(address(this));3 }Salin
Dapatkan saldo saat ini. Kontrak perifer mengirimkan kita token sebelum memanggil kita untuk penukarannya. Mempermudah kontrak dalam memeriksa tidak sedang dicurangi, pemeriksaan yang harus terjadi dalam kontrak inti (karena kita dapat dipanggil oleh entitas selain dari kontrak perifer kita).
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');Salin
Ini adalah pemeriksaan kewarasan untuk memastikan kita tidak mengalami kerugian dari penukarannya. Tidak ada situasi di mana penukaran seharusnya mengurangi reserve0*reserve1
. This is also where we ensure a fee of 0.3% is being sent on the swap; before sanity checking the value of K, we multiply both balances by 1000 subtracted by the amounts multiplied by 3, this means 0.3% (3/1000 = 0.003 = 0.3%) is being deducted from the balance before comparing its K value with the current reserves K value.
1 }23 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 }Salin
Perbarui reserve0
dan reserve1
, dan jika diperlukan pengumpul harga dan stempel waktu dan pancarkan aksi.
Sync or Skim
Saldo asli yang tidak tersinkronisasi dengan cadangan yang dianggap bursa pasangan sebagai miliknya mungkin terjadi. Tidak ada cara untuk menarik token tanpa persetujuan kontrak, tetapi setoran adalah masalah yang berbeda. Suatu akun dapat mentransfer token ke bursa tanpa memanggil cetak
atau tukar
.
Dalam kasus ini, ada dua solusi:
sinkronisasi
, memperbarui cadangan saldo saat iniskim
, menarik jumlah tambahan. Perhatikan bahwa akun mana pun diizinkan untuk memanggilskim
karena kita tidak mengetahui pihak yang menyetor token. Informasi ini dipancarkan dalam aksi, tetapi aksi tidak dapat diakses dari rantai blok.
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}Tampilkan semuaSalin
UniswapV2Factory.sol
Kontrak ini(opens in a new tab) membuat bursa pasangan.
1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';56contract UniswapV2Factory is IUniswapV2Factory {7 address public feeTo;8 address public feeToSetter;Salin
Variabel status ini diperlukan untuk menerapkan biaya protokol (lihat laporan resmi(opens in a new tab), hal. 5). Alamat feeTo
mengumpulkan token likuiditas untuk biaya protokol, dan feeToSetter
adalah alamat yang diizinkan untuk mengubah feeTo
ke alamat yang berbeda.
1 mapping(address => mapping(address => address)) public getPair;2 address[] public allPairs;Salin
Variabel ini menelusuri pasangannya, bursa di antara kedua jenis token.
Yang pertama, getPair
, adalah pemetaan yang mengenali kontrak bursa pasangan didasarkan pada kedua token ERC-20 yang dipertukarkan. Token-token ERC-20 dikenali oleh alamat kontrak yang menerapkan token-token tersebut, sehingga kunci dan nilainya adalah semua alamat. Untuk mendapatkan alamat dari bursa pasangan sehingga membuat Anda dapat mengonversi dari tokenA
ke tokenB
, Anda menggunakan getPair[<tokenA address>][<tokenB address>]
(atau sebaliknya).
Variabel kedua, allPairs
, adalah larik yang mencakup semua alamat dari bursa pasangan yang dibuat oleh pabrik ini. Di Ethereum, Anda tidak dapat mengulang konten dari pemetaan, atau mendapatkan daftar semua kunci, sehingga variabel ini satu-satunya cara untuk mengetahui bursa yang dikelola pabrik ini.
Catatan: Alasan Anda tidak dapat mengulang semua kunci dari pemetaan adalah bahwa penyimpanan data kontrak yang mahal, sehingga semakin jarang kita menggunakannya maka semakin baik, dan semakin jarang kita mengubahnya maka semakin baik. Anda dapat membuat pemetaan yang mendukung pengulangan(opens in a new tab), tetapi memerlukan penyimpanan tambahan untuk daftar kunci. Dalam kebanyakan aplikasi, Anda tidak memerlukannya.
1 event PairCreated(address indexed token0, address indexed token1, address pair, uint);Salin
Aksi ini dipancarkan ketika suatu bursa pasangan baru dibuat. Mencakup alamat token, alamat bursa pasangan, dan total jumlah bursa yang dikelola oleh pabrik.
1 constructor(address _feeToSetter) public {2 feeToSetter = _feeToSetter;3 }Salin
Satu-satunya hal yang dilakukan oleh konstrukstor adalah menentukan feeToSetter
. Pabrik memulai tanpa biaya, dan hanya feeSetter
yang dapat mengubahnya.
1 function allPairsLength() external view returns (uint) {2 return allPairs.length;3 }Salin
Fungsi ini mengembalikan jumlah bursa pasangan.
1 function createPair(address tokenA, address tokenB) external returns (address pair) {Salin
Ini adalah fungsi utama dari pabrik, untuk membuat bursa pasangan di antara dua token ERC-20. Perhatikan bahwa siapa pun dapat memanggil fungsi ini. Anda tidak memerlukan izin dari Uniswap untuk membuat bursa pasangan baru.
1 require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');2 (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);Salin
Kita menginginkan alamat dari bursa baru yang bersifat deterministik, sehingga dapat dihitung sebelum di luar rantai (dapat bermanfaat untuk transaksi lapisan ke-2). Untuk melakukan hal tersebut, kita perlu memiliki urutan alamat token yang konsisten, terlepas dari urutan di mana kita menerimanya, sehingga kita memilahnya di sini.
1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficientSalin
Pool likuiditas besar lebih baik dari pool likuiditas kecil, karena memiliki harga yang lebih stabil. Kita tidak ingin memiliki jumlah yang lebih banyak dari pool likuiditas tunggal per pasangan token. Jika telah ada bursa, tidak perlu membuat bursa baru untuk pasangan yang sama.
1 bytes memory bytecode = type(UniswapV2Pair).creationCode;Salin
Untuk membuat kontrak baru, kita perlu kode yang membuatnya (baik fungsi konstruktor maupun kode yang menulis ke memori kode bita EVM dari kontrak sebenarnya). Secara normal di Solidity, kita hanya menggunakan addr = new <name of contract>(<constructor parameters>)
dan pengompilasi mengurus segala sesuatunya untuk kita, tetapi untuk memiliki akun kontrak deterministik, kita perlu menggunakan opcode CREATE2(opens in a new tab). Ketika kode ini ditulis, opcode ini belum didukung oleh Solidity, sehingga kodenya secara manual perlu didapatkan. Ini bukan lagi masalah, karena Solidity sekarang mendukung CREATE2(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 }Salin
Ketika opcode tidak didukung oleh Solidity, kita dapat memanggilnya menggunakan perakitan sebaris(opens in a new tab).
1 IUniswapV2Pair(pair).initialize(token0, token1);Salin
Panggil fungsi inisialisasi
untuk memberitahu bursa baru dua token yang dipertukarkan.
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 }Salin
Simpan informasi pasangan baru dalam variabel status dan pancarkan aksi untuk memberitahu dunia tentang bursa pasangan barunya.
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}Tampilkan semuaSalin
Kedua fungsi ini membuat feeSetter
dapat mengendalikan penerima biaya (jika ada), dan untuk mengubah feeSetter
ke alamat baru.
UniswapV2ERC20.sol
Kontrak ini(opens in a new tab) menerapkan token likuiditas ERC-20. Sama dengan kontrak ERC-20 OpenWhisk, sehingga saya hanya akan menjelaskan perbedaannya, fungsionalitas izin
.
Transaksi di Ethereum membutuhkan ether (ETH), yang sama dengan uang sebenarnya. Jika Anda memiliki token ERC-20, tetapi bukan ETH, Anda tidak dapat mengirim transaksi, sehingga Anda tidak dapat melakukan apa pun dengannya. Satu solusi untuk menghindari masalah ini adalah transaksi meta(opens in a new tab). Pemilik token menandatangani transaksi yang membuat seseorang lainnya menarik token di luar rantai dan mengirimnya menggunakan Internet kepada penerima. Penerima, yang memiliki ETH, kemudian mengirim izin atas nama pemilik.
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;Salin
Hash ini adalah pengenal jenis transaksi(opens in a new tab). Satu-satunya yang kami dukung di sini adalah Izin
dengan parameter ini.
1 mapping(address => uint) public nonces;Salin
Penerima tidak mungkin memalsukan tanda tangan digital. Namun, tampak remeh untuk mengirim transaksi yang sama dua kali (ini adalah bentuk dari serangan perulangan(opens in a new tab)). Untuk mencegahnya, kita menggunakan nonce(opens in a new tab). Jika nonce dari Izin
yang baru tidak satu kali lebih dari yang terakhir kita gunakan, kita mengganggapnya tidak valid.
1 constructor() public {2 uint chainId;3 assembly {4 chainId := chainid5 }Salin
Ini adalah kode untuk mengambil pengenal rantai(opens in a new tab). Menggunakan dialek perakitan EVM yang disebut Yul(opens in a new tab). Perhatikan bahwa dalam versi Yul saat ini, Anda harus menggunakan chainid()
, bukan chainid
.
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 }Tampilkan semuaSalin
Hitunglah pemisah domain(opens in a new tab) untuk EIP-712.
1 function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {Salin
Ini adalah fungsi yang menerapkan izin. Fungsi ini menerima field yang relevan sebagai parameter, dan ketiga nilai skalar untuk tandatangan(opens in a new tab) (v, r, dan s).
1 require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');Salin
Jangan menerima transaksi setelah tenggat waktu.
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 );Salin
abi.encodePacked(...)
adalah pesan yang kita harap untuk didapatkan. Kita mengetahui nonce seharusnya, sehingga kita tidak perlu mendapatkannya sebagai parameter
Algoritma tandatangan Ethereum berharap mendapatkan 256 bita untuk ditandatangani, sehingga kita menggunakan fungsi hash keccak256
.
1 address recoveredAddress = ecrecover(digest, v, r, s);Salin
Dari intisari dan tandatangan, kita bisa mendapatkan alamat yang ditandatangani menggunakan ecrecover(opens in a new tab).
1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');2 _approve(owner, spender, value);3 }4Salin
Jika semuanya OKE, anggaplah ini sebagai persetujuan ERC-20(opens in a new tab).
Kontrak Perifer
Kontrak perifer adalah API (antarmuka program aplikasi) untuk Uniswap. Kontrak ini tersedia untuk panggilan eksternal, baik dari kontrak lain maupun aplikasi terdesentralisasi. Anda dapat memanggil kontrak inti secara langsung, tetapi lebih rumit dan Anda mungkin kehilangan nilai jika Anda membuat kesalahan. Kontrak-kontrak inti hanya memuat tes untuk memastikan tidak dicurangi, bukan pemeriksaan kewarasan untuk seseorang lainnya. Kontrak-kontrak itu terdapat dalam perifer, sehingga dapat diperbarui sesuai keperluan.
UniswapV2Router01.sol
Kontrak ini(opens in a new tab) memiliki masalah, dan seharusnya tidak lagi digunakan(opens in a new tab). Untungnya, kontrak perifer bersifat tanpa status dan tidak menampung aset apa pun, sehingga mudah untuk mengusangkannya dan menyarankan orang-orang menggunakan penggantinya, UniswapV2Router02
, sebagai gantinya.
UniswapV2Router02.sol
Dalam kebanyakan kasus, Anda akan menggunakan Uniswap melalui kontrak ini(opens in a new tab). Anda dapat melihat cara menggunakannya di sini(opens in a new tab).
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';Tampilkan semuaSalin
Kebanyakan dari kontrak-kontrak ini, kita telah menemuinya sebelumnya, atau cukup jelas. Satu-satunya pengecualian adalah IWETH.sol
. Uniswap v2 membuat perdagangan untuk pasangan apa pun dari token ERC-20, tetapi ether (ETH) sendiri bukanlah token ERC-20. Mendahului standar dan ditransfer melalui mekanisme unik. Untuk mengaktifkan penggunaan ETH dalam kontrak yang menerapkan token ERC-20, orang-orang menemukan kontrak wrapped ether (WETH)(opens in a new tab). Anda mengirimkan ETH ke kontrak ini, dan mencetaknya untuk Anda dalam jumlah WETH yang setara. Atau, Anda dapat membakar WETH, dan mendapatkan kembali ETH.
1contract UniswapV2Router02 is IUniswapV2Router02 {2 using SafeMath for uint;34 address public immutable override factory;5 address public immutable override WETH;Salin
Perute perlu mengetahui pabrik yang akan digunakan, dan untuk transaksi yang memerlukan WETH, kontrak WETH yang akan digunakan. Nilai-nilai ini tidak dapat diubah(opens in a new tab), artinya hanya dapat ditetapkan di konstruktor. Memberikan kepercayaan diri kepada pengguna bahwa tidak seorang pun akan dapat mengubahnya menjadi kontrak yang kurang jujur.
1 modifier ensure(uint deadline) {2 require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');3 _;4 }Salin
Pemodifikasi ini memastikan transaksi-transaksi yang dibatasi waktu ("lakukan X sebelum waktu Y jika Anda bisa") tidak terjadi setelah batas waktu mereka.
1 constructor(address _factory, address _WETH) public {2 factory = _factory;3 WETH = _WETH;4 }Salin
Konstruktor cukup menetapkan variabel statu yang tidak dapat diubah.
1 receive() external payable {2 assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract3 }Salin
Fungsi ini dipanggil ketika kita menebus token dari kontrak WETH kembali menjadi ETH. Hanya kontrak WETH yang kita gunakan diizinkan untuk melakukan penebusan tersebut.
Menambah Likuiditas
Fungsi ini menambahkan token ke bursa pasangan, yang meningkatkan pool likuiditas.
12 // **** ADD LIQUIDITY ****3 function _addLiquidity(Salin
Fungsi ini digunakan untuk menghitung jumlah token A dan B yang seharusnya disetor ke bursa pasangan.
1 address tokenA,2 address tokenB,Salin
Ini adalah alamat kontrak token ERC-20.
1 uint amountADesired,2 uint amountBDesired,Salin
Ini adalah jumlah yang ingin disetor oleh penyedia likuiditas. Selain itu, jumlah tersebut adalah jumlah maksimum A dan B yang akan disetor.
1 uint amountAMin,2 uint amountBMinSalin
Ini adalah jumlah minimum yang dapat diterima untuk disetor. Jika transaksi tidak dapat terjadi dengan jumlah ini atau lebih, balikkan. Jika Anda tidak menginginkan fitur ini, cukup tentukan nol.
Penyedia likuiditas menentukan jumlah minimum, biasanya, karena mereka ingin membatasi transaksi dalam nilai tukar yang dekat dengan nilai tukar saat ini. Jika nilai tukar berfluktuasi terlalu banyak, itu dapat berarti berita mengubah nilai dasarnya, dan ingin memutuskan secara manual hal yang harus dilakukan.
Contohnya, bayangkan kasus di mana nilai tukar adalah satu banding satu dan penyedia likuiditas menentukan nilai-nilai ini:
Parameter | Nilai |
---|---|
amountADesired | 1000 |
amountBDesired | 1000 |
amountAMin | 900 |
amountBMin | 800 |
Selama nilai tukar tetap berada antara 0,9 dan 1,25 maka transaksi terjadi. Jika nilai tukar keluar dari kisaran itu, transaksi dibatalkan.
Alasan untuk pencegahan ini adalah transaksi yang tidak terjadi dengan segera, Anda mengirimkannya dan pada akhirnya penambang akan memasukkannya dalam blok (kecuali jika harga gas Anda sangat rendah, dalam kasus ini Anda perlu mengirim transaksi lainnya dengan nonce yang sama dan harga gas yang lebih tinggi untuk menimpanya). Anda tidak dapat mengendalikan hal yang terjadi selama interval di antara pengiriman dan pemasukan.
1 ) internal virtual returns (uint amountA, uint amountB) {Salin
Fungsi mengembalikan jumlah yang seharusnya disetor penyedia likuiditas agar memiliki rasio setara dengan rasio sekarang di antara cadangan.
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 }Salin
Jika belum ada bursa untuk pasangan token ini, buatlah.
1 (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);Salin
Dapatkan cadangan saat ini dalam pasangan.
1 if (reserveA == 0 && reserveB == 0) {2 (amountA, amountB) = (amountADesired, amountBDesired);Salin
Jika cadangan saat ini kosong, maka ini adalah bursa pasangan baru. Jumlah yang akan disetor seharusnya sama persis dengan jumlah yang ingin disediakan oleh penyedia likuiditas.
1 } else {2 uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);Salin
Jika kita perlu melihat berapa jumlahnya, kita mendapatkan jumlah optimal menggunakan fungsi ini(opens in a new tab). Kita ingin rasionya sama seperti cadangan saat ini.
1 if (amountBOptimal <= amountBDesired) {2 require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 (amountA, amountB) = (amountADesired, amountBOptimal);Salin
Jika amountBOptimal
lebih kecil dari jumlah yang ingin disetor penyedia likuiditas, artinya token B lebih berharga saat ini daripada yang dipikirkan penyetor likuiditas, sehingga diperlukan jumlah yang lebih kecil.
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);Salin
Jika jumlah optimal B lebih dari jumlah B yang diinginkan, artinya token B kurang berharga saat ini daripada yang dipikirkan penyetor likuiditas, sehingga diperlukan jumlah yang lebih tinggi. Namun, jumlah yang diinginkan adalah maksimum, sehingga kita tidak dapat melakukan hal tersebut. Sebagai gantinya, kita menghitung jumlah optimal token A untuk jumlah token B yang diinginkan.
Dengan menggabungkannya, kita mendapatkan grafik ini. Asumsikan Anda mencoba menyetor seribu token A (garis biru) dan seribu token B (garis merah). Sumbu x adalah nilai tukar, A/B. Jika x=1, mereka sama dalam nilai dan Anda menyetorkan masing-masing seribu. Jika x=2, nilai A dua kali nilai B (Anda mendapatkan dua token B untuk setiap token A) sehingga Anda menyetor seribu token B, tetapi hanya 500 token A. Jika x=0,5, situasi terbalik, seribu token A dan lima ratus token B.
1 }2 }3 }Salin
Anda dapat menyetor likuiditas secara langsung ke dalam kontrak inti (menggunakan UniswapV2Pair::mint(opens in a new tab)), tetapi kontrak inti hanya memeriksa apakah tidak dicurangi diri sendiri, sehingga Anda berisiko kehilangan nilai jika nilai tukar berubah pada saat antara waktu Anda mengirimkan transaksi Anda dan waktu pelaksanaan. Jika Anda menggunakan kontrak perifer, kontrak tersebut menggambarkan jumlah yang seharusnya Anda setor dan menyetornya dengan segera, sehingga nilai tukar tidak berubah dan Anda tidak kehilangan apa pun.
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 deadlineTampilkan semuaSalin
Fungsi ini dapat dipanggil melalui transaksi untuk menyetor likuiditas. Kebanyakan parameter sama seperti dalam _addLiquidity
di atas, dengan dua pengecualian:
. ke
adalah alamat yang mendapatkan token likuiditas baru yang dicetak untuk menunjukkan porsi pool penyedia likuiditas . tenggat waktu
adalah batas waktu transaksi
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);Salin
Kita menghitung jumlah yang benar-benar disetor dan kemudian menemukan alamat pool likuditas. Untuk menghemat gas, kita tidak melakukan hal tersebut dengan meminta pabrik, tetapi menggunakan fungsi pustaka pairFor
(lihat di bawah di pustaka)
1 TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);2 TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);Salin
Transfer jumlah token yang benar dari pengguna ke bursa pasangan.
1 liquidity = IUniswapV2Pair(pair).mint(to);2 }Salin
Sebagai hasilnya, berikan alamat ke
token likuiditas untuk kepemilikan sebagian dari pool. Fungsi cetak
dari kontrak inti melihat seberapa banyak token tambahan yang dimiliki (dibandingkan dengan yang dimiliki sejak terakhir kali likuiditas berubah) dan mencetak likuiditas sesuai dengan itu.
1 function addLiquidityETH(2 address token,3 uint amountTokenDesired,Salin
Ketika penyedia likuiditas ingin menyediakan likuiditas ke bursa pasangan Token/ETH, ada beberapa perbedaan. Kontrak menangani pembungkusan ETH untuk penyedia likuiditas. Tidak perlu menentukan seberapa banyak ETH yang ingin disetor oleh pengguna, karena pengguna hanya mengirimkannya dengan transaksi (jumlahnya tersedia dalam msg.value
).
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));Tampilkan semuaSalin
Untuk menyetor ETH, kontrak pertama-tama dibungkus ke WETH dan kemudian mentransfer WETH ke bursa pasangan. Perhatikan bahwa transfer dibungkus dalam assert
. Artinya jika transfer gagal, panggilan kontrak ini juga gagal, dan oleh karena itu pembungkusan tidak benar-benar terjadi.
1 liquidity = IUniswapV2Pair(pair).mint(to);2 // refund dust eth, if any3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);4 }Salin
Pengguna telah mengirimkan ETH kepada kami, sehingga jika ada sisa ekstra (karena token lainnya kurang berharga daripada yang dipikirkan pengguna), kita perlu mengajukan pengembalian dana.
Menghapus Likuiditas
Fungsi ini akan menghapus likuiditas dan membayar kembali penyedia likuiditas.
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) {Tampilkan semuaSalin
Kasus paling sederhana dari menghapus likuiditas. Ada jumlah minimum dari setiap token yang disetujui penyedia likuditas untuk diterima, dan harus terjadi sebelum tenggat waktu.
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);Salin
Fungsi bakar
kontrak inti menangani pembayaran kembali token ke pengguna.
1 (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);Salin
Ketika fungsi mengembalikan beberapa nilai, tetapi kita hanya tertarik dengan beberapa dari nilai tersebut, inilah cara kita mendapatkan nilai-nilai tersebut. Bagaimana pun juga, lebih murah dalam penggunaan gas daripada membaca nilai dan tidak pernah menggunakannya.
1 (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);Salin
Terjemahkan jumlah dari cara kontrak inti mengembalikannya (alamat token yang lebih rendah terlebih dahulu) hingga cara pengguna mengharapkannya (sesuai dengan tokenA
dan tokenB
).
1 require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');2 require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 }Salin
Ini OKE untuk melakukan transfer terlebih dahulu dan kemudian memverifikasi ini sah, karena jika tidak demikian, kita akan membalikkan semua perubahan statusnya.
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 }Tampilkan semuaSalin
Hapus likuiditas untuk ETH hampir sama, kecuali kita menerima token WETH dan kemudian menebusnya dengan ETH untuk memberikannya kembali ke penyedia likuiditas.
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 }Tampilkan semuaSalin
Fungsi ini menyampaikan transaksi meta untuk memungkinkan pengguna tanpa ether menarik dari pool, dengan menggunakan mekanisme izin.
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 }24Tampilkan semuaSalin
Fungsi ini dapat digunakan untuk token yang memiliki biaya transfer atau penyimpanan. Ketika token memiliki biaya demikian, kita tidak dapat mengandalkan fungsi removeLiquidity
untuk memberitahu kita seberapa banyak dari token yang kita dapatkan kembali, sehingga kita perlu menariknya terlebih dahulu dan kemudian mendapatkan saldonya.
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 }Tampilkan semuaSalin
Fungsi terakhir menggabungkan biaya penyimpanan dengan transaksi meta.
Perdagangkan
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 {Salin
Fungsi melakukan pemrosesan internal yang diperlukan untuk fungsi yang terpapar ke pedagang.
1 for (uint i; i < path.length - 1; i++) {Salin
Ketika saya menulisnya, terdapat 388.160 token ERC-20(opens in a new tab). Jika ada satu bursa pasangan untuk setiap pasangan token, maka akan berjumlah lebih dari 150 miliar bursa pasangan. Keseluruhan rantai, pada saat ini, hanya memiliki 0,1% akun dari jumlah tersebut(opens in a new tab). Sebagai gantinya, fungsi penukaran mendukung konsep jalur. Pedagang dapat mempertukarkan A dengan B, B dengan C, dan C dengan D, sehingga tidak diperlukan bursa pasangan A-D secara langsung.
Harga pada pasar ini cenderung disinkronisasi, karena ketika harga tidak tersinkronisasi, maka membentuk peluang untuk arbitrase. Bayangkan, contohnya, tiga token, A, B, dan C. Ada tiga bursa pasangan, satu bursa untuk setiap pasangan.
- Situasi awal
- Pedagang menjual 24,695 token A dan mendapatkan 25,305 token B.
- Pedagang menjual 24,695 token B untuk 25,305 token C, yang mempertahankan kira-kira 0,61 token B sebagai keuntungan.
- Lalu, pedagang menjual 24,695 token C untuk 25,305 token A, yang mempertahankan kira-kira 0,61 token C sebagai keuntungan. Pedagang juga memiliki 0,61 token A ekstra (jumlah 25,305 yang didapatkan pedagang, dikurangi investasi sebelumnya sebesar 24,695).
Langkah | Bursa A-B | Bursa B-C | Bursa A-C |
---|---|---|---|
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];Salin
Dapatkan pasangan yang kita tangani saat ini, pilah pasangan tersebut (untuk digunakan dengan pasangan) dan dapatkan jumlah output yang diharapkan.
1 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));Salin
Dapatkan jumlah keluar yang diharapkan, pilah seperti yang diharapkan melalui cara bursa pasangan.
1 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;Salin
Apakah ini bursa terakhir? Jika demikian, kirim token yang diterima untuk perdagangan ke tujuan. Jika tidak, kirimkan ke bursa pasangan berikutnya.
12 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(3 amount0Out, amount1Out, to, new bytes(0)4 );5 }6 }Salin
Benar-benar memanggil bursa pasangan untuk menukar token. Kita tidak memerlukan panggilan kembali untuk mengetahui tentang bursanya, sehingga kita tidak perlu mengirim bita apa pun dalam field tersebut.
1 function swapExactTokensForTokens(Salin
Fungsi ini digunakan secara langsung oleh pedagang untuk menukar satu token dengan token lainnya.
1 uint amountIn,2 uint amountOutMin,3 address[] calldata path,Salin
Parameter ini memuat alamat dari kontrak ERC-20. Seperti dijelaskan di atas, ini adalah larik karena Anda mungkin perlu melalui beberapa bursa pasangan untuk memindahkan aset yang Anda miliki ke aset yang Anda inginkan.
Parameter fungsi dalam solidity dapat disimpan di memori
atau calldata
. Jika fungsi adalah titik masuk ke kontrak, yang dapat dipanggil secara langsung dari pengguna (menggunakan transaksi) atau dari kontrak berbeda, maka nilai parameter dapat diambil secara langsung dari panggilan data. Jika fungsi dipanggil secara internal, seperti _swap
di atas, maka parameter harus disimpan dalam memori
. Dari perspektif kontrak yang dipanggil, calldata
hanya untuk dibaca.
Dengan jenis skalar seperti uint
atau alamat
, pengompilasi menangani pilihan penyimpanan untuk kita, tetapi dengan larik, yang lebih panjang dan mahal, kita menentukan jenis penyimpanan yang akan digunakan.
1 address to,2 uint deadline3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {Salin
Nilai pengembalian selalu dikembalikan dalam memori.
1 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);2 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');Salin
Hitung jumlah yang akan dibeli dalam setiap penukaran. Jika hasilnya kurang dari jumlah minimum yang ingin diterima oleh pedagang, balikkan transaksinya.
1 TransferHelper.safeTransferFrom(2 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]3 );4 _swap(amounts, path, to);5 }Salin
Akhirnya, transfer token ERC-20 awal ke akun untuk bursa pasangan pertama dan panggil _swap
. Ini semua terjadi dalam transaksi yang sama, sehingga bursa pasangan mengetahui bahwa token mana pun yang tidak diharapkan adalah bagian dari transfer ini.
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 }Tampilkan semuaSalin
Fungsi sebelumnya, swapTokensForTokens
, membuat pedagang dapat menentukan jumlah persis dari token input yang ingin diberikan dan jumlah minimum dari token output yang ingin diterima sebagai hasilnya. Fungsi ini memang membalikkan penukaran, fungsi ini membuat pedagang dapat menentukan jumlah token output yang diinginkan, dan jumlah maksimum dari token input yang ingin dibayarkan untuk pedagang.
Dalam kedua kasus, pedagang harus memberikan kontrak perifer pertama ini tunjangan agar dapat mentransfer.
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 }Tampilkan semuaSalin
Keempat varian ini semuanya melibatkan perdagangan antara ETH dan token. Satu-satunya perbedaan adalah kita menerima ETH dari pedagang dan menggunakannya untuk mencetak WETH, atau kita menerima WETH dari bursa terakhir dalam jalur dan membakarnya, mengirimkan kembali ETH yang dihasilkan ke pedagang.
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 {Salin
Ini adalah fungsi internal untuk menukar token yang memiliki biaya transfer atau penyimpanan untuk menyelesaikan (masalah ini(opens in a new tab)).
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);Tampilkan semuaSalin
Karena biaya transfer, kita tidak dapat mengandalkan fungsi getAmountsOut
untuk memberitahu kita seberapa banyak yang kita dapatkan dari setiap transfer (cara kita melakukannya sebelum memanggil _swap
yang sebenarnya). Sebagai gantinya, kita harus mentransfer terlebih dahulu dan kemudian melihat berapa sebanyak token yang kita dapatkan kembali.
Catatan: Dalam teori, kita cukup dapat menggunakan fungsi ini alih-alih _swap
, tetapi dalam kasus tertentu (contohnya, jika transfer dibalikkan karena tunjangan tidak cukup pada akhirnya untuk memenuhi jumlah minimum yang diharuskan) maka akan menyebabkan gas yang lebih banyak. Token-token biaya transfer cukup langka, sehingga sementara kita perlu mengakomodasinya, tidak menjadi keharusan untuk menukar semua token agar menganggap melewati minimal salah satu dari token tersebut.
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 }Tampilkan semuaSalin
Ini varian-varian yang sama yang digunakan untuk token normal, tetapi memanggil _swapSupportingFeeOnTransferTokens
sebagai gantinya.
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}Tampilkan semuaSalin
Fungsi ini hanyalah proksi yang memanggil fungsi UniswapV2Library.
UniswapV2Migrator.sol
Kontrak ini digunakan untuk memindahkan bursa dari v1 yang lama ke v2. Sekarang, karena mereka telah bermigrasi, hal tersebut tidak lagi relevan.
Pustaka
Pustaka SafeMath(opens in a new tab) didokumentasikan dengan baik, sehingga tidak diperlukan untuk mendokumentasikannya di sini.
Math
Pustaka ini memuat beberapa fungsi matematika yang biasanya tidak diperlukan dalam kode Solidity, sehingga fungsi-fungsi ini bukanlah bagian dari bahasa.
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;Tampilkan semuaSalin
Mulai dengan x sebagai perkiraan yang lebih tinggi dari akar kuadrat (yang adalah alasan kita perlu menganggap 1-3 sebagai kasus khusus).
1 while (x < z) {2 z = x;3 x = (y / x + x) / 2;Salin
Dapatkan perkiraan yang lebih mendekati, rata-rata dari perkiraan sebelumnya dan jumlah yang akar kuadratnya sedang kita coba cari dibagi dengan perkiraan sebelumnya. Ulangi hingga perkiraan baru tidak lebih rendah dari perkiraan yang sudah ada. Untuk mendapatkan rincian selengkapnya, lihat di sini(opens in a new tab).
1 }2 } else if (y != 0) {3 z = 1;Salin
Kita seharusnya tidak akan pernah memerlukan akar kuadrat dari nol. Akar kuadrat dari satu, dua, dan tiga kira-kira satu (kita menggunakan bilangan bulat, sehingga kita mengabaikan pecahan).
1 }2 }3}Salin
Pecahan Poin Tetap (UQ112x112)
Pustaka ini menangani pecahan, yang biasanya bukan bagian dari aritmatika Ethereum. Dilakukan dengan mengodekan angka x sebagai x*2^112. Membuat kita dapat menggunakan opcode penambahan dan pengurangan asli tanpa perubahan.
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;Tampilkan semuaSalin
Q112
adalah pengodean untuk satu.
1 // encode a uint112 as a UQ112x1122 function encode(uint112 y) internal pure returns (uint224 z) {3 z = uint224(y) * Q112; // never overflows4 }Salin
Because y is uint112
, the most it can be is 2^112-1. Angka tersebut masih dapat dikodekan sebagai UQ112x112
.
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}Salin
Jika kita membagi dua nilai UQ112x112
, hasilnya tidak lagi dikalikan dengan 2^112. Jadi, sebagai gantinya, kita mengambil bilangan bulat sebagai penyebut. Kita perlu menggunakan trik yang serupa untuk melakukan perkalian, tetapi kita tidak perlu melakukan perkalian untuk nilai UQ112x112
.
UniswapV2Library
Pustaka ini hanya digunakan oleh kontrak perifer
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 }Tampilkan semuaSalin
Pilah kedua token berdasarkan alamat, sehingga kita akan bisa mendapatkan alamat dari bursa pasangan untuk kedua token. Diperlukan karena jika tidak, kita akan memiliki dua kemungkinan, satu kemungkinan untuk parameter A,B, dan kemungkinan lain untuk parameter B,A, yang menghasilkan dua bursa alih-alih satu bursa.
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 }Tampilkan semuaSalin
Fungsi ini menghitung alamat dari bursa pasangan untuk kedua token. Kontrak ini dibuat menggunakan opcode CREATE2(opens in a new tab), sehingga kita dapat menghitung alamat menggunakan algoritma yang sama jika kita mengetahui parameter yang digunakan. Jauh lebih murah daripada meminta pabrik, dan
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 }Salin
Fungsi ini mengembalikan cadangan dari kedua token yang dimiliki bursa pasangan. Perhatikan bahwa fungsi tersebut dapat menerima token-token dalam urutan mana pun, dan memilahnya untuk penggunaan internal.
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 }Salin
Fungsi ini memberikan Anda jumlah token B yang akan Anda dapatkan sebagai hasil untuk token A jika tidak ada biaya yang dilibatkan. Perhitungan ini mempertimbangkan bahwa transfernya mengubah nilai tukar.
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) {Salin
Fungsi kutipan
di atas berfungsi sangat baik jika tidak ada biaya untuk menggunakan bursa pasangan. Namun, jika ada biaya bursa 0,3%, jumlah yang benar-benar Anda dapatkan lebih rendah. Fungsi ini menghitung jumlah setelah biaya bursa.
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 }Salin
Solidity aslinya tidak menangani pecahan, sehingga kita tidak dapat hanya mengalikan jumlah keluar dengan 0,997. Sebagai gantinya, kita mengalikan pembilang dengan 997 dan penyebut dengan 1000, yang memberikan efek yang sama.
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 }Salin
Fungsi ini kira-kira melakukan hal yang sama, tetapi mendapatkan jumlah output dan menyediakan input.
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}Tampilkan semuaSalin
Kedua fungsi ini menangani pengenalan nilai ketika diperlukan untuk melalui beberapa bursa pasangan.
Pembantu Transfer
Pustaka ini(opens in a new tab) menambahkan pemeriksaan keberhasilan seputar transfer ERC-20 dan Ethereum untuk memperlakukan pembalikan dan nilai salah
pengembalian dalam cara yang sama.
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));14Tampilkan semuaSalin
Kita dapat memanggil kontrak yang berbeda dalam salah satu dari dua cara:
- Menggunakan definisi antarmuka untuk membuat panggilan fungsi
- Gunakan antarmuka biner aplikasi (ABI)(opens in a new tab) "secara manual" untuk melakukan panggilan. Hal ini yang diputuskan untuk dilakukan oleh penulis kode.
1 require(2 success && (data.length == 0 || abi.decode(data, (bool))),3 'TransferHelper::safeApprove: approve failed'4 );5 }Salin
Demi kompatibilitas mundur dengan token yang dibuat sebelum standar ERC-20, panggilan ERC-20 dapat menjadi gagal baik dengan membalikkan (dalam kasus tersebut success
adalah false
) atau dengan menjadi sukses dan mengembalikan nilai false
(dalam kasus tersebut terdapat data output, dan jika Anda mengartikan kode sebagai boolean, Anda mendapatkan false
).
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 }Tampilkan semuaSalin
Fungsi ini menerapkan fungsionalitas transfer ERC-20(opens in a new tab), sehingga akun dapat menghabiskan tunjangan yang disediakan oleh akun berbeda.
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 }Tampilkan semuaSalin
Fungsi ini menerapkan fungsionalitas transferFrom ERC-20(opens in a new tab), sehingga akun dapat menghabiskan tunjangan yang disediakan oleh akun yang berbeda.
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}Salin
Fungsi ini mentransfer ether ke akun. Setiap panggilan ke kontrak yang berbeda dapat mencoba mengirimkan ether. Karena kita sebenarnya tidak perlu memanggil fungsi apa pun, kita tidak mengirim data apa pun dengan panggilan tersebut.
Kesimpulan
Ini adalah artikel dengan panjang sekitar 50 halaman. Jika Anda berhasil sampai di sini, selamat! Semoga saat ini Anda telah memahami pertimbangan dalam menulis aplikasi kehidupan nyata (dibandingkan dengan program sampel singkat) dan lebih baik agar dapat menulis kontrak untuk kasus penggunaan Anda sendiri.
Sekarang, lanjutkan dan tulis sesuatu yang berguna dan buat kami takjub.
Terakhir diedit: @wackerow(opens in a new tab), 2 April 2024