Lanjut ke konten utama

Panduan Lengkap Kontrak Uniswap-v2

solidity
Tingkat menengah
Ori Pomerantz
1 Mei 2021
55 bacaan singkat minute read

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:

  1. Menukar antara token-token yang berbeda
  2. Menambahkan likuiditas ke pasar dan mendapatkan imbalan dengan bursa pasangan token likuiditas ERC-20
  3. 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

  1. Menyediakan akun perifer dengan tunjangan dalam jumlah yang dapat ditukarkan.
  2. 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)

  1. Mengenali jumlah yang perlu diperdagangkan pada setiap bursa sepanjang jalur.
  2. 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)

  1. Verifikasi bahwa kontrak inti tidak dicurangi dan dapat mempertahankan likuiditas yang memadai setelah penukaran.
  2. Melihat seberapa banyak token tambahan yang kita miliki selain dari cadangan yang diketahui. Jumlah tersebut adalah jumlah token input yang kita terima untuk dipertukarkan.
  3. Mengirimkan token output ke tujuan.
  4. Memanggil _update untuk memperbarui jumlah cadangan

Kembali dalam kontrak perifer (UniswapV2Router02.sol)

  1. Melakukan pembersihan mana pun yang diperlukan (contohnya, membakar token WETH untuk mendapatkan kembali ETH yang akan dikirimkan ke pedagang)

Menambah Likuiditas

Pemanggil

  1. Menyediakan akun perifer dengan tunjangan dalam jumlah yang akan ditambahkan ke pool likuiditas.
  2. Memanggil salah satu dari fungsi addLiquidity kontrak perifer.

Dalam kontrak perifer (UniswapV2Router02.sol)

  1. Membuat bursa pasangan baru jika diperlukan
  2. 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.
  3. Memeriksa jika jumlahnya dapat diterima (pemanggil dapat menentukan jumlah minimum di bawah yang tidak akan mereka tambahkan ke likuiditas)
  4. Memanggil kontrak inti.

Dalam kontrak inti (UniswapV2Pair.sol)

  1. Mencetak token likuiditas dan mengirimkannya ke pemanggil
  2. Memanggil _update untuk memperbarui jumlah cadangan

Menghapus Likuiditas

Pemanggil

  1. Menyediakan akun perifer dengan tunjangan dari token likuiditas yang akan dibakar di bursa untuk token yang mendasarinya.
  2. Memanggil salah satu dari fungsi removeLiquidity kontrak perifer.

Dalam kontrak perifer (UniswapV2Router02.sol)

  1. Mengirimkan token likuiditas ke bursa pasangan

Dalam kontrak inti (UniswapV2Pair.sol)

  1. 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.
  2. Membakar token likuiditas
  3. 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;
2
3import './interfaces/IUniswapV2Pair.sol';
4import './UniswapV2ERC20.sol';
5import './libraries/Math.sol';
6import './libraries/UQ112x112.sol';
7import './interfaces/IERC20.sol';
8import './interfaces/IUniswapV2Factory.sol';
9import './interfaces/IUniswapV2Callee.sol';
Tampilkan semua
Salin

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 getReserves
2 uint112 private reserve1; // uses single storage slot, accessible via getReserves
Salin

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 getReserves
Salin

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 event
Salin

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.

Aksireserve0reserve1reserve0 * reserve1Rata-rata nilai tukar (token1 / token0)
Pengaturan awal1.000,0001.000,0001.000.000
Pedagang A menukar 50 token0 untuk 47,619 token11.050,000952,3811.000.0000,952
Pedagang B menukar 10 token0 untuk 8,984 token11.060,000943,3961.000.0000,898
Pedagang C menukar 40 token0 untuk 34,305 token11.100,000909,0901.000.0000,858
Pedangan D menukar 100 token1 untuk 109,01 token0990,9901.009,0901.000.0000,917
Pedagang E menukar 10 token0 untuk 10,079 token11.000,990999,0101.000.0001,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:

  1. Balikkan. If a call to an external contract reverts, then the boolean return value is false
  2. 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 to
8 );
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 deployment
2 function initialize(address _token0, address _token1) external {
3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
4 token0 = _token0;
5 token1 = _token1;
6 }
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 accumulators
2 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 desired
3 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 desired
2 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:

Aksireserve0reserve1stempel waktuNilai tukar marginal (reserve1 / reserve0)price0CumulativeLast
Pengaturan awal1.000,0001.000,0005.0001,0000
Pedagang A menyetor 50 token0 dan mendapatkan kembali 47,619 token11.050,000952,3815.0200,90720
Pedagang B menyetor 10 token0 dan mendapatkan kembali 8,984 token11.060,000943,3965.0300,89020+10*0,907 = 29,07
Pedagang C menyetor 40 token0 dan mendapatkan kembali 34,305 token11.100,000909,0905.1000,82629,07+70*0,890 = 91,37
Pedagang D menyetor 100 token1 dan mendapatkan kembali 109,01 token0990,9901.009,0905.1101,01891,37+10*0,826 = 99,63
Pedagang E menyetor 10 token0 dan mendapatkan kembali 10,079 token11.000,990999,0105.1500,99899,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 savings
Salin

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 checks
2 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 savings
Salin

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 _mintFee
2 if (_totalSupply == 0) {
3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
4 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
Salin

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.

Aksireserve0reserve1reserve0 * reserve1Nilai dari pool (reserve0 + reserve1)
Pengaturan awal83225640
Pedagang menyetor 8 token Token0, mendapatkan kembali 16 Token1161625632

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).

Aksireserve0reserve1reserve0 * reserve1Nilai pool (reserve0 + reserve1)Token likuiditas yang dicetak untuk setoran iniTotal token likuiditasnilai dari setiap token likuiditas
Pengaturan awal8,0008,0006416,000882,000
Menyetor empat dari setiap jenis12,00012,00014424,0004122,000
Menyetor dua dari setiap jenis14,00014,00019628,0002142,000
Nilai setoran tidak sama18,00014,00025232,000014~2,286
Setelah arbitrase~15,874~15,874252~31,748014~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.

1
2 _update(balance0, balance1, _reserve0, _reserve1);
3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
4 emit Mint(msg.sender, amount0, amount1);
5 }
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 checks
2 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 savings
2 address _token0 = token0; // gas savings
3 address _token1 = token1; // gas savings
4 uint balance0 = IERC20(_token0).balanceOf(address(this));
5 uint balance1 = IERC20(_token1).balanceOf(address(this));
6 uint liquidity = balanceOf[address(this)];
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 _mintFee
3 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
4 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
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));
6
7 _update(balance0, balance1, _reserve0, _reserve1);
8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
9 emit Burn(msg.sender, amount0, amount1, to);
10 }
11
Tampilkan semua
Salin

Selebihnya dari fungsi bakar adalah gambar cermin dari fungsi cetak di atas.

1 // this low-level function should be called from a contract which performs important safety checks
2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
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 savings
3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
4
5 uint balance0;
6 uint balance1;
7 { // scope for _token{0,1}, avoids stack too deep errors
Salin

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 tokens
5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
Salin

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 errors
5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
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 }
2
3 _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 ini
  • skim, menarik jumlah tambahan. Perhatikan bahwa akun mana pun diizinkan untuk memanggil skim 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 reserves
2 function skim(address to) external lock {
3 address _token0 = token0; // gas savings
4 address _token1 = token1; // gas savings
5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
7 }
8
9
10
11 // force reserves to match balances
12 function sync() external lock {
13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
14 }
15}
Tampilkan semua
Salin

UniswapV2Factory.sol

Kontrak ini(opens in a new tab) membuat bursa pasangan.

1pragma solidity =0.5.16;
2
3import './interfaces/IUniswapV2Factory.sol';
4import './UniswapV2Pair.sol';
5
6contract UniswapV2Factory is IUniswapV2Factory {
7 address public feeTo;
8 address public feeToSetter;
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 sufficient
Salin

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 direction
3 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 }
5
6 function setFeeToSetter(address _feeToSetter) external {
7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
8 feeToSetter = _feeToSetter;
9 }
10}
Tampilkan semua
Salin

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 := chainid
5 }
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 semua
Salin

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 }
4
Salin

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;
2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
5
6import './interfaces/IUniswapV2Router02.sol';
7import './libraries/UniswapV2Library.sol';
8import './libraries/SafeMath.sol';
9import './interfaces/IERC20.sol';
10import './interfaces/IWETH.sol';
Tampilkan semua
Salin

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;
3
4 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 contract
3 }
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.

1
2 // **** 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 amountBMin
Salin

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:

ParameterNilai
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

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 yet
2 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.

Grafik

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 deadline
Tampilkan semua
Salin

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 deadline
5 ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
6 (amountToken, amountETH) = _addLiquidity(
7 token,
8 WETH,
9 amountTokenDesired,
10 msg.value,
11 amountTokenMin,
12 amountETHMin
13 );
14 address pair = UniswapV2Library.pairFor(factory, token, WETH);
15 TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
16 IWETH(WETH).deposit{value: amountETH}();
17 assert(IWETH(WETH).transfer(pair, amountETH));
Tampilkan semua
Salin

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 any
3 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 deadline
10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
Tampilkan semua
Salin

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 pair
3 (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 deadline
8 ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
9 (amountToken, amountETH) = removeLiquidity(
10 token,
11 WETH,
12 liquidity,
13 amountTokenMin,
14 amountETHMin,
15 address(this),
16 deadline
17 );
18 TransferHelper.safeTransfer(token, to, amountToken);
19 IWETH(WETH).withdraw(amountETH);
20 TransferHelper.safeTransferETH(to, amountETH);
21 }
Tampilkan semua
Salin

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 s
10 ) external virtual override returns (uint amountA, uint amountB) {
11 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
12 uint value = approveMax ? uint(-1) : liquidity;
13 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
14 (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
15 }
16
17
18 function removeLiquidityETHWithPermit(
19 address token,
20 uint liquidity,
21 uint amountTokenMin,
22 uint amountETHMin,
23 address to,
24 uint deadline,
25 bool approveMax, uint8 v, bytes32 r, bytes32 s
26 ) external virtual override returns (uint amountToken, uint amountETH) {
27 address pair = UniswapV2Library.pairFor(factory, token, WETH);
28 uint value = approveMax ? uint(-1) : liquidity;
29 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
30 (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
31 }
Tampilkan semua
Salin

Fungsi ini menyampaikan transaksi meta untuk memungkinkan pengguna tanpa ether menarik dari pool, dengan menggunakan mekanisme izin.

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

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.

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

Fungsi terakhir menggabungkan biaya penyimpanan dengan transaksi meta.

Perdagangkan

1 // **** SWAP ****
2 // requires the initial amount to have already been sent to the first pair
3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
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.

  1. Situasi awal
  2. Pedagang menjual 24,695 token A dan mendapatkan 25,305 token B.
  3. Pedagang menjual 24,695 token B untuk 25,305 token C, yang mempertahankan kira-kira 0,61 token B sebagai keuntungan.
  4. 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).
LangkahBursa A-BBursa B-CBursa A-C
1A:1000 B:1050 A/B=1,05B:1000 C:1050 B/C=1,05A:1050 C:1000 C/A=1,05
2A:1024,695 B:1024,695 A/B=1B:1000 C:1050 B/C=1,05A:1050 C:1000 C/A=1,05
3A:1024,695 B:1024,695 A/B=1B:1024,695 C:1024,695 B/C=1A:1050 C:1000 C/A=1,05
4A:1024,695 B:1024,695 A/B=1B:1024,695 C:1024,695 B/C=1A:1024,695 C:1024,695 C/A=1
1 (address input, address output) = (path[i], path[i + 1]);
2 (address token0,) = UniswapV2Library.sortTokens(input, output);
3 uint amountOut = amounts[i + 1];
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.

1
2 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 deadline
3 ) 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 deadline
7 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
8 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
9 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
10 TransferHelper.safeTransferFrom(
11 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
12 );
13 _swap(amounts, path, to);
14 }
Tampilkan semua
Salin

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

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 pair
3 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 errors
8 (uint reserve0, uint reserve1,) = pair.getReserves();
9 (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
10 amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
11 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
Tampilkan semua
Salin

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 }
7
8
9 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
10 uint amountIn,
11 uint amountOutMin,
12 address[] calldata path,
13 address to,
14 uint deadline
15 ) external virtual override ensure(deadline) {
16 TransferHelper.safeTransferFrom(
17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
18 );
19 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
20 _swapSupportingFeeOnTransferTokens(path, to);
21 require(
22 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
23 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
24 );
25 }
26
27
28 function swapExactETHForTokensSupportingFeeOnTransferTokens(
29 uint amountOutMin,
30 address[] calldata path,
31 address to,
32 uint deadline
33 )
34 external
35 virtual
36 override
37 payable
38 ensure(deadline)
39 {
40 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
41 uint amountIn = msg.value;
42 IWETH(WETH).deposit{value: amountIn}();
43 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
44 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
45 _swapSupportingFeeOnTransferTokens(path, to);
46 require(
47 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
48 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
49 );
50 }
51
52
53 function swapExactTokensForETHSupportingFeeOnTransferTokens(
54 uint amountIn,
55 uint amountOutMin,
56 address[] calldata path,
57 address to,
58 uint deadline
59 )
60 external
61 virtual
62 override
63 ensure(deadline)
64 {
65 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
66 TransferHelper.safeTransferFrom(
67 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
68 );
69 _swapSupportingFeeOnTransferTokens(path, address(this));
70 uint amountOut = IERC20(WETH).balanceOf(address(this));
71 require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
72 IWETH(WETH).withdraw(amountOut);
73 TransferHelper.safeTransferETH(to, amountOut);
74 }
Tampilkan semua
Salin

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 }
5
6 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
7 public
8 pure
9 virtual
10 override
11 returns (uint amountOut)
12 {
13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
14 }
15
16 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
17 public
18 pure
19 virtual
20 override
21 returns (uint amountIn)
22 {
23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
24 }
25
26 function getAmountsOut(uint amountIn, address[] memory path)
27 public
28 view
29 virtual
30 override
31 returns (uint[] memory amounts)
32 {
33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);
34 }
35
36 function getAmountsIn(uint amountOut, address[] memory path)
37 public
38 view
39 virtual
40 override
41 returns (uint[] memory amounts)
42 {
43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);
44 }
45}
Tampilkan semua
Salin

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;
2
3// a library for performing various math operations
4
5library Math {
6 function min(uint x, uint y) internal pure returns (uint z) {
7 z = x < y ? x : y;
8 }
9
10 // babylonian method (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
11 function sqrt(uint y) internal pure returns (uint z) {
12 if (y > 3) {
13 z = y;
14 uint x = y / 2 + 1;
Tampilkan semua
Salin

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;
2
3// a library for handling binary fixed point numbers (https://wikipedia.org/wiki/Q_(number_format))
4
5// range: [0, 2**112 - 1]
6// resolution: 1 / 2**112
7
8library UQ112x112 {
9 uint224 constant Q112 = 2**112;
Tampilkan semua
Salin

Q112 adalah pengodean untuk satu.

1 // encode a uint112 as a UQ112x112
2 function encode(uint112 y) internal pure returns (uint224 z) {
3 z = uint224(y) * Q112; // never overflows
4 }
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 UQ112x112
2 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;
2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
4
5import "./SafeMath.sol";
6
7library UniswapV2Library {
8 using SafeMath for uint;
9
10 // returns sorted token addresses, used to handle return values from pairs sorted in this order
11 function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
12 require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
13 (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
14 require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
15 }
Tampilkan semua
Salin

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 calls
2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
3 (address token0, address token1) = sortTokens(tokenA, tokenB);
4 pair = address(uint(keccak256(abi.encodePacked(
5 hex'ff',
6 factory,
7 keccak256(abi.encodePacked(token0, token1)),
8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
9 ))));
10 }
Tampilkan semua
Salin

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 pair
2 function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
3 (address token0,) = sortTokens(tokenA, tokenB);
4 (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
5 (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
6 }
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 asset
2 function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
3 require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
4 require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
5 amountB = amountA.mul(reserveB) / reserveA;
6 }
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 asset
2 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.

1
2 require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
3 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
4 uint amountInWithFee = amountIn.mul(997);
5 uint numerator = amountInWithFee.mul(reserveOut);
6 uint denominator = reserveIn.mul(1000).add(amountInWithFee);
7 amountOut = numerator / denominator;
8 }
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 asset
2 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
3 require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
4 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
5 uint numerator = reserveIn.mul(amountOut).mul(1000);
6 uint denominator = reserveOut.sub(amountOut).mul(997);
7 amountIn = (numerator / denominator).add(1);
8 }
Salin

Fungsi ini kira-kira melakukan hal yang sama, tetapi mendapatkan jumlah output dan menyediakan input.

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

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-later
2
3pragma solidity >=0.6.0;
4
5// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
6library TransferHelper {
7 function safeApprove(
8 address token,
9 address to,
10 uint256 value
11 ) internal {
12 // bytes4(keccak256(bytes('approve(address,uint256)')));
13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
14
Tampilkan semua
Salin

Kita dapat memanggil kontrak yang berbeda dalam salah satu dari dua cara:

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).

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

Fungsi ini menerapkan fungsionalitas transfer ERC-20(opens in a new tab), sehingga akun dapat menghabiskan tunjangan yang disediakan oleh akun berbeda.

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

Fungsi ini menerapkan fungsionalitas transferFrom ERC-20(opens in a new tab), sehingga akun dapat menghabiskan tunjangan yang disediakan oleh akun yang berbeda.

1
2 function safeTransferETH(address to, uint256 value) internal {
3 (bool success, ) = to.call{value: value}(new bytes(0));
4 require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
5 }
6}
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: @yeremiaryangunadi(opens in a new tab), 2 April 2024

Apakah tutorial ini membantu?