[Bunzz Decipher] UniswapV2Router02のコントラクトを理解しよう!
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回はBunzzの新機能『DeCipher』を使用して、UniswapV2の「UniswapV2Router02」のコントラクトを見てみようと思います。
『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。
詳しい使い方に関しては以下の記事を参考にしてください!
今回使用する『DeCipher』のリンクは以下になります。
概要
要約
UniswapV2Router02コントラクトは、Ethereumブロックチェーン上に構築された分散型取引所(DEX)であるUniswapプロトコルの重要なコンポーネントです。
このコントラクトは、Uniswapエコシステム内での流動性の追加と削除、トークンのスワップを容易にする役割を果たしています。
コントラクトの目的
UniswapV2Router02コントラクトの主な目的は、Uniswapプロトコルとのシームレスな対話を可能にすることです。
ユーザーが流動性プールに流動性を追加または削除し、Ethereumブロックチェーン上でトークンをスワップするための一連の機能を提供しています。
このコントラクトは、WETH(Wrapped Ether)コントラクトとも連携しており、これはERC20標準に準拠したEtherのトークン化バージョンです。
これにより、他のERC20トークンを期待するコントラクトとの互換性が高まります。
コントラクトの責任
流動性の管理
このコントラクトは、流動性を追加および削除するための機能を提供しています。
addLiquidity
およびaddLiquidityETH
関数は、ユーザーがトークンのペアを流動性プールに預け入れ、代わりに流動性トークンを受け取ることを可能にします。
これらの流動性トークンは、ユーザーのプール内でのシェアを表します。
removeLiquidity
関数は、ユーザーが自身の流動性トークンを燃やしてプール内のトークンのシェアを引き出すことを可能にします。
これは、ユーザーがポジションを解消したい場合に役立ちます。
トークンのスワップ
swap
関数は、ユーザーがEthereumブロックチェーン上でトークンを直接交換することを可能にします。
これがUniswapプロトコルの主要な機能であり、分散型取引所としての機能を提供します。
WETHとの連携
このコントラクトは、WETHコントラクトと連携してEtherの預け入れと引き出しを行うことができるようになっています。
deposit
関数を使用してユーザーはEtherをWETHに変換し、withdraw
関数を使用してWETHを再度Etherに変換することができます。
コントラクトの初期化
initialize
関数は、コントラクトが初めてデプロイされる際に設定を行うために使用されます。
この関数は、FactoryコントラクトとWETHコントラクトのアドレスを設定し、これらのアドレスはコントラクトの多くの機能で使用されます。
安全対策
このコントラクトには、WETHコントラクトから送信されたEtherのみを受け付けるreceive
関数が含まれています。
これは、コントラクトが処理できない方法でEtherが送信されるのを防ぐための安全対策です。
コントラクトには、トランザクションの締め切りが過ぎていないことを確認するensure
修飾子も含まれています。
これは、トランザクションが期限を過ぎた後にブロックに含まれるのを防ぐために使用されます。
まとめ
UniswapV2Router02コントラクトは、Uniswapプロトコルの重要な部分であり、ユーザーがプロトコルと対話するために必要な機能を提供しています。
流動性の追加と削除、トークンのスワップ、WETHコントラクトとの連携を処理し、プロトコルの安全な運用を確保するための安全対策も含まれています。
使い方
UniswapV2Router02コントラクトは、Ethereum上の自動流動性プロトコルであるUniswapプロトコルの一部です。
このコントラクトは、流動性の追加と削除、トークンのスワップ、およびトークンペアに関する情報のクエリ機能を提供します。
UniswapV2Router02コントラクトを使用する手順は以下の通りです。
1. コントラクトのデプロイ
UniswapV2Router02コントラクトをEthereumネットワーク上にデプロイします。
コンストラクタにはUniswapV2FactoryコントラクトとWETHコントラクトのアドレスが必要です。
2. 流動性の追加
addLiquidity
またはaddLiquidityETH
関数を呼び出して、ペアに流動性を追加します。
-
addLiquidity
関数は、2つのトークンのアドレス、各トークンの希望する量と最小量、流動性トークンを受け取るアドレス、および期限が必要です。 -
addLiquidityETH
関数は似ていますが、トークンの1つがEthereumの場合に使用されます。
3. 流動性の削除
removeLiquidity
関数を呼び出して、ペアから流動性を削除します。
この関数には、2つのトークンのアドレス、削除する流動性の量、各トークンを受け取る最小量、トークンを受け取るアドレス、および処理の有効期限が必要です。
4. トークンのスワップ
swap
関数を呼び出して、トークンをスワップします。
この関数には、スワップする各トークンの量、トークンを受け取るアドレス、および追加データが必要です。
5. 情報のクエリ
getReserves
関数を呼び出して、ペアのリザーブバランスを取得します。
price0CumulativeLast
およびprice1CumulativeLast
関数を呼び出して、ペア内のトークンの最後の累積価格を取得します。
kLast
関数を呼び出して、ペアの最後のリザーブバランスの積を取得します。
6. その他の関数
-
mint
関数は、流動性トークンを発行するために使用されます。 -
burn
関数は、流動性トークンを焼却するために使用されます。 -
skim
関数は、コントラクトに誤って送信されたトークンを削除するために使用されます。 -
sync
関数は、ペアのリザーブバランスを更新するために使用されます。
すべてのコントラクトの状態を変更する関数は、トランザクションがEthereumアカウントから送信される必要があります。
アカウントにはトランザクションのガス手数料を支払うための十分な残高が必要です。
パラメータ
_factory
UniswapV2Factoryコントラクトのアドレス。
新しいペアを作成したり既存のペアを取得するために使用されます。
_WETH
Wrapped Ether(WETH) コントラクトのアドレス。
EtherのERC20トークンバージョンを表し、Uniswapの取引ペアで使用されます。
定数・修飾子
定数
WETH
address public immutable override WETH;
WETHトークンのコントラクトアドレスを格納した定数。
factory
address public immutable override factory;
Factoryコントラクトのコントラクトアドレスを格納した定数。
修飾子
ensure
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
ある操作が行われる前に、与えられた期限(deadline)がまだ有効であるかどうかを確認し、期限が切れている場合には操作を拒否する役割を持つ修飾子。
- 引数として渡された「
deadline
」(期限)を確認します。 - もし現在のブロックのタイムスタンプ(時間)が「
deadline
」よりも後であれば、条件を満たしていないとみなします。 - もし条件を満たしていない場合、例外を発生させて、エラーメッセージ「UniswapV2Router: EXPIRED」を表示します。
- もし条件を満たしている場合、モディファイア内のコードブロック(「_;」)が実行されます。
その他
constructor
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
WETHとFactoryコントラクトのアドレスを引数で受け取り設定しています。
receive
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
WETHコントラクトからのみ送信されたEtherを受け入れるための仕組みを提供します。
- 外部からETHがこのコントラクトに送られてきたときに、自動的に呼び出されます。
- 「
assert
」文は、条件が成立しない場合に例外を発生させます。
この場合、条件は「msg.sender == WETH
」となっています。 - 条件式「
msg.sender == WETH
」は、Etherを送信したアドレス(送信者)がWETHコントラクトであるかどうかを確認しています。 - もし送信者がWETHコントラクトでない場合、条件を満たさないと判断され、例外が発生してトランザクションが中止されます。
- 送信者がWETHコントラクトである場合、条件を満たすと判断され、ETHの受け入れが許可されます。
関数
READ
quote
指定された数量のトークンAが、指定されたトークンAとトークンBのリザーブ(保有数量)に基づいて相当する数量のトークンBを計算する関数。
引数
-
amountA
- トークンAの数量。
- この数量に対して、相当するトークンBの数量が計算される。
-
reserveA
- トークンAのリザーブ(保有数量)。
- この数量はUniswapプール内のトークンAの保有数量。
-
reserveB
- トークンBのリザーブ(保有数量)。
- この数量はUniswapプール内のトークンBの保有数量。
処理
- 指定された数量のトークンAが、指定されたトークンAとトークンBのリザーブに基づいて相当する数量のトークンBを計算します。
- 計算された相当するトークンBの数量を
amountB
として返します。
getAmountOut
指定された数量の入力トークンが、指定された入力トークンと出力トークンのリザーブ(保有数量)に基づいて交換時の予測される出力トークンの数量を計算する関数。
引数
-
amountIn
- 入力トークンの数量。
- この数量がどれだけ交換に使われるかを示す。
-
reserveIn
- 入力トークンのリザーブ(保有数量)。
-
reserveOut
- 出力トークンのリザーブ(保有数量)。
処理
- 指定された数量の入力トークンが、指定された入力トークンと出力トークンのリザーブに基づいて交換時の予測される出力トークンの数量を計算します。
- 計算された予測出力トークンの数量を
amountOut
として返します。
getAmountIn
指定された数量の出力トークンが、指定された入力トークンと出力トークンのリザーブ(保有数量)に基づいて交換時に必要な入力トークンの数量を計算する関数。
引数
-
amountOut
- 出力トークンの数量。
- この数量がどれだけの出力トークンを得たいかを示す。 -
reserveIn
- 入力トークンのリザーブ(保有数量)。 -
reserveOut
- 出力トークンのリザーブ(保有数量)。
処理
- 指定された数量の出力トークンが、指定された入力トークンと出力トークンのリザーブに基づいて交換時に必要な入力トークンの数量を計算します。
- 計算された必要な入力トークンの数量を
amountIn
として返します。
getAmountsOut
指定された入力トークンの数量とトークンのパス(トークン間の交換経路)を使用して、それぞれのトークンの数量を予測数量として計算する関数。
引数
-
amountIn
- 入力トークンの数量。
- この数量が交換に際してどれだけの入力トークンを使用するかを示す。 -
path
- 交換するトークン間の順番通りのアドレスが含まれる配列。
- 例えば、[TokenA, TokenB, TokenC]のように、TokenAからTokenBを経由してTokenCに交換する場合、このパスは[TokenAのアドレス, TokenBのアドレス, TokenCのアドレス]となる。
処理
- 指定された入力トークンの数量とトークンのパスを使用して、それぞれのトークンの数量を
path
配列内の各トークンの予測数量として計算します。 - 計算された予測数量の配列を
amounts
として返します。 - この配列は、
path
配列内の各トークンの数量が含まれており、交換後のトークンの数量を表します。
getAmountsIn
指定された出力トークンの数量とトークンのパス(トークン間の交換経路)を使用して、それぞれのトークンの必要数量を計算する関数。
引数
-
amountOut
- 出力トークンの数量。
- この数量が交換によって得られる出力トークンの数量を示す。 -
path
- 交換するトークン間の順番通りのアドレスが含まれる配列。
- 例えば、[TokenA, TokenB, TokenC]のように、TokenAからTokenBを経由してTokenCに交換する場合、このパスは[TokenAのアドレス, TokenBのアドレス, TokenCのアドレス]となる。
処理
- 指定された出力トークンの数量とトークンのパスを使用して、それぞれのトークンの数量を
path
配列内の各トークンの必要数量として計算します。 - 計算された必要数量の配列を
amounts
として返します。 - この配列は、
path
配列内の各トークンの数量が含まれており、交換後のトークンの数量を表します。
WRITE
_addLiquidity
Uniswapの流動性を追加するためのロジックを実装している関数。
流動性プールにトークンを追加する際に使用されるもので、トークンの交換率やリザーブ(保有量)の状態に基づいて適切な量を計算して流動性を提供します。
また、新しいトークンペアを作成することで、まだ存在しないトークンペアの追加も行えます。
引数
-
tokenA
- 流動性プールに追加するトークンAのアドレス。
-
tokenB
- 流動性プールに追加するトークンBのアドレス。
-
amountADesired
- 預け入れるトークンAの数量。
-
amountBDesired
- 預け入れるトークンBの数量。
-
amountAMin
- 受け入れ可能な最小トークンAの数量。
-
amountBMin
- 受け入れ可能な最小トークンBの数量。
処理
- ペアが存在しない場合は、指定されたトークンAとトークンBのペアを作成します。
これによって流動性プールが初めて作成されることがあります。 - 指定されたトークンAとトークンBのリザーブ(保有量)を取得します。
- もし両方のトークンのリザーブがゼロであれば、預け入れる数量そのままを設定します。
- もしリザーブがゼロでない場合、最適な取引量を計算します。
トークンAを指定数量で交換した場合の、トークンBの最適な数量を計算します。
その後、トークンBの最適な数量が望む数量以下であれば、最小数量を満たすか確認します。
条件を満たす場合、トークンAとトークンBの数量を設定します。 - そうでなければ、トークンBを指定数量で交換した場合の、トークンAの最適な数量を計算します。計算された最適な数量が望む数量以上であるか確認し、最小数量を満たすか確認します。
条件を満たす場合、トークンAとトークンBの数量を設定します。
addLiquidity
Uniswapの流動性を追加する関数。
指定されたトークンAとトークンBの数量に基づいて流動性プールにトークンを供給し、対応する流動性トークンを受け取ります。
引数
-
tokenA
- 流動性プールに追加するトークンAのアドレス。
-
tokenB
- 流動性プールに追加するトークンBのアドレス。
-
amountADesired
- 預け入れるトークンAの数量。
-
amountBDesired
- 預け入れるトークンBの数量。
-
amountAMin
- 受け入れ可能な最小トークンAの数量。
-
amountBMin
- 受け入れ可能な最小トークンBの数量。
-
to
- 流動性トークンを受け取るアドレス。
-
deadline
- 処理の有効期限。
処理
- 有効期限が確認されます。
トランザクションの実行が期限内であることを確認するための修飾子「ensure
」が適用されています。 - 内部関数「
_addLiquidity
」を呼び出し、指定された引数に基づいて最適な取引量と供給するトークンの数量を計算します。 - トークンAとトークンBの数量が計算されると、それらのトークンを指定の流動性プールへ移動させるために「
TransferHelper.safeTransferFrom
」関数が使用されます。 - 最後に、計算された数量と指定された受取アドレス「
to
」を使用して、流動性トークンを発行するための「IUniswapV2Pair(pair).mint(to)
」が呼び出されます。
この操作によって、新しい流動性トークンが発行され、供給者に発行された流動性トークンが送られます。
addLiquidityETH
ETH(Ether)とトークンを使用してUniswapの流動性を追加する関数。
ETHとトークンを使用してUniswapの流動性を追加するためのものであり、ETHを受け入れつつトークンを供給する場合に利用されます。
引数
-
token
- 流動性プールに追加するトークンのアドレス。
-
amountTokenDesired
- 預け入れるトークンの数量。
-
amountTokenMin
- 受け入れ可能な最小トークンの数量。
-
amountETHMin
- 受け入れ可能な最小ETHの数量。
-
to
- 流動性トークンを受け取るアドレス。
-
deadline
- 処理の有効期限。
処理
- 有効期限が確認されます。
トランザクションの実行が期限内であることを確認するための修飾子「ensure
」が適用されています。 - 内部関数「
_addLiquidity
」を呼び出し、指定された引数に基づいて最適な取引量と供給するトークンとETHの数量を計算します。
この関数は、トークンとETHの両方を受け取り、トークンとETHを流動性プールに供給するための取引量を計算します。 - トークンとETHの数量が計算されると、トークンを指定の流動性プールへ移動させるために「
TransferHelper.safeTransferFrom
」関数が使用されます。
また、ETHも受け入れるために、内部で「IWETH(WETH).deposit{value: amountETH}()
」と「IWETH(WETH).transfer(pair, amountETH)
」が呼び出されます。 - 最後に、計算された数量と指定された受け取りアドレス「
to
」を使用して、流動性トークンを発行するための「IUniswapV2Pair(pair).mint(to)
」が呼び出されます。
この操作によって、新しい流動性トークンが発行され、供給者に発行された流動性トークンが送られます。 - トランザクションに余分なETHが含まれている場合、その余分なETHは送り返されます。
removeLiquidity
Uniswapの流動性を取り出す関数。
指定数量の流動性トークンを取り出して、対応するトークンAとトークンBを返すためのものです。
引数
-
tokenA
- 流動性プールから取り出すトークンAのアドレス。
-
tokenB
- 流動性プールから取り出すトークンBのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountAMin
- 受け入れ可能な最小トークンAの数量。
-
amountBMin
- 受け入れ可能な最小トークンBの数量。
-
to
- 預け入れていたトークンを受け取るアドレス。
-
deadline
- 処理の有効期限。
処理
- 有効期限が確認されます。
トランザクションの実行が期限内であることを確認するための修飾子「ensure
」が適用されています。 - トークンAとトークンBのアドレスを使用して、対応する流動性プールのアドレスを計算します。
- 「
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity)
」を呼び出して、指定数量の流動性トークンを持っているユーザーから流動性プールへ送信します。
これによって、指定数量の流動性トークンが流動性プールに戻されます。 - 「
IUniswapV2Pair(pair).burn(to)
」を呼び出して、流動性プールから指定された受け取りアドレス「to
」に向けてトークンAとトークンBを取り出します。
この操作により、トークンAとトークンBの数量が計算されます。 - トークンAとトークンBのアドレスをソートし、トークンAのアドレスがどちらかを特定します。
そして、それに基づいて数量を「amountA
」と「amountB
」に割り当てます。 - 最後に、「
amountAMin
」と「amountBMin
」の条件を確認し、取り出すトークンの数量が受け入れ可能な最小数量を下回らないことを確認します。
条件を満たしていない場合、トランザクションは中断されます。
removeLiquidityETH
ETHと任意のトークン間での流動性を取り出す関数。
ETHと任意のトークンの流動性トークンを取り出し、指定数量のトークンとETHを受け取るためのものです。
引数
-
token
- 取り出すトークンのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountTokenMin
- 受け入れ可能な最小トークンの数量。
-
amountETHMin
- 受け入れ可能な最小ETHの数量。
-
to
- 取り出したトークンとETHを受け取るアドレス。
-
deadline
- 処理の有効期限。
処理
- 有効期限が確認されます。
トランザクションの実行が期限内であることを確認するための修飾子「ensure
」が適用されています。 - 内部で定義された「
removeLiquidity
」関数を呼び出し、指定数量のトークンとETHの流動性トークンを取り出します。
この際、関数の引数として指定された「token
」に加えて、WETH(ETHをラップしたトークン)も引数として渡します。 - 「
removeLiquidity
」関数から返された「amountToken
」と「amountETH
」を使用して、指定数量のトークンとETHを受け取りアドレス「to
」に送信します。 - 取り出したETHはWETHトークンにラップされているため、ETHを取り出すために「
IWETH(WETH).withdraw(amountETH)
」を呼び出します。
これにより、指定数量のETHがWETHトークンから取り出されます。 - 最後に、「
TransferHelper.safeTransferETH(to, amountETH)
」を使用して、指定数量のETHを受け取りアドレス「to
」に送信します。
これにより、取り出されたETHが受け取りアドレスに送信されます。
removeLiquidityWithPermit
UniswapV2のプールからトークンを取り出す際に、トークンの所有者が事前に署名した許可を使用してトランザクションを実行する関数。
トークンの所有者が署名を用いて許可を与えた上で、プールからトークンを取り出すためのものです。
引数
-
tokenA
- 取り出すトークンAのアドレス。
-
tokenB
- 取り出すトークンBのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountAMin
- 受け入れ可能な最小トークンAの数量。
-
amountBMin
- 受け入れ可能な最小トークンBの数量。
-
to
- 取り出したトークンを受け取るアドレス。
-
deadline
- 処理の有効期限。
-
approveMax
- 署名の許可を与えるためのフラグ。
-
true
ならば無制限、false
ならばliquidity
で指定した数量までの許可が与えられる。
-
v
,r
,s
- 署名のパラメータ
処理
-
pair
変数を使用して、tokenA
とtokenB
のアドレスからプールのペアのアドレスを取得します。 -
approveMax
がtrue
であれば、value
に最大値(2^256 - 1
)を設定し、それ以外の場合はliquidity
の値を設定します。
これはトークンの許可を与える量を指定します。 -
IUniswapV2Pairのpermit
関数を呼び出して、トークンの所有者が事前に署名した許可情報を使用してトランザクションを起こします。 -
removeLiquidity
関数を呼び出して、実際にトークンを取り出す処理を行います。
この関数はトークンの数量や最小受け入れ可能量を考慮してトークンを取り出します。 - 最終的に、
amountA
とamountB
に取り出されたトークンの数量が代入され、この値が関数の戻り値として返されます。
removeLiquidityETHWithPermit
UniswapV2のプールからトークンとETHを同時に取り出す操作を行う際に、トークンの所有者があらかじめ署名した許可情報を使用してトランザクションを実行する関数。
引数
-
token
- 取り出すトークンのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountTokenMin
- 受け入れ可能な最小トークンの数量。
-
amountETHMin
- 受け入れ可能な最小ETHの数量。
-
to
- 取り出したトークンとETHを受け取るアドレス。
-
deadline
- 処理の有効期限。
-
approveMax
- 署名の許可を与えるためのフラグ。
-
true
ならば無制限、false
ならばliquidity
で指定した数量までの許可が与えられる。
-
v
,r
,s
- 署名のパラメータ
処理
-
pair
変数を使用して、token
とWETH(ラップされたEther)のアドレスからプールのペアのアドレスを取得します。 -
approveMax
がtrue
であれば、value
に最大値(2^256 - 1
)を設定し、それ以外の場合はliquidity
の値を設定します。
これはトークンの許可を与える量を指定します。 -
IUniswapV2Pairのpermit
関数を呼び出して、トークンの所有者が事前に署名した許可情報を使用してトランザクションを起こします。 -
removeLiquidityETH
関数を呼び出して、実際にトークンとETHを同時に取り出す処理を行います。
この関数はトークンとETHの数量や最小受け入れ可能量を考慮してトークンとETHを取り出します。 - 最終的に、
amountToken
とamountETH
に取り出されたトークンとETHの数量が代入され、この値が関数の戻り値として返されます。
removeLiquidityETHSupportingFeeOnTransferTokens
UniswapV2のプールからトークンとETHを同時に取り出す操作を行う際に、フィー(手数料)を支払うトークン(fee-on-transfer tokens)をサポートするための関数。
引数
-
token
- 取り出すトークンのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountTokenMin
- 受け入れ可能な最小トークンの数量。
-
amountETHMin
- 受け入れ可能な最小ETHの数量。
-
to
- 取り出したトークンとETHを受け取るアドレス。
-
deadline
- 処理の有効期限。
処理
-
removeLiquidity
関数を呼び出して、トークンとETHを取り出す操作を行います。
この関数の戻り値であるamountETH
は取り出したETHの数量を示します。 - トークンのバランスを取得し、「
TransferHelper.safeTransfer
」を使用して指定したアドレスにトークンを送信します。
これにより、フィー(手数料)を支払うトークンを受け取るための準備が行われます。 -
WETHコントラクトの
withdraw
関数を呼び出して、取り出したETHを元のEtherに戻します。 - 「
TransferHelper.safeTransferETH
」を使用して、指定したアドレスに取り出したETHを送信します。
これにより、ETHが受け取り先に送信されます。
removeLiquidityETHWithPermitSupportingFeeOnTransferTokens
フィー(手数料)を支払うトークンをサポートしながら、UniswapV2のプールからトークンとETHを同時に取り出す操作を行う関数。
引数
-
token
- 取り出すトークンのアドレス。
-
liquidity
- 取り出す流動性トークンの数量。
-
amountTokenMin
- 受け入れ可能な最小トークンの数量。
-
amountETHMin
- 受け入れ可能な最小ETHの数量。
-
to
- 取り出したトークンとETHを受け取るアドレス。
-
deadline
- 処理の有効期限。
-
approveMax
- 署名の許可を与えるためのフラグ。
-
true
ならば無制限、false
ならばliquidity
で指定した数量までの許可が与えられる。
-
v
,r
,s
- 署名のパラメータ
処理
-
pairFor
関数を使用してトークンとWETHのペアのアドレスを取得します。 -
approveMax
の値に応じて、permit
の許可数量を設定します。
approveMax
がtrue
の場合は無制限の許可、false
の場合はliquidity
の数量を許可します。 -
IUniswapV2Pairインターフェースの
permit
関数を呼び出して、トークンの許可を行います。
これにより、フィー(手数料)を支払うトークンを許可します。 -
removeLiquidityETHSupportingFeeOnTransferTokens
関数を呼び出して、トークンとETHの同時取り出し操作を行います。
この関数の戻り値であるamountETH
を返します。
_swap
UniswapV2のプール内でトークンをスワップする関数。
引数
-
amounts
- スワップするトークンの量の配列。
-
amounts[0]
は入力トークンの数量で、amounts[n]
は出力トークンの数量(nはスワップするトークンの数)。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。
-
_to
- スワップのしたトークンを受け取るアドレス。
処理
-
path
の要素数から1
を引いた数(path.length - 1
)だけ繰り返します。
これはパス内のトークンのペアごとに処理を行うためです。 - 現在のトークンと次のトークンを取得し、ソートして
input
とoutput
に設定します。
ソートによって、トークンの順序が正しくなるように決定されます。 - スワップする出力トークンの数量を
amountOut
に設定します。 -
input
とoutput
がどちらがトークン0
かを判定し、それに応じてamount0Out
とamount1Out
を設定します。
これは、スワップペアによって異なる量のトークンが生成されるためです。 - 現在のトークンと次のトークンから、スワップの結果を受け取るアドレス
to
を決定します。
i
がpath.length - 2
未満の場合は次のトークンまでのペアのアドレス、それ以外の場合は_to
になります。 -
UniswapV2Library.pairFor
関数を使用して、現在のトークンと次のトークンのペアのアドレスを取得します。 -
IUniswapV2Pairインターフェースの
swap
関数を呼び出して、トークンをスワップします。
amount0Out
とamount1Out
に応じて、適切な量のトークンがスワップされます。
swapExactTokensForTokens
指定したトークンの入力量に対して一定量の出力トークンを得るために、UniswapV2のプールでトークンをスワップする関数。
引数
-
amountIn
- 入力トークンの数量。
-
amountOutMin
- 受け入れ可能な最小出力トークンの数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
UniswapV2Library.getAmountsOut
関数を使用して、指定された入力トークン量とトークンパスに基づいて、スワップ後の出力トークン量の配列amounts
を取得します。
amounts[0]
は入力トークンから最初のトークンへのスワップで得られる数量で、amounts[n]
は最終的な出力トークンの数量です。 -
amounts
配列の最後の要素(amounts[amounts.length - 1]
)がamountOutMin
以上であることを確認します。
これにより、最低限必要な出力トークンの数量を保証します。 -
TransferHelper.safeTransferFrom
関数を使用して、入力トークンを送信します。
具体的には、msg.sender
から最初のトークンへのスワップペアのアドレスに対して、指定された入力トークン量を送信します。 -
_swap
関数を呼び出して、トークンを実際にスワップします。
これにより、指定されたトークンパスに基づいてスワップが行われます。
swapTokensForExactTokens
指定した出力トークンの数量に対して、最大限許容される入力トークンの量を計算し、その入力トークン量でトークンをスワップする関数。
引数
-
amountOut
- 受け入れ可能な最小出力トークンの数量。
-
amountInMax
- 受け入れ可能な最大入力トークンの数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
UniswapV2Library.getAmountsIn
関数を使用して、指定された出力トークン量とトークンパスに基づいて、スワップに必要な最大入力トークン量の配列amounts
を取得します。
amounts[0]
は入力トークンから最初のトークンへのスワップで得られる数量で、amounts[n]
は最終的な出力トークンの数量です。 -
amounts
配列の最初の要素(amounts[0]
)がamountInMax
以下であることを確認します。
これにより、許容される最大の入力トークン量を超えないようにします。 -
TransferHelper.safeTransferFrom
関数を使用して、入力トークンを送信します。
具体的には、msg.sender
から最初のトークンへのスワップペアのアドレスに対して、指定された最大入力トークン量を送信します。 -
_swap
関数を呼び出して、トークンを実際にスワップします。
これにより、指定されたトークンパスに基づいてスワップが行われます。
swapExactETHForTokens
指定したETHの数量を使用して、ETHをトークンにスワップする関数。
引数
-
amountOutMin
- 受け入れ可能な最小出力トークンの数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path
配列の最初の要素がWETH(ETHのラップトークン)であることを確認します。
これはETHからトークンにスワップするためのパスを指定する必要があることを示します。 -
UniswapV2Library.getAmountsOut
関数を使用して、指定されたETH量とトークンパスに基づいて、スワップによって得られる出力トークンの数量の配列amounts
を計算します。
amounts[0]
はETHからのスワップに必要なETHの数量であり、amounts[n]
は出力トークンまでのスワップによって得られるトークンの数量です。 -
amounts
配列の最後の要素(amounts[amounts.length - 1]
)がamountOutMin
以上であることを確認します。
これにより、望む出力トークンの最小数量を満たすことを保証します。 -
IWETH(WETH).deposit{value: amounts[0]}()
を使用して、指定されたETH量をWETHに変換し、WETHを受け取ります。 -
assert(IWETH(WETH).transfer(...))
を使用して、WETHをスワップの最初のペアに送信します。 -
_swap
関数を呼び出して、トークンのスワップを実行します。
これにより、指定されたトークンパスに基づいてETHからトークンにスワップが行われます。
swapTokensForExactETH
指定したトークンの数量を使用してトークンをETHにスワップする関数。
引数
-
amountOut
- 受け入れ可能な最小出力ETHの数量。
-
amountInMax
- 受け入れ可能な最大トークン数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたETHを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path
配列の最初の要素がWETH(ETHのラップトークン)であることを確認します。
これはトークンからETHにスワップするためのパスを指定する必要があることを示します。 -
UniswapV2Library.getAmountsIn
関数を使用して、指定された出力ETH量とトークンパスに基づいて、必要な入力トークンの数量の配列amounts
を計算します。
amounts[0]
はスワップに必要な入力トークンの数量であり、amounts[n]
はETHへのスワップによって得られるETHの数量です。 -
amounts[0]
がamountInMax
以下であることを確認します。
これにより、指定した最大入力トークン数量内でスワップを行うことを保証します。 -
TransferHelper.safeTransferFrom
を使用して、指定された入力トークンをペアに送信します。 -
_swap
関数を呼び出して、トークンのスワップを実行します。
これにより、指定されたトークンパスに基づいてトークンからETHにスワップが行われます。 -
IWETH(WETH).withdraw
を使用して、WETHをETHに変換し、出力ETHの数量を取得します。 -
TransferHelper.safeTransferETH
を使用して、指定されたアドレスに出力ETHの数量を送信します。
swapExactTokensForETH
指定したトークンの数量を使用してトークンをETHにスワップする関数。
引数
-
amountIn
- 受け入れ可能な最小入力トークンの数量。
-
amountInMax
- 受け入れ可能な最小ETH数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたETHを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path
配列の最初の要素がWETH(ETHのラップトークン)であることを確認します。
これはトークンからETHにスワップするためのパスを指定する必要があることを示します。 -
UniswapV2Library.getAmountsOut
関数を使用して、指定されたETH量とトークンパスに基づいて、スワップによって得られる出力ETHの数量の配列amounts
を計算します。
amounts[0]
は入力トークンからスワップに必要な出力ETHの数量であり、amounts[n]
はETHへのスワップによって得られるETHの数量です。 -
amounts[n]
がamountOutMin
以上であることを確認します。
これにより、指定した最小出力ETH数量を満たすようにスワップが行われることを保証します。 -
TransferHelper.safeTransferFrom
を使用して、指定された入力トークンをペアに送信します。 -
_swap
関数を呼び出して、トークンのスワップを実行します。
これにより、指定されたトークンパスに基づいてトークンからETHにスワップが行われます。 -
IWETH(WETH).withdraw
を使用して、WETHをETHに変換し、出力ETHの数量を取得します。 -
TransferHelper.safeTransferETH
を使用して、指定されたアドレスに出力ETHの数量を送信します。
swapETHForExactTokens
指定したETHの数量を使用してトークンを取得する関数。
引数
-
amountOut
- 受け入れ可能な最小出力トークン数量。
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path
配列の最初の要素がWETH(ETHのラップトークン)であることを確認します。
これはETHからトークンにスワップするためのパスを指定する必要があることを示します。 -
UniswapV2Library.getAmountsIn
関数を使用して、指定された出力トークン数量とトークンパスに基づいて、必要な入力ETHの数量の配列amounts
を計算します。
amounts[0]
はスワップに必要な入力ETHの数量であり、amounts[n]
は指定した出力トークン数量を得るために必要なETHの数量です。 -
amounts[0]
がmsg.value
以下であることを確認します。
これにより、指定した入力ETH数量を超えないようにスワップが行われることを保証します。 -
IWETH(WETH).deposit
を使用して、ETHをWETHに変換し、入力ETHの数量をWETHとして保持します。 -
IWETH(WETH).transfer
を使用して、WETHを指定されたペアに送信します。
これにより、WETHからトークンへのスワップが行われます。 -
_swap
関数を呼び出して、トークンのスワップを実行します。
これにより、指定されたトークンパスに基づいてETHからトークンにスワップが行われます。 - もし実際に送信されたETHが必要な入力ETHの数量よりも多い場合、余剰のETHを送信元に返金します。
_swapSupportingFeeOnTransferTokens
手数料を支払うトークンをサポートするスワップ処理を実行する関数。
引数
-
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。
処理
-
for
ループを使用して、パスに含まれる各トークンのスワップを順番に実行します。 - ループ内で、
path
配列の現在の要素と次の要素をinput
とoutput
に分割します。 -
UniswapV2Library.sortTokens
関数を使用して、input
とoutput
のトークンの順序をソートし、それぞれtoken0
とtoken1
に割り当てます。 - IUniswapV2Pairインターフェースを使用して、スワップするペアのリザーブ(保有数量)を取得します。
-
IERC20(input).balanceOf(address(pair))
を使用して、スワップペアに保有されているinput
トークンの残高を取得します。
この値からリザーブの差分を計算し、スワップするためのトークンの数量amountInput
を決定します。 -
UniswapV2Library.getAmountOut
関数を使用して、amountInput
トークンをスワップした場合に得られるoutput
トークンの数量を計算します。 -
input
トークンとoutput
トークンの関係に応じて、スワップに使用するトークンの数量をamount0Out
とamount1Out
に設定します。 -
UniswapV2Library.pairFor
関数を使用して、スワップ先のペアのアドレスを計算し、スワップ結果を送信するアドレスto
を決定します。 -
pair.swap
関数を使用して、実際のトークンのスワップを実行します。
この関数により、スワップされたトークンがto
に送信されます。
swapExactTokensForTokensSupportingFeeOnTransferTokens
手数料を支払うトークンをサポートする状態で、一定数量のトークンをスワップして別のトークンに交換する関数。
引数
-
amountIn
- スワップ元のトークンの数量。
-
amountOutMin
- 受け入れ可能な最小出力トークン数量。 -
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
TransferHelper.safeTransferFrom
関数を使用して、msg.sender
からスワップ元のトークンを移動します。
移動するトークンはamountIn
で指定された数量です。 -
UniswapV2Library.pairFor
関数を使用して、スワップするトークンのペアのアドレスを計算します。
このアドレスにスワップ元のトークンを送信します。 - スワップ前の
to
アドレスにおけるpath[path.length - 1]
トークンの残高を取得し、balanceBefore
として保持します。 -
_swapSupportingFeeOnTransferTokens
関数を呼び出して、手数料を支払うトークンをサポートする状態でトークンをスワップします。 - スワップ後、
to
アドレスにおけるpath[path.length - 1]
トークンの残高を再度取得し、スワップ結果として得られたトークンの増加量を計算します。 -
amountOutMin
と比較して、スワップ結果として得られたトークンの増加量が十分かを確認します。
十分でない場合、エラーを出力します。
swapExactETHForTokensSupportingFeeOnTransferTokens
ETH(イーサリアム)を支払って、手数料を支払うトークンをサポートする状態で一定数量のETHをスワップして別のトークンに交換する関数。
引数
-
amountOutMin
- 受け入れ可能な最小出力トークン数量。 -
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path[0]
がWETH(Wrapped Ether)であることを確認します。
WETHはETH をイーサリアムのトークンとしてラップしたものです。 -
msg.value
から得られるETHの数量をamountIn
として保持します。 -
IWETH(WETH).deposit
関数を使用して、ETHをWETHに変換し、WETHの数量をamountIn
として保持します。 -
IWETH(WETH).transfer
関数を使用して、WETHをスワップの最初のトークンであるpath[0]
として指定されたペアに送信します。 - スワップ前の
to
アドレスにおけるpath[path.length - 1]
トークンの残高を取得し、balanceBefore
として保持します。 -
_swapSupportingFeeOnTransferTokens
関数を呼び出して、手数料を支払うトークンをサポートする状態でトークンをスワップします。 - スワップ後、
to
アドレスにおけるpath[path.length - 1]
トークンの残高を再度取得し、スワップ結果として得られたトークンの増加量を計算します。 -
amountOutMin
と比較して、スワップ結果として得られたトークンの増加量が最低量amountOutMin
を満たしているかを確認します。
満たしていない場合、エラーを出力します。
swapExactTokensForETHSupportingFeeOnTransferTokens
手数料を支払うトークンをサポートする状態で一定数量のトークンをスワップしてETHに交換する関数。
引数
-
amountIn
- スワップするトークンの数量。
-
amountOutMin
- 受け入れ可能な最小出力ETH数量。 -
path
- スワップするトークンのパスを示す配列。
- パスは入力トークンから出力トークンまでのトークンの順序を指定。 -
to
- スワップのしたトークンを受け取るアドレス。 -
deadline
- 処理の有効期限。
処理
-
path[path.length - 1]
がWETH(Wrapped Ether)であることを確認します。
ETHを表すWETHトークンです。 -
path[0]
からトークンの数量amountIn
を、指定されたペアに送信します。 -
_swapSupportingFeeOnTransferTokens
関数を呼び出して、手数料を支払うトークンをサポートする状態でトークンをスワップします。 - スワップ後、アドレス
address(this)
におけるWETHトークンの残高を取得し、amountOut
として保持します。 -
amountOut
が最小数量amountOutMin
を満たしているかを確認します。
満たしていない場合、エラーを出力します。 -
IWETH(WETH).withdraw
関数を使用してWETHをETHに戻し、その数量を引き出します。 -
TransferHelper.safeTransferETH
関数を使用して、指定されたアドレスto
にETHを送信します。
イベント
特になし。
コード
記事の最大文字数の関係から、UniswapV2Router01と共通の関数は省きます。
IUniswapV2Router02
IUniswapV2Router02
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
UniswapV2Router02
UniswapV2Router02
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// refund dust eth, if any
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
SafeMath
SafeMath
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
UniswapV2Library
UniswapV2Library
library UniswapV2Library {
using SafeMath for uint;
// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}
// fetches and sorts the reserves for a pair
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// performs chained getAmountIn calculations on any number of pairs
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
最後に
今回の記事では、Bunzzの新機能『DeCipher』を使用して、UniswapV2の「UniswapV2Router02」のコントラクトを見てきました。
いかがだったでしょうか?
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。
普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!
Discussion