Uniswap V2 入門&徹底解析
ここではUniswapとは何?という紹介するわけではなく、Uniswap V2の中を徹底的に解析して紹介しています。なので、solidityの基礎知識など必要になるかと思います!
何か間違えている箇所などありましたらコメントいただけるとありがたいです!
主に以下を解析していきます。
- UniswapV2Pair
- UniswapV2Factory
- UniswapV2Router02
流動性
UniswapV2において流動性というのはすごく重要な概念なので、簡単にまとめてみます。
流動性の計算が自動市場メイカー(AMM)アルゴリズムの中心になります。このアルゴリズムは以下の式に基づいています。
x * y = k
流動性プロバイダーは2つのトークンをプールにデポジットし、流動性トークンを受け取ります。AMMアルゴリズムは、式x * y = kに基づいて流動性を計算します。プロバイダーは、mint関数で流動性を追加し、burn関数で流動性を引き出します。Uniswap V2の流動性は、価格スリップと手数料に影響を与えるため、適切なバランスが重要なので、流動性というのはすごい重要なんです。
UniswapV2 Pair
initialize
ペアを作る際に、2つのトークンを設定するために使います。
constructorの代わりにinitializeを使っていますが、それには理由があってfactoryパターンをつかっているからなんですね。
UniswapV2Factory
という一つのコントラクトから複数のペアを作成することができます。このFactoryパターンを実現するために、initialize
関数を使用します。initialize
関数を使用することで、ペアコントラクトのデプロイ後に、各ペアごとに独自の初期設定を行うことができます。これにより、Factoryコントラクトは効率的にペアコントラクトを生成・管理することができます。
引数:
_token0 | address | ペア内の最初のトークンのアドレス |
_token1 | address | ペア内の2番目のトークンのアドレス |
function initialize(address _token0, address _token1) external {
// pairを実行するのがUniswapV2Factoryであることを確認
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
// 以下は各トークンを設定
token0 = _token0;
token1 = _token1;
}
mint
mint関数は、新しい流動性プロバイダーがペアにトークンを追加する際に使用されます。流動性トークンを発行し、それを追加する流動性プロバイダーに割り当てます。
流動性の計算は非常に重要な概念であるため、簡単に説明します。
流動性の計算は以下の式で成り立ちます。
x * y = k
ペアのリザーブ残高(xとy)の積(k)が一定であることを保証することで、取引によって各トークンのレートが決まります。この計算がAMMの中核となるので、すごく重要な概念です。
引数:
to | address | 流動性トークンを受け取るアドレス |
function mint(address to) external lock returns (uint liquidity) {
// 現在のリザーブを取得
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// token0の残高を確認
uint balance0 = IERC20(token0).balanceOf(address(this));
// token1の残高を確認
uint balance1 = IERC20(token1).balanceOf(address(this));
// _reserve0 - reserve0で合計のトークン残高を計算
uint amount0 = balance0.sub(_reserve0);
// _reserve1 - reserve1で合計のトークン残高を計算
uint amount1 = balance1.sub(_reserve1);
// 手数料を計算・蓄積し、手数料が適用されているかどうかをboolで取得
bool feeOn = _mintFee(_reserve0, _reserve1);
// ガス代節約のため一度ローカル変数へ代入する
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// _totalSupplyがゼロの時
if (_totalSupply == 0) {
// 追加されたトークン量の積の平方根に基づいて初期流動性を計算
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
// 最初のMINIMUM_LIQUIDITYトークンをアドレス0で永久にロック
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
// 追加されたトークンと既存のリザーブの比率に基づいて新しい流動性トークンを計算
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
// 発行された流動性が0より大きいことを確認
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
// 流動性トークンを指定されたアドレスに発行
_mint(to, liquidity);
// 新しいトークン残高でリザーブを更新
_update(balance0, balance1, _reserve0, _reserve1);
// プロトコル手数料が有効な場合はkLastを更新
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
// 送信者と追加されたトークン量を含むMintイベントを発行
emit Mint(msg.sender, amount0, amount1);
}
burn
burn
関数は、流動性プロバイダーが流動性を引き出す際に使用されます。流動性プロバイダーは、流動性トークンを破棄し、リザーブからトークンを引き出します。関数はトークンの残高とリザーブを更新し、必要に応じてfee(手数料)を計算します。
要するに、自分が預けていた各トークンをLPトークンを返して引き出す処理です。
引数:
to | address | トークンを受け取るアドレス |
function burn(address to) external lock returns (uint amount0, uint amount1) {
// 現在のリザーブを取得
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
// token0の残高を取得
uint balance0 = IERC20(_token0).balanceOf(address(this));
// token1の残高を取得
uint balance1 = IERC20(_token1).balanceOf(address(this));
// 送信者の持っている流動性トークンの量を取得
uint liquidity = balanceOf[address(this)];
// 手数料を計算・蓄積し、手数料が適用されているかどうかをboolで取得
bool feeOn = _mintFee(_reserve0, _reserve1);
// ガス代節約のため一度ローカル変数へ代入する
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// 引き出すトークン量を計算
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
// 引き出すトークン量を計算
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
// 各引き出すトークン量が0以上であることを確認
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
// 送信者の流動性トークンをburnする
_burn(address(this), liquidity);
// 引き出すトークンをtoへ送る
_safeTransfer(_token0, to, amount0);
// 引き出すトークン量を計算
_safeTransfer(_token1, to, amount1);
// 引き出した後のトークン保有量を取り出す
balance0 = IERC20(_token0).balanceOf(address(this));
// 引き出した後のトークン保有量を取り出す
balance1 = IERC20(_token1).balanceOf(address(this));
// 上記で取引した内容をリザーブへ更新する
_update(balance0, balance1, _reserve0, _reserve1);
// プロトコル手数料が有効な場合はkLastを更新
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
// 送信者とburnトークン量を含むBurnイベントを発行
emit Burn(msg.sender, amount0, amount1, to);
}
swap
swap
関数は、トークン間の取引を行う際に使用されます。この関数は、リザーブ内のトークンを交換することで、2つの異なるトークン間の価格を提供し、リザーブを更新し必要に応じてfee(手数料)を計算します。
引数:
amount0Out | uint | 交換されるtoken0の量 |
amount1Out | uint | 交換されるtoken1の量 |
to | address | 交換されるトークンを受け取るアドレス |
data | bytes | 追加のデータ(必要に応じて) |
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
// 出力量がゼロ以上であることを確認
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
// 各リザーブを取得
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
// gas savings
// 各出力量がリザーブより小さいことを確認
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{// scope for _token{0,1}, avoids stack too deep errors
// トークンアドレスを変数に格納
address _token0 = token0;
address _token1 = token1;
// 送信先アドレスがトークンのアドレスと異なることを確認
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
// amount0Outがゼロ以上ならtoken0を送信
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
// amount1Outがゼロ以上ならtoken1を送信
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
// カスタムロジックを実行(オプション)
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
// 残高を取得
balance0 = IERC20(_token0).balanceOf(address(this));
// 残高を取得
balance1 = IERC20(_token1).balanceOf(address(this));
}
// 入力量を計算
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
// 各入力量が0以上であることを確認
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{// scope for reserve{0,1}Adjusted, avoids stack too deep errors
// スワップ後の調整されたトークン残高を計算
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
// スワップ前のトークン残高よりもKの値が減少しないよう確認
// 価格スリッページや不正な取引を防いでいます
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000 ** 2), 'UniswapV2: K');
}
// 上記で取引した内容をリザーブへ更新する
_update(balance0, balance1, _reserve0, _reserve1);
// Swapイベントを通知
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
Uniswap V2Factory
Uniswap V2Factoryは主にトークンペアの作成と管理を行います。
createPairの中身を見てみましょう。各行に何の処理をしているのかを説明しているので気になる方は詳しくみてください。
createPair
function createPair(address tokenA, address tokenB) external returns (address pair) {
// tokenAとtokenBが同じでないことを確認
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
// tokenAとtokenBを比較して大きい方をtoken0小さい方をtoken1へ入れ直す
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
// token0のアドレスが0でないことを確認
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
// token0とtoken1のペアが存在していないか確認
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
// UniswapV2Pairのバイトコード取得
bytes memory bytecode = type(UniswapV2Pair).creationCode;
// token0とtoken1のアドレスを文字列結合してhash化でsaltを作る
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
// create2を使ってコントラクトアドレスをデプロイ前に計算
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
// UniswapV2Pairのイニシャライズをする
IUniswapV2Pair(pair).initialize(token0, token1);
// pairのmapにtoken0とtoknen1のpairのアドレスを入れておく
getPair[token0][token1] = pair;
// 逆のtoken1&token0にも同じようにする
getPair[token1][token0] = pair; // populate mapping in the reverse direction
// pairのアドレスが入った配列のallPairsに追加
allPairs.push(pair);
// emitでペア作成を通知
emit PairCreated(token0, token1, pair, allPairs.length);
}
こんな感じで中身はそんなに複雑なことはしておらず、pairが既に作られているか探したり、なければ新しくPairを生成する処理が書かれています。
使い方もシンプルで見ての通りpairにしたいトークンのアドレスを入れるだけです!超簡単に書くとこんな感じ
IUniswapV2Factory factory;
address pair;
function create(address _tokenA, address _tokenB) external {
pair = factory.createPair(_tokenA, _tokenB);
}
なので、UniswapV2Pairはペアを作っているだけで、実際にペアの管理をしているのはUniswapV2Factoryになります。ペアの管理といってもどのペアで作られたとかどのペアを持っているといった簡単な管理だけです。
Pairのトークンたちに対してswapやburnといった処理を行いたい場合はUniswapV2Pairを使用します。
setFeeTo & setFeeToSetter
setFeeTo
はプロトコル全体で生成された手数料の受取人のアドレスが入ります。Uniswap V2 では、各トレードに対して一定の手数料(デフォルトで 0.3%)が適用され、その手数料が流動性プールに追加されます。これにより、流動性プロバイダーは、プールからの引き出し時に手数料を収益として受け取ることができます。
feeToSetter
は、feeTo
アドレス(手数料受取人)を設定および変更する権限を持つアドレスがになります。feeToSetter
は通常、プロトコルのガバナンス機能や管理者によって設定され、プロトコルの運営や開発に関与する当事者が手数料の受取人を適切に設定および変更できるようにします。
なので、手数料の受け取り人とそれを管理する人を決めているという感じですね。
UniswapV2Router02
Uniswap V2 Router02 は、Uniswap V2 のインターフェースを提供し、トークンの交換や流動性の追加・削除を容易にするためのコントラクトです。順番に説明していきます。
ここでは主要な
addLiquidity
この関数では任意のERC20トークンペアのプールに流動性を追加する際に使用されます。中の実装はどうなっているのか見てみましょう!
まずは引数から
tokenA | ERC-20トークンのアドレス |
tokenB | 二つ目のERC-20トークンのアドレス |
amountADesired | 提供したいトークンAの数量 |
amountBDesired | 提供したいトークンBの数量 |
amountAMin | 提供するトークンAの最小数量。スリッページを考慮に入れた値。 |
amountBMin | 提供するトークンBの最小数量。スリッページを考慮に入れた値。 |
to | LPトークンを受け取るアドレス |
deadline | トランザクションの有効期限(UNIXタイムスタンプ) |
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);
// tokenAをペアcontractへ送る
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
// tokenBをペアcontractへ送る
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
// ここで流動性トークンをtoに発行
liquidity = IUniswapV2Pair(pair).mint(to);
}
やっていることは、
- 最適なトークン量を算出する
- ペアトークンのアドレス生成
- トークンをpairのcontractへ送信
- 流動性トークンを発行
とさまざまなことをおこなっています。
この関数は一見してることは少ないのですが、`_addLiquidity`の関数では色々なことが行われています。
// **** 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);
// 保有量が両方とも0かをチェック
if (reserveA == 0 && reserveB == 0) {
// 保有量が両方ゼロなら希望するトークンの量をペアにそれぞれ入れます
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// ペアがどちらもゼロでなければ、ペアの保有量からamountBの最適なトークン量を計算する
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
// 上記で求められたamountBOptimalのトークン量が希望するトークン量より小さいと
if (amountBOptimal <= amountBDesired) {
// 最適なトークン量が最低トークン量より少なければエラー
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
// ここでamountBのトークン量は最適なトークン量となる
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
// amountBOptimalのトークン量が希望するトークン量より小さいと
// ペアの保有量からamountAの最適なトークン量を計算する
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
// amountAOptimal が amountADesired より大きいとエラーになる
// ここではassertが使われているためここで引っかかるとガス代も消費する
assert(amountAOptimal <= amountADesired);
// 最適なトークン量が最低トークン量より少なければエラー
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
// ここでamountAのトークン量は最適なトークン量となる
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
addLiquidityETH
なぜETHだけ別の関数を使わないといけないのかというと、ETHはERC20トークンではないので、処理を分けてWETHへ変換してERC20へ対応する作業が入ります。
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);
// ERC20トークンをペアcontractへ送る
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
// ETHをWETHへ変換
IWETH(WETH).deposit{value: amountETH}();
// WETH をペアcontractへ送る
assert(IWETH(WETH).transfer(pair, amountETH));
// ここで流動性トークンをtoに発行
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
// 実際に送った金額と使用した金額に差異がある場合をあまりを返却する
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
上記を見てわかるようにDEXにおいてすごく重要なプールに流動性を追加するに関しての関数でした!
removeLiquidity
ここでは流動性プールから流動性を引き出すために使います。この関数は、流動性プロバイダー(LP)がリキッドポジションを解消し、保有していた流動性トークンを、ペアに対応する2つのトークンに交換する際に使用されます。
引数に関しては以下です。
tokenA | 流動性プールから引き出す最初のトークンのアドレス |
tokenB | 流動性プールから引き出す2番目のトークンのアドレス |
liquidity | 引き出す流動性トークンの量 |
amountAMin | 引き出す最小限のトークンAの量。これはスリッページ保護のために使用されます。 |
amountBMin | 引き出す最小限のトークンBの量。これもスリッページ保護のために使用されます |
to | 引き出されたトークンが送られるアドレス |
deadline | トランザクションの有効期限(UNIXタイムスタンプ) |
実際に関数の中身を見ていきましょう!
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
// ペアの流動性トークンをburnする
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
// tokenA,tokenBをソートして大きい順に並べ替える
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
// amountの並び替え
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
// 引き出す最小限のtokenAの量より大きか確認
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
// 引き出す最小限のtokenBの量より大きか確認
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
この関数は、指定された流動性トークンをburnして、プロバイダーに対応する2つのトークンを返します。スリッページ保護として、amountAMinとamountBMinで実際の引き出し額がこれらの値よりも低い場合取引は失敗します。これにより、予期しない価格変動による損失を防ぐことができます。
同じくETHに対応しているaddLiquidityETHも見てみましょう!
addLiquidityETH
この関数を使用することで、ユーザーはERC-20トークンとEtherを流動性プールにデポジットし、その対に対応する流動性プロバイダー(LP)トークンを受け取ります。LPトークンは、ユーザーがプールから流動性を引き出す際に使用されます
token | 引き出すERC20トークンのアドレス |
liquidity | 引き出す流動性トークンの量 |
amountTokenMin | 引き出すトークンの最小数量。スリッページを考慮に入れた値 |
amountETHMin | 引き出すEtherの最小数量。スリッページを考慮に入れた値 |
to | トークンおよびEtherを受け取るアドレス |
deadline | トランザクションの有効期限(UNIXタイムスタンプ) |
removeLiquidityETH
関数は、LPトークンをUniswap V2ペアコントラクトに送信し、トークンとEtherをユーザーに返還します。この関数は、内部で _removeLiquidity
関数を呼び出して、実際の流動性の引き出しロジックを処理します。また、この関数はWETH(Wrapped Ether)を使用して、EtherをERC-20トークンとして扱うため、IWETHインターフェースのwithdraw
関数を呼び出してWETHをEtherに変換します。
注意すべき点として、removeLiquidityETH
関数は、既存の流動性プールからのみ流動性を引き出すことができます。新しい流動性プールを作成したり、流動性を追加したりする場合は、addLiquidityETH
関数などの他の関数を使用する必要があります。
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
// removeLiquidityをそのまま使うでもETHを使えないので、wethを代わりに使い本来の処理の流れにしている
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
// 引き出すトークンを対象のアドレスへ送る
TransferHelper.safeTransfer(token, to, amountToken);
// WETH→ETHへ変換
IWETH(WETH).withdraw(amountETH);
// ETHを返却
TransferHelper.safeTransferETH(to, amountETH);
}
実際に処理はremoveLiquidityとそんなに変わらず、ETHをわかりに扱っているだけです。
_swap
次はswap関連の関数の説明なんですが、先に共通の_swap関数を見てみましょう。
この関数では全てのswap関連関数が使う共通の関数です。この関数は、Uniswap V2 Router02の内部関数_swap
であり、トレードを実行するためのロジックを含んでいます。
// **** 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 {
// swapするアドレスの配列をループで回す
for (uint i; i < path.length - 1; i++) {
// アドレスの1つ目と次の配列のアドレスをinputとoutputとして変数に入れる
(address input, address output) = (path[i], path[i + 1]);
// 入力と出力トークンをソートし、token0(より小さいアドレス)を取得します
(address token0,) = UniswapV2Library.sortTokens(input, output);
// 現在のペアでの出力金額を取得します
uint amountOut = amounts[i + 1];
// input == token0の条件に合えばinputにゼロのamount0OutでtokenにamountOutの値を入れたamount1Out
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
// 取引の受信者アドレスを設定します。最後のペアではない場合、次のペアのアドレスを設定し、最後のペアの場合、_toアドレスを使用します
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
// 現在のペアでのswap関数を呼び出し、トレードを実行します。
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
swapExactTokensForTokens
swapExactTokensForTokens
関数は、正確な量の入力トークンを使用して、最小量の出力トークンを購入するための関数です。この関数を使用することで、ユーザーは2つの異なるトークン間の取引を簡単に実行できます。
出力トークンの受信者アドレス
amountIn | 入力トークンの正確な量 |
amountOutMin | 受け取る出力トークンの最小量。これは、スリッページに対処するために使用されます。 |
path | 取引に使用されるトークンアドレスの配列。各連続したペアに対して、path内のトークンはペアの両方のトークンである必要があります |
to | 出力トークンの受け取るアドレス |
deadline | トランザクションの有効期限(UNIXタイムスタンプ) |
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
// swapするトークンの出力金額を予測し計算
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
// 予測の金額が最低出力額より大きいことを担保
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
// swap予定のトークンをペアcontractへ送金
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
// swap処理
_swap(amounts, path, to);
}
swapTokensForExactTokens
あるトークンを別のトークンに交換する際に、受け取りたい出力トークンの正確な量を指定して取引を行うことができます。この関数は、一連のトークンペアを経由して取引を行い、指定された量の出力トークンを得るために必要な入力トークンの量を計算し、実際の取引を実行します。
引数:
amountOut | 取引後に受け取りたい出力トークンの正確な量 |
amountInMax | 入力トークンの最大許容量(スリッページを考慮) |
path | 取引経路を表すアドレスの配列(例:[トークンA, トークンB, トークンC]) |
to | 出力トークンを受け取るアドレス |
deadline | トランザクションの有効期限(Unixタイムスタンプ) |
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');
// swap予定のトークンをペアcontractへ送金
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
// swap処理
_swap(amounts, path, to);
}
swapExactTokensForETH
swapExactTokensForETH
は、Uniswap V2 Router02 コントラクト内の関数で、指定された入力トークンの正確な量を ETH に交換(スワップ)することを目的としています。この関数は、複数のトークンペアを経由して取引ができるように設計されています。
引数:
amountIn | 入力トークンの正確な量 |
amountOutMin | 受け取る ETH の最小許容量(スリッページを考慮) |
path | 取引経路を表すアドレスの配列(例:[トークンA, トークンB, WETH]) |
to | 出力トークン(ETH)を受け取るアドレス |
deadline | トランザクションの有効期限(Unixタイムスタンプ) |
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
// pathの最後がWETHであることを確認
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
// 特定の出力トークン量を取得するために必要な入力トークン量を計算
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
// 入力されるトークン量が最大入力量でないことを確認
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
// swap予定のトークンをペアcontractへ送金
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
// swap処理
_swap(amounts, path, address(this));
// WETH -> ETHへ戻す
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
// ETHをtoへ送金する
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
swapExactETHForTokens
swapExactETHForTokens関数は、指定された量のETHを使って、パスに指定されたトークン間でスワップを行い、最終的に取得できるトークンを指定されたアドレスに送れます。
この関数を使用すると、ユーザーはETHを使ってパスに指定されたトークン間で連続的にスワップを行い、最終的に目的のトークンを獲得できます。これにより、ETHを他のトークンに簡単に交換することができます。また、スリッページを制御するためにamountOutMin
を設定し、取引が期限を過ぎる前に完了することを確認するためにdeadline
を使用します。
引数:
uint amountOutMin | スワップで取得する最低トークン量。これはスリッページを制御するために使用されます。 |
address[] calldata path | トークンの交換パス。最初の要素はETHである必要があり、最後の要素が目的のトークンです。 |
address to | 最終的に取得されるトークンが送信されるアドレスです。 |
uint deadline | トランザクションが有効な期限(UNIXタイムスタンプ)。期限を過ぎるとトランザクションは失敗します。 |
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
// pathの最初がWETHであることを確認
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
// swapするトークンの出力金額を予測し計算
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
// amountsの最後の金額が最小出力量より大きいことを確認
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
// ETH -> WETHへ変換します
IWETH(WETH).deposit{value: amounts[0]}();
// WETHがペアcontractへ送れていることを確認&送金
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
// swap処理
_swap(amounts, path, to);
}