UniswapV2 通過其創新的協議設計和去中心化的運營模式,徹底改變了加密貨幣交易的方式,成為 DeFi 領域的重要基石。
學生作者:@cryptoyzr
指導老師:@CryptoScott_ETH
首發時間:2024.6.27
封面:Uniswap
本篇研報是對 UniswapV2 協定的工作原理、專案構成、源碼分析等部分進行詳細解讀。 工作原理主要是涉及自動做市商(AMM),本篇研報會描述流動性池的創建和管理過程,以及如何通過提供流動性賺取手續費; 在項目構成方面,我們主要概述 UniswapV2 的架構,包括主要合約(如工廠合約、交換合約)及其功能; 在源碼分析部分我們分析 UniswapV2 的智慧合約源碼,解釋關鍵函數和數據結構的設計理念。
1. 協定簡介
UniswapV2 是一種去中心化交易協定,基於乙太坊區塊鏈,允許使用者無需信任仲介即可進行加密貨幣的交易。 與傳統的中心化交易所不同,UniswapV2 採用了自動做市商(AMM)模型,通過智慧合約來管理交易和流動性池,從而實現完全的去中心化。
UniswapV2 的核心在於其恆定乘積公式( x * y = k ),其中 x 和 y 分別代表流動性池中兩種不同資產的數量,k 是一個常數。 這個公式確保了在每次交易后,池中的資產比例會重新平衡,從而為使用者提供流動性。 這種設計使得交易過程透明且公平,用戶可以隨時添加或移除流動性,並通過交易手續費賺取收益。
UniswapV2 協定由多個智慧合約構成,其中最主要的是工廠合約和交換合約。 工廠合約負責創建和管理流動性池,而每個交換合約則對應一個特定的交易對(例如 ETH/DAI)。
此外,UniswapV2 還引入了路由器合約和庫函數,以提高交易效率和安全性。 與其前身 UniswapV1 相比,UniswapV2 帶來了幾項重要改進。 首先是閃電交換功能,允許使用者在單個交易中借入資產,只要在交易結束前歸還即可。 其次是價格預言機,通過累積價格時間加權平均值(TWAP)來提供更可靠的價格數據。
此外,UniswapV2 支援任意 ERC-20 代幣的直接交易,而無需通過乙太坊作為仲介。 UniswapV2 的成功不僅在於其技術創新,還在於其開放性和社區驅動的開發模式。 任何人都可以自由使用和擴展 Uniswap 協定,這為去中心化金融(DeFi)生態系統帶來了無限的可能性。 許多其他 DeFi 專案,如借貸平臺和穩定幣協定,都建立在 Uniswap 的基礎之上,形成了一個繁榮的生態系統。
總的來說,UniswapV2 通過其創新的協議設計和去中心化的運營模式,徹底改變了加密貨幣交易的方式,成為 DeFi 領域的重要基石。 隨著技術的不斷發展和社區的持續創新,UniswapV2 的影響力將進一步擴大,為全球用戶帶來更多的金融自由和機會。
2. 協定特性
2.1 ERC-20 Pa rs
Uniswapv1 會使用乙太坊(ETH)作為過渡貨幣,即如果使用者想進行 TokenA 和 TokenB 交換,需要先使用 TokenA 來換取 ETH ,之後再使用 ETH 來換取 TokenB ,儘管這種措施減少了流動性的分散,但它對流動性提供者造成了極大的成本壓力。 每個流動性提供者都必須具備與 ETH 交換的介面,同時,流動性提供者所持有資產的價值隨著 ETH 的價格波動而波動,可能會導致嚴重的損失。
當兩個資產 ABC 和 XYZ 相關聯時(例如,它們都是美元穩定幣),Uniswap 上的流動性提供者在 ABC / XYZ 對中通常會承受較小的永久損失,相較於 ABC/ETH 或 XYZ/ETH 對。 此外,使用 ETH 作為強制過渡貨幣會增加交易者的成本。 交易者需要支付的費用是直接購買 ABC/XYZ 對費用的兩倍,並且還會遭受兩次滑點。
在 UniswapV2 當中,允許流動性提供者為任意兩個 ERC-20 代幣創建交易對合約。 雖然任意 ERC-20 代幣之間的交易對數量激增,可能會使找到特定資產交易路徑變得更加複雜,但這一問題可以通過更高級別的路由來解決(通過鏈外或鏈上路由器或聚合器)。
2.2 價格預言機
UniswapV1 的價格計算方式是在 t 時刻,Uniswap 所提供的邊際價格(不包含手續費)可以通過用資產 a 的儲備量除以資產 b 的儲備量來獲得。 具體的計算公式如下:
然而,UniswapV1 用作鏈上價格預言機並不安全,因為它非常容易被操縱。 操縱者會在一個區塊的開頭大量賣出某種資產 A 以影響價格,然後在該區塊中間根據波動的價格執行其他合約操作(非 Uniswap 交易對合約),最後在區塊結束時買回相同數量的資產 A,使價格恢復正常水準。 Uniswap V2 引入了價格累計機制,允許第三方使用某一區間的平均價格,從而大大增加了價格操縱的難度,並使得操縱行為無實質收益。
具體來說,Uniswap v2 通過在每個區塊開始時記錄價格的累積和來累積這個價格,其中有人與合約交互。 每個價格根據自上一個區塊更新以來所經過的時間進行加權,根據區塊時間戳。 這意味著,在任何給定時間(更新后),累積器的值應該是合約歷史中每一秒的現貨價格的總和。
為了估算從時間 t₁ 到 t₂ 的時間加權平均價格,外部調用者可以在 t₁ 檢查累積器的值,然後在 t₂ 再次檢查,減去第一個值,然後除以經過的秒數。(注意,合約本身不存儲這個累積器的歷史值——調用者必須在周期開始時調用合約以讀取和存儲這個值。)
預言機的使用者可以選擇何時開始和結束這個週期。 選擇一個更長的週期會使攻擊者操縱時間加權平均價格(TWAP)的成本更高,儘管這會導致價格的最新程度較低。 由於採用了平均值,A/B 和 B/A 在某一區間內的平均值不再是倒數關係,因此 Uniswap V2 提供了這兩種價格。
2.3 價格計算精度
由於 Solidity 不支援非整數的數值數據類型,UniswapV2 採用 UQ112.112 數據格式來提升價格計算的精度,同時使用 uint112 來存儲交易對中的資產數量,並用 32 位來記錄當前區塊的創建時間。 這種時間記錄方式在 100 年後,也就是 2106 年 2 月 7 日,會導致 Unix 時間戳溢出。 這個是因為 Unix 時間戳是以自 1970 年 1 月 1 日(稱為 Unix 紀元)以來經過的秒數來表示時間的,而 uint32 能表示的範圍是從 0 到 2^{32} - 1 秒,即約 136 年。
為了確保系統在 2106 年 2 月 7 日及以後仍能正常工作,UniswapV2 的設計中要求預言機每隔一個溢出週期(約 136 年)至少檢查一次價格。 這是因為在這種設計下,即使時間戳溢出,累積價格的方法仍然是溢出安全的,即使交易跨越溢出間隔也可以正確計算價格變化。 通過這種方法,確保系統在長時間內保持準確和可靠。
2.4 閃電兌換
閃電兌換是一種在區塊鏈平台上進行的即時加密貨幣交換,用戶可以在無需等待多個區塊確認的情況下快速完成不同加密貨幣之間的交易。 這種交易通常由智慧合約自動執行,確保交易雙方同時履行交換承諾,從而降低交易風險並提高效率。 簡答來說,閃電兌換就是先欠再還進行交易,通過閃電兌換可以實現零成本套利。
我們通過一個實際例子來解釋一下閃電兌換,假定 UniswapV2 中存在一個 A/B 的交易對,我們可以先借 A ,從而從 Uniswap 當中獲取 B ,之後再使用 B 從其他的去中心化交易所上獲取 A0,之後歸還 Uniswap 等量的 A 即可。 通過上面這個流程,我們就 0 成本的完成了 A0-A 之間的價差套利。 我們並不需要任何本金,只需要支付 gas 費即可。 如果發現鏈上其他的 dex 之間有價差,也可以對其進行類似的操作進行零成本的套利。
2.5 ProtocolFee
UniswapV2 引入了一個可以開啟和關閉的 0.05% 協定費。 如果開啟,這個費用將發送到 factory 合約中指定的 feeTo 位址。 最初,feeTo 是未設置的,因此不收取費用。 一個預先指定的位址 feeToSetter 可以調用 UniswapV2 factory 合約中的 setFeeTo 函數,設置 feeTo 為不同的值。 feeToSetter 還可以調用 setFeeToSetter 來更改 feeToSetter 位址本身。
如果設置了 feeTo 位址,協定將開始收取 5 個基點的費用,這是流動性提供者賺取的 30 個基點費用的 \frac{1}{6} 。 也就是說,交易者將繼續支付 0.30% 的所有交易費用,其中 83.3%(即 0.25%)將支付給流動性提供者,16.6%(即 0.05%)將支付給 feeTo 位址。 在每次交易時收取這 0.05% 的費用會增加額外的 gas 成本。 為了避免這種情況,累計費用僅在存入或提取流動性時收取。 合約會計算累計費用,並在代幣鑄造或銷毀之前立即將新的流動性代幣鑄造給費用受益人。
累計費用可以通過測量√k 的增長來計算(即 √(x·y)),自上次收取費用以來。 這公式給出了在 t₁ 和 t₂ 之間,作為 t₂ 時流動池中流動性的百分比的累計費用:
如果費用在 t₁ 之前被啟動,feeTo 位址應當捕獲 t₁ 到 t₂ 之間累計費用到 1/6。 因此,我們想要鑄造新的流動性代幣到 feeTo 位址,代表 Φ·f₁,₂ 的池子, 其中Φ=1/6 。
也就是說,我們希望選擇 sm 來滿足以下關係,其中 s₁ 是時間 t₁ 時的流通股的總量:
經過一些運算,包括用 1-
來代替 f₁,₂ 並求解 sm , 我們可以將其改寫為:
將 Φ 設置為 1/6 ,我們會得到以下公式
下面,我們通過一個具體的例子來解釋一下。 假設初始存款人將 100 DAI 和 1 ETH 存入一個配對中,獲得 10 股。 一段時間后(沒有其他存款人參與該配對),他們嘗試提取資金,此時該配對中有 96 DAI 和 1.5 ETH。 將這些值代入上述公式,我們得到如下結果:
2.6 交易費用計算
UniswapV1 交易費用的計算是公式是
這個公式意味著只是先減少支付數量后再執行恆定乘積公式; 在 UniswapV2 的版本中,因為 Flash Swaps 機制的存在,交易費用的計算公式調整為
2.7 sync()和 skim()
sync()用於將合約中暫存的資產數量更新為合約的當前實際值,主要用於處理那些比例失衡且沒有流動性提供者的情況。 skim()則用於處理合約內某種資產數量超過 uint112 最大值的情況,允許使用者提取超出 uint112 最大值的那部分資產。
2.8 處理非標準和不常用代市
標準的 ERC20 代幣合約在轉移代幣後需要返回一個布爾值來表示轉移是否成功,但並非所有代幣都會這麼做。 有些代幣沒有返回值。 在 UniswapV1 中,對於沒有返回值的代幣轉移預設視為失敗,整個交易會被重置。 而在 Uniswap V2 中,沒有返回值的代幣轉移被視為成功。
此外,UniswapV1 假設代幣轉移不會觸發交易對的重入,但支援 ERC777 hooks 的一些 ERC20 代幣打破了這一假設。 為了支援這些代幣,UniswapV2 在所有公共狀態變數修改函數中增加了防重入鎖功能,同時也阻止了 Flash Swaps 中使用者自定義回調的重入。
2.9 流動性初始化設置
如果使用者為一個已經存在的交易對 A/B 提供流動性,那麼根據當前 A 和 B 的比例就可以計算出來要提供多少比例的 A 和 B 。 但是在一個交易對初始化的時候,沒有可以參考的比例,拿這個時候應該怎麼處理呢? 在 UniswapV1 的版本中,當新的流動性提供者將代幣存入現有的 Uniswap 代幣對時,將根據現有代幣數量計算所鑄造的流動性代幣數量。 具體的計算公式如下:
對於第一個提供流動性的人來說,公式當中的是 Xstarting 是 0。 在面對這種情況,UniswapV1 採用的方法是初始流動性的數值直接等於初始提供的 ETH 數量,這種初始流動性的提供的問題在於交易對的價值完全由初始流動性的比例所決定的,但是問題在於,沒有任何機制去擔保這個比例就是符合真實價值的。 在 UniswapV2 當中,流動性的初始化可以用如下的公式
這個公式的意思是 Sminted 這是你將獲得的流動性代幣的數量, Xdeposited 這是你存入的第一種代幣的數量. 例如,如果你存入的是 ETH,那 Xdeposited 就是你存入的 ETH 數量。 Ydeposited 這是你存入的第二種代幣的數量。
例如,如果你存入的是 DAI,那 Ydeposited 就是你存入的 DAI 數量。 這個公式可以保證流動資金池中的份額永遠不會低於該池中的幾何平均值,但是這個公式的值也會隨著池子當中代幣數量的變化而變化,為了減弱資金池中代幣數量變化帶來的影響,UniswapV2 銷毀了最初的 1e- ¹⁵ 流動性,這個數值是最小流動性 1e-¹⁸ 的 1000 倍。 儘管對於任何交易對來說這都微不足道,但卻顯著增加了利用這一機制獲利的攻擊者的成本。
2.10 WETH
由於交易乙太坊原生貨幣 ETH 的介面與交易 ERC20 代幣的介面不同,許多協定不直接支援 ETH,而是使用一種替代品 WETH(包裝后的 ETH 代幣)。 UniswapV1 是個例外,因為它的交易對中直接包含了 ETH,允許使用者直接使用 ETH 進行交易。 然而,Uniswap V2 設計為支援任意 ERC20 代幣之間的交易對,直接支援 ETH 會使系統複雜化且增加風險。
因此,UniswapV2 中不直接支援 ETH,使用者在使用交易對前必須先將 ETH 轉換成 WETH。 實際上,UniswapV2 內部自動將使用者提供的 ETH 轉換為 WETH,這樣簡化了使用者的操作,讓他們無需手動轉換 ETH 為 WETH。 雖然對任何交易對來說這種轉換是微不足道的,但它有效地提高了系統的安全性和操作簡便性。
2.11 確定性交易對位址
無論是 UniswapV1 還是 UniswapV2,所有交易對都是通過單一的工廠(factory)合約創建的。 在 UniswapV1 中,使用的是 create 操作碼,交易對合約的位址會受到創建順序的影響。 而在 UniswapV2 中,採用了新的操作碼 create2 ,這種方法生成的地址是確定的。 這意味著可以在鏈下提前計算出交易對的位址,而不需要查詢鏈上的狀態。
2.12 最大代幣數量
為了高效地實現預言機功能,Uniswap V2 採用 uint112 來保存代幣數量,這意味著其最大支援的代幣數量為 2¹¹² - 1 。 對於精度為 18 的代幣來說,這個數值是足夠的,大約為 5192296858534828(5.19e¹⁵)枚,即 5.19 千萬億枚。 如果合約中的記錄值超過了這個限制,交易將會失敗並重置。 正如之前提到的,任何人都可以使用 skim()函數來恢復,通過移除流動性池中多餘的資產來解決這一問題。
3.UniswapV2 原理解析
Uniswap 是一種自動化流動性協定,由恆定的產品公式提供支援,並在乙太坊區塊鏈上的不可升級智慧合約系統中實現。 它消除了對可信中介機構的需求,優先考慮去中心化、抗審查和安全性。 每個 Uniswap 智慧合約或貨幣對都管理著一個由兩個 ERC-20 代幣儲備組成的流動性池。 任何人都可以通過存入每個標的代幣的等值值來換取池代幣,從而成為資金池的流動性提供者(LP)。 這些代幣跟蹤總儲備中按比例分配的 LP 份額,並且可以隨時贖回標的資產。
首先先介紹 Uniswap 的自動做市商機制,下面展示了 UniswapV2 自動做市商(AMM)模型的函數圖像,是基於如下公式的:
其中,x 代表 Token A 的數量,y 代表 Token B 的數量,k 是一個常數,表示池中兩種代幣數量的乘積保持不變。 交易者在 Uniswap 上進行交易時,通過向池中添加或移除代幣,改變了池中代幣的數量。 根據恆定乘積公式,另一種代幣的數量會相應變化,以保持乘積 k 不變。 這種變化決定了交易的價格。
例如,如果交易者希望用 Token A 交換 Token B,他們需要向池中增加一定數量的 Token A,這會導致池中的 Token B 數量減少,從而改變價格。 交易者的操作會沿著這條曲線移動,改變代幣的數量和價格。 曲線上任意一點都滿足恆定乘積關係。
UniswapV2 的工作原理大概可以分成三個部分,流動性提供者(Liquidity Provider),Uniswap 池(Uniswap Pool),交易者(Trader)。 流動性提供者的作用是流動性提供者將兩種代幣(例如,Token A 和 Token B)存入 Uniswap 池中。
圖中顯示的例子中,流動性提供者存入了 10 個 Token A 和 1 個 Token B; Uniswap 池中儲備了各種代幣,例如圖中顯示的 100 個 Token A 和 10 個 Token B。
池中的流動性份額由流動性代幣表示,圖中顯示共有 12 個流動性代幣; 交易者交易者可以向池中提交代幣並交換他們需要的另一種代幣。 例如,交易者可以存入 10 個 Token A,並支付 0.3% 的手續費,從池中獲取 1 個 Token B。
我們先看流動性提供者(LP)是如何提供流通性的。 如下圖所示,流動性提供者將代幣存入 Uniswap 池中,增加流動性。
例如,在圖中,流動性提供者存入了 3 個 Token A 和 1 個 Token B 。 當流動性提供者存入代幣后,他們會收到代表其流動性份額的池代幣(Pool Tokens)。 在圖中,流動性提供者獲得了 12.4 個池代幣。 池中的代幣儲備會增加,例如,圖中池中的代幣儲備變為 1210 個 Token A 和 399 個 Token B 。 更多的流動性有助於降低價格滑點,使交易更加穩定。
Uniswap 使用的恆定乘積公式 x · y = k 來確定價格曲線。 增加的流動性擴展了低滑點區域,提升了交易的價格穩定性。 流動性提供者通過存入代幣來增加池中的流動性,並獲得相應的流動性代幣作為回報。 這不僅説明了交易者獲得更穩定的價格,也為流動性提供者帶來了交易手續費的收益。
接下來,我們從交易者的視角去看,交易者是如何換取代幣以及交易行為會對 Uniswap Pool 產生什麼影響。
如下圖所示,交易者希望在 Uniswap 上交換代幣。 例如,在圖中,交易者打算交換 3 個 Token A 。 交易者輸入 3 個 Token A ,並支付 0.3% 的手續費。 最終,交易者將獲得約 0.997 個 Token B 作為輸出。 交易會改變池中的儲備平衡,從而導致新的價格。 在交易前,池中有 1200 個 Token A 和 400 個 Token B 。
根據恒定乘積公式 x·y = k 交易后的池中將有大約 1203.009 個 Token A 和大約 399.003 個 Token B 。 Uniswap 使用 x·y = k 的恆定乘積公式來定義價格曲線。 隨著交易者的交換操作,池中的代幣數量變化,價格曲線也隨之調整,確定新的價格。
4. 源碼分析
4.1 核心操作流程圖解析
在這一節,我們會介紹在 UniswapV2 當中最常用的三個操作,即添加流動性,撤除流動性,交換代幣。 我們會通過流程圖來分析他們調用的合約以及調用的函數,來更加深刻的理解 UniswapV2 的源碼。
4.1.1 添加流動性
使用者在添加流動性的時候,使用者首先調用 UniswapV2Router.sol 合約,提供 Token A 和 Token B 的數量,UniswapV2Router.sol 合約的 addLiquidity 函數接收使用者的請求並進行處理。
addLiquidity 函數進一步調用 UniswapV2Pair.sol 合約,在 UniswapV2Pair.sol 合約中,調用 mint 函數執行實際的流動性添加操作,mint 函數根據使用者提供的 Token A 和 Token B 的數量,計算應鑄造的流動性代幣(LP 代幣)的數量,並將這些 LP 代幣分配給使用者,流動性添加操作完成後,mint 函數調用 _ update 函數更新儲備量。
4.1.2 交换代市
使用者想要交換代幣的時候,首先調用 UniswapV2Router.sol 合約,提供輸入代幣數量和最小輸出代幣數量。
然後 UniswapV2Router 合約的 swapExactTokensForTokens 函數接收使用者的請求並進行處理,swapExactTokensForTokens 函數進一步調用 UniswapV2Pair.sol 合約,在 UniswapV2Pair.sol 合約中,調用 swap 函數執行實際的代幣交換, swap 函數根據輸入代幣數量和儲備量,計算應輸出的代幣數量,並執行交換,交換完成後,swap 函數調用 _update 函數更新儲備量和累計手續費。 具體的流程如下所示:
4.1.3 撤除流動性
使用者首先調用 UniswapV2Router.sol 合約,提供要撤出 LP 代幣的數量,UniswapV2Router.sol 合約的 removeLiquidity 函數接收使用者的請求並進行處理,removeLiquidity 函數進一步調用 UniswapV2Pair.sol 合約,在 UniswapV2Pair.sol 合約中,調用 burn 函數執行實際的流動性撤出操作,burn 函數根據提供的 LP 代幣數量,計算應返還的 Token A 和 Token B 數量,並將這些代幣返還給使用者。
4.2 core 合約
UniswapV2 Core 合約是去中心化交易平臺 Uniswap 的核心部分,負責實現其自動化做市商(AMM)功能。 與傳統訂單簿不同,Uniswap 通過流動性池和恆定乘積公式 x·y = k 來實現交易。 流動性提供者將兩種代幣存入池中,獲得流動性代幣作為憑證。 用戶在進行交易時,合約根據池中代幣數量和恆定乘積公式計算交易價格。 UniswapV2 引入了多項改進,包括 ERC20 pairs 直接交易,價格預言機的改進, 閃電貸以及協定費的調整。 core 合約當中的核心元件包括下面三個檔:
- UniswapV2Pair.sol :管理每個交易對的流動性池,處理代幣交換、流動性添加和移除
- UniswapV2Factory.sol :負責創建和管理交易對
- UniswapV2ERC20.sol :流動性代幣的標準實現,代表流動性提供者的份額
4.2.1 UniswapV2Factory.sol
UniswapV2Factory 合約的作用是負責創建和管理交易對(流動性池)。 該合約允許用戶創建新的交易對,並記錄所有創建的交易對。 此外,它還管理交易費接收地址和設置者位址。 UniswapV2Factory.sol 有五個函數,分別來看一下
- constructor 函數:構造函數,用於初始化 UniswapV2Factory 合約。 輸入是交易費設定者地址 _feeToSetter , 輸出是無。
- allPairsLength 函數:返回所有創建的交易對的數量。 輸入是無, 輸出是所有交易對數量的 unit。
- createPair 函數: 創建新的交易對。 輸入是 tokenA 和 tokenB 的兩個代幣地址,輸出是創建的交易對位址 pair。
- setFeeTo 函數: 設置交易費接受位址。 輸入是新的交易費接受位址 _feeTo, 輸出是無。
- setFeeToSetter 函數: 設置新的交易費設置者位址。 輸入是新的交易費設定者地址 _feeToSetter, 輸出是無。
具體的代碼解析如下:
createPair 函數
createPair 函數的作用是創建一個以 TokenA 和 TokenB 的交易對,在前端輸入 TokenA 和 TokenB 之後,會先檢查 TokenA 和 TokenB 是否是同一個幣種,之後會對 TokenA 和 TokenB 做一個簡單的排序,之後是檢查 Token0 的位址,要求 Token0 的位址不能是 0;
之後是通過 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); 來檢查這個代幣對是否存在,只有存在了,才可以進行下去; 後面通過 creationCode 來獲取 UniswapV2Pair 合約的創建位元組碼;
之後使用 token0 和 token1 的哈希值作為鹽值,確保每個代幣對的位址是唯一的,因為如果代幣對的位址不唯一,那麼交易者添加流動性可能會添加到錯誤的池子當中; 之後使用內聯彙編的 create2 指令創建合約,保證合約位址的唯一性和可預測性;
之後便是初始化新創建的代幣對合約,然後更新映射表,記錄這個代幣對合約的位址,然後將新創建的代幣對合約位址添加到所有代幣對的清單中,最後是觸發 PairCreated 事件,通知外部有新的代幣對創建
4.2.2 UniswapV2ERC20.sO
UniswapV2ERC20.sol 的主要功能是实现 ERC-20 代币,它实现了 ERC20 标准的代币功能,专门用于 UniswapV2 流动性池。合约包含铸造(mint)、销毁(burn)、批准(approve)和转移(transfer)等基本操作。此外,它还支持 permit 功能,允许使用签名来批准代币转移。我们来逐个看他包含的函数:
- constructor 函数:初始化合约,设置 DOMAIN_SEPARATOR 用于 permit 功能。输入无,输出无。
- _mint 函数:铸造新的代币,输入是接收地址 “ to ”,和铸造数量 “ value ”,输出无
- _burn 函数: 销毁代币,输入是销毁地址 from 和销毁数量 value,输出无。
- _approve 函数: 批准代币转移,所有者地址 owner,批准地址 spender 和批准数量 value,输出无。
- _transfer 函数:转移代币, 输入是转出地址 from,接收地址 to 和转移数量 value, 输出无。
- approve 函数:公开的批准函数,作用是调用 _approve 函数,输入是 approve 函数公开的批准函数, 输出是返回布尔值 true 表示操作成功。
- transfer 函数:作用是调用 _transfer 函数,输入是接受地址 to 和转移数量 value, 输出是返回布尔值 true 表示操作成功
- transferFrom 函数:公开的授权转移函数。输入是转出地址 from,接收地址 to 和 转移数量 value,输出是 返回布尔值 true 表示操作成功
- permit 函数: 使用签名来批准代币转移,验证签名并调用 _approve 函数,输入是所有者地址 owner,批准地址 spender,批准数量 value,截止时间 deadline,签名参数 v、r、s,输出无。
官方的源码解析如下所示:
4.2.3 UniswapV2Pair.sol
UniswapV2Pair 即交易对合约,实现了 Uniswap v2 的核心功能,即管理和操作每个交易对的流动性池。该合约负责处理代币的交换、流动性的添加和移除,以及价格的累积计算。它确保在每次交易后,交易对的储备和价格信息得到更新,并触发相应的事件通知。UniswapV2Pair.sol 中有 11 个函数,具体入下面表格所示:
官方的 UniswapV2Pair.sol 的代码和注释如下:
UniswapV2Pair 是继承 IUniswapV2Pair, UniswapV2ERC20,首先看看 IUniswapV2Pair 的源码,看看 IUniswapV2Pair 如何定义接口:
之后定义了全局变量和修饰器
上面的 MINIMUM_LIQUIDITY 是一个常量,它设定了流动性池中必须保留的最小流动性代币数量,以确保流动性提供者在任何时候都至少保留一定量的代币,从而避免流动性枯竭,数值是 10 的 3 次方,在提供初始流动性时会被燃烧掉; SELECTOR 存储的是代币转账函数的 ABI(应用程序二进制接口)选择器,它用于在智能合约中准确地识别和调用其他合约的 transfer 函数,确保在执行代币转移时使用正确的函数签名;
factory 用于存储交易对合约的 Uniswap V2 工厂合约的地址,Token0,Token1 用于存储代币地址,reserve0, reserve1 和 blockTimestampLast 这三个状态变量记录了最新的恒定乘积中两种资产的数量和交易时的区块 (创建) 时间;
而 price0CumulativeLast 和 price1CumulativeLast 变量用于记录交易对中两种价格的累计值,kLast 用于跟踪 UniswapV2 交易对中两种代币储备量乘积的最近状态,作为一个关键参数来维持流动性池的价格稳定性和计算交易费用,主要用于团队手续费的计算。
下面这一段修饰器是提供了一种锁机制来防止重入攻击,具体代码解析如下:
上面的代码当中的 _; 表示被修饰的函数体,这段代码的大致逻辑是:定义了一个 lock 修饰符,它通过改变 unlocked 变量的状态来确保在执行被修饰的函数期间合约不会被重新进入,从而防止重入攻击和竞态条件。
下面的 getReserves 函数的作用是提供一种方式来公开查询并返回 UniswapV2 交易对合约当前的两种代币储备量和最后更新时间戳的信息。
_safeTransfer 函数的作用是在智能合约内部执行代币的转移操作,并检查转移是否成功,如果失败则抛出异常,确保了合约的安全性和代币转移的可靠性,下面是这一段代码的详细注释:
下面的构造函数只是简单的用于初始化 factory:
initialize 函數的作用是設置交易對合約所涉及的兩種代幣的位址,且只能由部署交易對的工廠合約(factory)來調用,確保交易對的初始化過程是安全和受控的。
_update 函數的主要作用是確保交易對合約的儲備量和價格累積器能夠反映最新的狀態, 具體實現的方法是通過比較當前區塊的時間戳和上次更新的時間戳。 _update 函數的四個輸入參數分別是:balance0 和 balance1 ,表示交易對中兩種代幣當前的餘額; _reserve0 和 _reserve1 ,表示函數調用前兩種代幣的儲備量。 我們接下來用 bullet point 的方式來講解一下 _update 函數是如何實現的:
- 檢查餘額值是否可能導致溢出:通過 require 語句確保傳入的 balance0 和 balance1 不會超過 uint112 的最大值,這是因為 reserve0 和 reserve1 在儲存時使用 uint112 類型,需要保證數據類型轉換的安全性。
- 記錄當前區塊時間:獲取當前區塊的時間戳,並將其與 blockTimestampLast 進行模 2^32 運算,得到 blockTimestamp 。 這個操作是因為乙太坊的區塊時間戳是 32 位的,而且我們只關心在一個區塊內的時間差,而不是絕對時間。
- 計算時間差:計算當前區塊時間與上次更新時間的差值 timeElapsed 。 如果 timeElapsed 為 0,表示這是同一區塊內的連續調用,因此不會更新價格累積值。
- 價格累積更新:如果時間差大於 0,並且儲備量不為 0,使用固定點數學庫 UQ112x112 來計算價格比例,並更新 price0CumulativeLast 和 price1CumulativeLast 。 這裡的「never overflows」意味著由於時間間隔 timeElapsed 是 uint32 類型,與價格累積值(uint224)相乘不會導致溢出。 “+ overflow is desired” 指的是價格累積值允許溢出,因為價格計算使用的是變化量(delta)而不是絕對值,即使有溢出,計算平均價格時使用的變化量仍然是準確的。
- 更新儲備量:將新的餘額賦值給 reserve0 和 reserve1 ,更新流動性池的儲備量。
- 更新時間戳:將當前區塊時間戳賦值給 blockTimestampLast ,為下一次更新做準備。
- 觸發同步事件:通過 emit 關鍵字發出 Sync 事件,告知外部監聽者儲備量已經更新。
這種設計允許 UniswapV2 在處理大量交易時保持價格的連續性和準確性,即使在區塊時間戳或價格累積值可能溢出的情況下,依然能夠通過變化量來準確計算出平均交易價格。 這是通過巧妙地利用固定點數學和時間差分來實現的。
在 UniswapV2 中,使用者每筆交易會被收取 0.3% 的手續費。 這筆手續費中的六分之一將分配給開發團隊,而剩下的六分之五將作為獎勵給予流動性提供者。 然而,如果每次交易都計算一次手續費,這將不可避免地增加使用者的 Gas 費用。
因此,在 UniswapV2 中,手續費會被累積起來,只有在流動性發生變化時才會對手續費進行分配。 _mintFee 函數首先檢查是否開啟了交易費用,並確定費用接收位址。 如果交易費用未開啟,且之前有鑄造過費用(_kLast 不為 0),則重置 kLast 值。 這種費用鑄造機制是 UniswapV2 的一部分,用於為流動性提供者提供額外的激勵; 如果交易費用開啟,則根據下面的公式來計算手續費的值,
Sm 表示應該鑄造的手續費流動性代幣數量,k₁表示上一個流動性事件后的儲備的乘積 k,k₂ 表示當前的儲備乘積 k,S₁表示上一個流動性事件后的總流動性代幣供應量。
4.3 Periphery 合约
UniswapV2 周边合约的主要作用是作为外部账户与核心合约之间的桥梁,包含接口定义、工具类库、Router 和示例实现四部分内容。
4.3.1 lipraries
Libraries 文件夹下面包含了四个文件
- SafeMath.sol
- UniswapV2Library.sol
- UniswapV2LiquidityMathLibrary.sol
- UniswapV2OracleLibrary.sol
我们接下来分别对这四个 sol 文件进行详细的解析
SafeMath.sol
SafeMath.sol 用于执行溢出安全的数学运算,这种安全数学运算对于避免整数溢出和下溢错误非常重要,特别是在区块链和智能合约开发中。主要是包含三个函数。
- add 函数:用于安全地执行两个无符号整数的加法运算
- sub 函数:用于安全地执行两个无符号整数的减法运算
- mul 函数:用于安全地执行两个无符号整数的乘法运算
具体的代码注释如下
UniswapV2Library.sol
UniswapV2Library.sol 提供了一些實用函數,用於與 Uniswap v2 交換對(pairs)進行交互和操作。
這些函數主要用於計算交易路徑、獲取儲備、計算價格和執行鏈式計算。 該庫使用了一個名為 SafeMath 的庫來確保數學運算的安全性,避免整數溢出和下溢。 UniswapV2Library.sol 這個文件包含了八個函數:
- sortTokens 函數:返回按位址排序的兩個代幣位址。 輸入是兩個代幣位址 tokenA 和 tokenB。 輸出是排序後的代幣位址 token0 和 token1。
- pairFor 函數:計算出給定工廠位址和兩個代幣位址的對的位址,而無需進行外部調用。 輸入是工廠位址 factory, 兩個代幣位址 tokenA 和 tokenB; 輸出是該對的位址 pair
- getReserves 函數:獲取並排序一個對的儲備。 輸入是工廠位址 factory,兩個代幣位址 tokenA 和 tokenB; 輸出是兩個代幣的儲備量 reserveA 和 reserve B
- quote 函數:根據給定資產數量和對的儲備,返回另一個資產的等價數量。 輸入:資產數量 amountA ,儲備 reserveA 和 reserveB 。 輸出是另外一個資產的數量 amountB。
- getAmountOut 函數: 根據輸入資產數量和對的儲備,返回另一個資產的最大輸出數量。 輸入是輸入資產數量 amountIn,儲備 reserveIn 和 reserveOut; 輸出是 輸出資產的數量 amountOut。
- getAmountIn 函數:根據輸出資產數量和對的儲備,返回所需輸入的另一個資產的數量。 輸入是輸出資產數量 amountOut,儲備 reserveIn 和 reserveOut。
- getAmountsOut 函數:在任意數量的對上執行鏈式 getAmountOut 計算,輸入是工廠位址 factory ,輸入資產數量 amountIn ,路徑 path; 輸出是每個路徑節點的資產數量陣列 amounts。
- getAmountsIn 函數:在任意數量的對上執行鏈式 getAmountIn 計算,輸入是工廠位址 factory,輸出資產數量 amountOut,路徑 path ; 輸出是每個路徑節點的資產數量陣列 amounts
UniswapV2Libray 源碼詳細註釋如下:
UniswapV2OracleLibrary.sol
UniswapV2OracleLibrary.sol 文件,提供了一些辅助方法,用于与预言机计算平均价格相关的操作。该库包括获取当前区块时间戳和计算累积价格的方法,帮助节省 gas 费用和避免频繁的同步调用。包含了两个函数,具体如下:
- currentBlockTimestamp 函数:返回当前区块时间戳。 输入无,输出是当前区块时间戳,类型为 uint32 。
- currentCumulativePrices 函数:计算并返回累积价格。输入是交易对地址 pair,输出是累积价格 price0Cumulative 和 price1Cumulative 以及当前区块时间戳 blockTimestamp。
UniswapV2OracleLibrary.sol 官方的源码详细注释如下:
UniswapV2LiquidityMathLibrary.sol
UniswapV2LiquidityMathLibrary.sol 的官方源码如下
- computeProfitMaximizingTrade 函数:计算实现利润最大化的交易方向和大小。
- getReservesAfterArbitrage 函数:在观察到的 “真实价格” 下,通过套利交易后获取流动性池的储备量。
- computeLiquidityValue 函数:计算流动性价值,给定流动性池的所有参数。
- getLiquidityValue 函数:获取当前所有参数,并计算流动性金额的价值。
- getLiquidityValueAfterArbitrageToPrice 函数:给定两种代币和它们的 “真实价格”,以及流动性金额,返回流动性的价值。
UniswapV2Router02.sol
我们先看看 UniswapV2Router02 的源码,这一部分的代码可以大致分成六个大的部分,构造函数和修饰符, 接受 ETH 的函数, 添加流动性,移除流动性, 代币交换, 库函数。
构造函数和修饰符
- constructor(address _factory, address _WETH): 初始化工厂合约地址和 WETH 合约地址。
- modifier ensure(uint deadline): 确保交易在截止时间之前完成。
接收 ETH 的函数
- receive() external payable: 接收 ETH,只允许从 WETH 合约调用。
添加流动性
- _addLiquidity: 内部函数,用于添加流动性,根据现有储备量计算最优的代币数量。
- addLiquidity: 添加两种 ERC-20 代币的流动性。
- addLiquidityETH: 添加 ERC-20 代币和 ETH 的流动性。
移除流动性
- removeLiquidity: 移除两种 ERC-20 代币的流动性。
- removeLiquidityETH: 移除 ERC-20 代币和 ETH 的流动性。
- removeLiquidityWithPermit: 带许可的移除两种 ERC-20 代币的流动性。
- removeLiquidityETHWithPermit: 带许可的移除 ERC-20 代币和 ETH 的流动性。
- removeLiquidityETHSupportingFeeOnTransferTokens: 移除 ERC-20 代币和 ETH 的流动性,支持手续费代币。
- removeLiquidityETHWithPermitSupportingFeeOnTransferTokens: 带许可的移除 ERC-20 代币和 ETH 的流动性,支持手续费代币。
代币交换
- _swap: 内部函数,执行代币交换逻辑。
- swapExactTokensForTokens: 使用确切数量的代币交换另一种代币。
- swapTokensForExactTokens: 使用代币交换确切数量的另一种代币。
- swapExactETHForTokens: 使用确切数量的 ETH 交换代币。
- swapTokensForExactETH: 使用代币交换确切数量的 ETH。
- swapExactTokensForETH: 使用确切数量的代币交换 ETH。
- swapETHForExactTokens: 使用 ETH 交换确切数量的代币。
- swapExactTokensForTokensSupportingFeeOnTransferTokens: 使用确切数量的代币交换另一种代币,支持手续费代币。
- swapExactETHForTokensSupportingFeeOnTransferTokens: 使用确切数量的 ETH 交换代币,支持手续费代币。
- swapExactTokensForETHSupportingFeeOnTransferTokens: 使用确切数量的代币交换 ETH,支持手续费代币。
库函数
- quote: 根据储备量计算给定数量代币 A 对应的代币 B 数量。
- getAmountOut: 计算给定输入数量和储备量的情况下可以获得的输出数量。
- getAmountIn: 计算给定输出数量和储备量的情况下需要的输入数量。
- getAmountsOut: 根据路径和输入数量计算输出数量。
- getAmountsIn: 根据路径和输出数量计算输入数量。
具体我们下面逐个函数的去看,UniswapV2Router02.sol 的合约通过下面这一段代码继承自 IUniswapV2Router02,
UniswapV2Router02 合约实现了 IUniswapV2Router02 接口,提供了以下关键功能:
- 添加流动性:允许用户向流动性池中添加两种代币,以换取流动性提供者代币。
- 移除流动性:允许流动性提供者将他们的流动性提供者代币兑换回两种代币。
- 交易:允许用户在不同代币之间进行交易,支持直接交易和通过多个交易对的路径交易。
- 报价计算:提供了一系列函数,用于计算给定输入或输出量时的交易细节。
ensure 修饰器用于检查当前区块时间是否超过最迟交易时间,确保用户指定的交易不会因超时而失败,有助于提高交易的安全性和可靠性。
构造函数初始化了工程合约地址和 weth 合约地址,这两个地址在整个合约的生命周期中都是不变的。
receive 函数通常用于允许合约直接接收以太币,而不是通过函数调用。在这个特定的例子中,receive 函数通过 assert 语句确保只有 WETH 合约可以向其发送以太币
_addLiquidity 函数的目的是计算在添加流动性时用户需要存入的两种代币的最优数量。这个函数的接受有六个参数,tokenA 和 tokenB 是要添加流动性的两种代币的地址,amountADesired 和 amountBDesired 是用户希望添加的两种代币的初始数量,amountAMin 和 amountBMin 是用户能接受的最小添加数量,用于防止滑点过低,函数返回两个参数:amountA 和 amountB,即实际添加的两种代币的数量。
这个计算考虑了流动性池中已有的代币储备量,以确保用户添加的流动性是平衡的。如果流动性池是新的,没有现有的储备,用户可以直接添加他们希望的数量。如果池中已经有储备,函数将计算基于当前比例的最优添加数量,确保流动性添加后池中的代币比例保持不变。
addLiquidity 的公共函数,用于向指定的两种代币 ( tokenA 和 tokenB ) 的流动性池添加流动性
addLiquidityETH 函数允许用户向 UniswapV2 流动性池添加非以太币代币和以太币,以换取相应的流动性代币,这个函数有六个接受的参数,token 是要添加流动性的非以太币代币的地址,amountTokenDesired 是用户想要添加的非以太币代币的数量,amountTokenMin 是用户能接受的最小非以太币添加数量,用于防止滑点过低,amountETHMin 是以太币的最小添加数量,同样用于防止滑点过低,to 是接收新铸造的流动性代币的地址,deadline 是交易的截止时间,用于防止交易超时。
函数返回三个参数:amountToken 和 amountETH 是实际添加到流动性池的非以太币和以太币的数量,liquidity 是新铸造的流动性代币的数量。
removeLiquidity 函数允许用户从流动性池中移除他们之前添加的流动性,并按照比例获得两种代币。burn 函数是 IUniswapV2Pair 流动性池合约中的一个底层函数,用于执行实际的流动性销毁和代币分配操作。
removeLiquidityETH 函数允许用户从 UniswapV2 流动性池中移除他们之前与以太币一起添加的流动性,并分别获得相应的非以太币和以太币。函数首先调用 removeLiquidity ,之后通过 safeTransfer 提取对应的 token ,然后将燃烧流动性提取的 WETH 换成 ETH ,之后将兑换的 ETH 转给接受者。
removeLiquidityWithPermit 函数使用户能够通过 ECDSA 签名进行授权,从而允许合约代表用户移除流动性,而无需用户先前通过 approve 函数进行授权。
removeLiquidityETHWithPermit 函数结合了 removeLiquidityETH 和 permit 授权机制,允许用户通过签名一次性授权合约移除流动性,而不需要使用标准的 approve 模式。这在提供更好的用户体验和安全性的同时,也减少了交易的 gas 成本。
removeLiquidityETHSupportingFeeOnTransferTokens 函數允許使用者從 UniswapV2 流動性池中移除他們之前添加的與特定代幣和乙太幣的流動性,同時考慮到某些代幣在轉帳時可能會收取費用。
函數首先調用 removeLiquidity 來執行流動性的移除,然後處理代幣的轉移,確保使用者獲得他們應得的代幣數量,最後將 WETH 轉換回乙太幣並轉移給使用者。 整個過程需要在使用者指定的交易截止時間之前完成。
removeLiquidityETHWithPermitSupportingFeeOnTransferTokens 函数结合了 removeLiquidityETHSupportingFeeOnTransferTokens 和 permit 授权机制,允许用户通过签名一次性授权合约移除流动性,同时处理可能在转账时收取费用的代币。
这种方法提供了一种无需使用 approve 模式即可授权合约操作用户资产的方式,从而减少了 gas 成本并提高了用户体验。
_swap 函数是一个内部函数,用于执行一系列代币交换操作。它按照指定的路径和数量,从一个代币交换到另一个代币,直到达到最终的代币。这个函数是流动性池交互的核心部分,用于实现代币的转换和流动性池的更新。
swapExactTokensForTokens 函數是 UniswapV2 Router 的一個關鍵功能,允許使用者以一個確切的輸入量來交換至少一個最小輸出量的代幣。 這個函數首先計算出整個交換路徑的輸出量,然後確保最終的輸出量滿足使用者的最小要求,接著安全地從使用者轉移代幣到流動性池,並執行交換操作。
swapTokensForExactTokens 函數允許使用者指定他們希望獲得的代幣數量,並提供不超過最大值的代幣以進行交換。 這個函數首先計算出為了獲得 amountOut 所需的最大輸入量,然後確保這個輸入量不超過使用者指定的 amountInMax 。
swapExactETHForTokens 函数允许用户以确切数量的 ETH 交换至少一定数量的另一种代币。这个函数首先验证交换路径是否有效,然后计算所需的 WETH 数量,将 ETH 存入 WETH 合约,然后执行交换操作,并将交换得到的代币发送到用户指定的地址。整个过程需要在用户指定的交易截止时间之前完成。
swapExactETHForTokens 函數的作用是用於賣出制定數量的 ETH 換取其他 Token 。 首先進行路徑有效檢查,確保 path 數位的第一個元素是 WETH 位址,因為 Uniswap 交易對為 ERC20/ERC20/ERC20 交易對;
接下來是計算輸出金額,使用 UniswapV2Library.getAmountsOut 函數,根據用戶發送的 ETH 數量 msg.value 和代幣路徑 path ,計算出用戶能夠獲得的每種代幣的數量,並將結果存儲在 amounts 陣列中; 接下來是做最小輸出驗證,函數檢查 amounts 陣列中最後一個元素(即目標代幣的數量)是否滿足使用者設定的最小輸出數量 amountOutMin 。
如果不滿足,將拋出一個錯誤; 接下來是調用 WETH 代幣合約的 deposit 函數,將 msg.value 的 ETH 存入 WETH 合約,斷言 WETH 合約向指定的交易對合約地址轉移 amounts[0] 數量的 WETH,如果失敗則交易回滾,最後是調用內部函數 _swap ,執行實際的代幣交換過程。
swapTokensForExactETH 是賣出其他 Token 換取一定數量 ETH 的過程,函數接收五個參數,包括期望獲得的 ETH 數量 amountOut ,用戶願意提供的最大代幣數量 amountInMax ,代幣交換路徑 path ,接收 ETH 的位址 to ,以及交易的截止時間 deadline;
首先是路徑有效性檢查,確保交換路徑的最後一個元素是 WETH 位址,如果不是則拋出異常; 接下來是輸入金額計算, 使用 UniswapV2Library.getAmountsIn 函數計算為了獲得 amountOut 數量的 ETH ,使用者需要提供的代幣數量; 確保計算出的需要提供的代幣數量不超過用戶設定的最大數量,如果不是則拋出異常; 使用 TransferHelper 的 safeTransferFrom 函數,從 msg.sender 位址安全轉移代幣到流動性池;
接下來調用內部函數 _swap ,執行實際的代幣交換過程; 調用 WETH 合約的 withdraw 函數,將交換得到的 WETH 轉換回 ETH; 最後使用 TransferHelper 的 safeTransferETH 函數,將獲得的 ETH 安全轉移到使用者指定的位址。
swapExactTokensForETH 函數的作用是用於以固定數量的代幣交換至少 amountOutMin 數量的 ETH,函數接收五個參數,包括用戶願意提供的代幣數量 amountIn ,期望獲得的最小 ETH 數量 amountOutMin ,代幣交換路徑 path ,接收 ETH 的位址 to ,以及交易的截止時間 deadline; 函數的實現邏輯和 swapTokensForExactETH 的邏輯很像。
swapETHForExactTokens 的函數,它允許使用者使用 ETH 交換固定數量的代幣,實現邏輯和上面的 swapExactTokensForETH,swapTokensForExactETH 類似。
_swapSupportingFeeOnTransferTokens 函數支援交易費用的代幣交換的邏輯,通過遍歷代幣路徑,計算每個流動性池中需要交換的代幣數量,並執行交換操作。
swapExactTokensForTokensSupportingFeeOnTransferTokens 固定数量的代币精确兑换至少期望数量的另一种代币,同时处理了交易费用代币的情况。
swapExactETHForTokensSupportingFeeOnTransferTokens 实现了用特定数量的 ETH 去换取其他 Token
swapExactTokensForETHSupportingFeeOnTransferTokens 函數實現了用戶能夠以固定數量的代幣精確兌換至少期望數量的 ETH,同時處理了交易費用代幣的情況,並確保了 ETH 的準確轉移。
quote 函數實現了根據使用者指定的代幣數量和流動性池中兩種代幣的儲備量,計算並返回用戶能夠交換得到的另一種代幣數量的功能。
getAmountOut 函數和 getAmountsOut 函數的計算原理都是恆定乘積演算法。 定義函數 getAmountOut ,用於計算給定輸入金額和儲備量后,使用者可以得到的輸出金額; 定義函數 getAmountsOut ,用於計算給定輸入金額和交換路徑后,使用者可以得到的一系列輸出金額。
getAmountIn 和 getAmountsIn 的計算原理是根據恆定乘積演算法,實現了制定買進資產數量的情況下,計算賣出資產的數量。
4.3.2 UniswapV2Migrator.sol
UniswapV2Migrator.sol 用於將流動性從 Uniswap v1 遷移到 Uniswap v2。 該合約包括接收 ETH 的能力,並使用 Uniswap v1 和 v2 的路由器和交換合約進行遷移操作。 它確保使用者可以安全地將他們在 Uniswap v1 中的流動性轉移到 Uniswap v2 中。 具體有下面三個函數,每個函數的具體功能如下:
- constructor 函數:構造函數,用於初始化 UniswapV2Migrator 合約。 輸入是 v1 工廠位址 _factoryV1 和 v2 路由器位址 _router; 輸出無
- receive 函數:接收 ETH,允許合約接收來自任何 v1 交換和路由器的 ETH。 輸入無,輸出無。
- migrate 函數:輸入是代幣位址 token、最小代幣數量 amountTokenMin、最小 ETH 數量 amountETHMin、接收位址 to、截止時間 deadline; 輸出是無
UniswapV2Migrator 的官方源碼詳細解析如下:
4.3.2 UniswapV2Migrator.sol
interfaces 資料夾包含了用於與 UniswapV1 和 V2 交換、路由器、工廠、以及 WETH 和 ERC20 合約進行互動的介面定義。 主要功能包括管理和遷移流動性、處理代幣交易、添加和移除流動性、以及包裝和解包 ETH。 這些介面確保合約間的標準化交互。 我們具體來看每個文件的內容。
IUniswapV1Exchange.sol
這個文件定義了一個介面 IUniswapV1Exchange,用於與 UniswapV1 交換合約進行互動。 交換合約主要負責處理代幣與 ETH 之間的交易和流動性管理。 包含如下四個函數:
- balanceOf 函數:返回某地址在交換合約中的餘額。
- transferFrom 函數:從一個位址轉移代幣到另一個位址。
- tokenToEthSwapInput 函數:用於將代幣交換為乙太幣。
- ethToTokenSwapInput 函數:將乙太幣交換為代幣。
IUniswapV1Factory.sol
IUniswapV1Factory.sol 它用於與 UniswapV1 的工廠合約進行交互。 具體的代碼解析如下:
IERC20.sol
IERC20.sol 定義了一個名為 IERC20 的介面,它遵循了乙太坊代幣標準(ERC-20)
IUniswapV2migrator.sol
IUniswapV2migrator.sol 用于与 Uniswap V2 的迁移合约进行交互,详细的代码注释如下:
IUniswapV2Router01.sol
IUniswapV2Router01 接口提供了多种功能,包括流动性的添加和移除、代币的交换等,这些功能是构建去中心化交易平台和流动性池的核心。通过这个接口,用户可以方便地与 UniswapV2 协议进行交互。
IUniswapV2Router02.sol
IUniswapV2Router02.sol 接口中的方法主要增加了对转账费用的支持,这允许流动性提供者或代币持有者在代币转账时获得一部分费用作为回报。这些方法的实现通常会在底层调用相应的代币合约,以实现转账时的费用收取。
IWETH.sol
IWETH.sol 定義了一個名為 IWETH 的介面,它代表了乙太坊上的一個標準介面,用於與包裝乙太幣(Wrapped Ether,簡稱 WETH)合約交互。
免責聲明:作為區塊鏈資訊平臺,本站所發佈文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。 文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。