🦄

[Bunzz Decipher] UniswapV2Migratorのコントラクトを理解しよう!

2023/08/14に公開

はじめに

初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

https://cryptospells.jp/

今回はBunzzの新機能『DeCipher』を使用して、UniswapV2の「UniswapV2Pair」のコントラクトを見てみようと思います。

DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。

https://www.bunzz.dev/decipher

詳しい使い方に関しては以下の記事を参考にしてください!

https://zenn.dev/heku/articles/33266f0c19d523

今回使用する『DeCipher』のリンクは以下になります。

https://app.bunzz.dev/decipher/chains/1/addresses/0x16d4f26c15f3658ec65b1126ff27dd3df2a2996b

概要

UniswapV2Migratorコントラクトは、Ethereumブロックチェーン上に構築された分散型取引所(DEX)であるUniswapプロトコルの重要なコンポーネントです。
このコントラクトは、UniswapV1からUniswapV2への流動性の移行をスムーズかつ効率的に行うために設計されています。

コントラクトの目的

UniswapV2Migratorコントラクトの主な目的は、流動性プロバイダーがUniswapV1からUniswapV2への流動性をシームレスかつ効率的に移行する手段を提供することです。
この移行は、Uniswapプロトコルのバージョン1からバージョン2へのアップグレードに伴い、いくつかの新機能と改善が導入されたため必要とされています。

移行プロセスは、V1取引所から流動性を取り出し、それをV2取引所に追加することを含みます。
これにより、流動性プロバイダーは流動性プールの同じ割合を保持し、プロセス中に資金を失うことはありません。

コントラクトの責任

UniswapV2Migratorコントラクトにはいくつかの重要な責任があります。

Uniswap V1およびV2との連携

このコントラクトは、UniswapプロトコルのV1およびV2の両方と連携します。
特定のトークンの取引所をV1ファクトリーから取得し、V2ルーターを使用してV2取引所に流動性を追加します。

流動性の移動

コントラクトは、V1取引所からV2取引所への流動性の移動を処理します。
これには、V1取引所から流動性を取り出し、それをV2取引所に追加する作業が含まれます。

安全な移行の確保

コントラクトは、移行プロセスが安全でセキュアであることを確認します。
V1取引所からの流動性の移動が成功したかどうかをチェックし、safeApprove関数を使用してV2ルーターにトークンの使用許可をセキュアに付与します。

ETHの処理

コントラクトには、ETHの受け取りを許可するreceive関数があります。
これは、移行プロセスがトークンをETHにスワップし、それをV2取引所に流動性を追加するために使用するため必要です。

まとめると、UniswapV2Migratorコントラクトは、UniswapプロトコルにおいてV1からV2への流動性の移行を円滑にサポートする重要な役割を果たしています。
これにより、移行プロセスが安全でセキュアであり、Uniswapエコシステムのインテグリティが維持されています。

使い方

UniswapV2Migratorコントラクトは、UniswapV1からUniswapV2への流動性の移行をサポートするために設計されました。
このコントラクトは、更新されたプロトコルに流動性を移動させたい流動性プロバイダーによって主に使用されます。

以下は、UniswapV2Migratorコントラクトを使用する手順です。

1. コントラクトのデプロイ

最初のステップは、UniswapV2MigratorコントラクトをEthereumネットワークにデプロイすることです。
これには、UniswapV1ファクトリーとUniswapV2ルーターのアドレスがコンストラクタ引数として必要です。

2. 移行の準備

migrate関数を呼び出す前に、流動性プロバイダーはUniswapV1取引所からの流動性トークンの移動をUniswapV2Migratorコントラクトに承認する必要があります。

3. Migrate関数の呼び出し

流動性プロバイダーは、migrate関数を呼び出すことができます。
この関数には、以下のパラメータが必要です。

  • token
    • 流動性ペアのトークンのアドレス。
  • amountTokenMin
    • 流動性プールに追加されるべきトークンの最小量。
  • amountETHMin
    • 流動性プールに追加されるべきETHの最小量。
  • to
    • 新しい流動性トークンが送られるアドレス。
  • deadline
    • 移行が完了する必要がある時刻。

4. 移行プロセス

migrate関数は、以下の手順を実行します。

  • 呼び出し元がUniswapV1取引所に持っている流動性の量を取得します。
  • この流動性を自身に送ります。
  • UniswapV1取引所から流動性を取り出し、その代わりに基礎となるトークンを受け取ります。
  • UniswapV2ルーターがこれらのトークンを使用することを承認します。
  • トークンとETHUniswapV2取引所に追加し、代わりに流動性トークンを受け取ります。

5. 流動性トークンの受け取り

移行プロセスが完了すると、新しい流動性トークンはmigrate関数の呼び出しで指定したtoアドレスに送られます。

注意点として、移行プロセスは複数のトランザクションを必要とし、ガス料金をカバーするために十分なETHが必要です。
また、移行プロセスは取り消すことができません。
一旦流動性がUniswapV2に移行されると、UniswapV1に戻すことはできません。

パラメータ

_factoryV1

UniswapV1ファクトリーコントラクトのアドレスです。
このアドレスは、UniswapV1の取引所を作成し管理するために使用されます。
UniswapV1ファクトリーコントラクトは、異なるトークン間の流動性プールを作成し、取引所をセットアップする際に使用される重要な役割を果たします。

_router

UniswapV2ルーターコントラクトのアドレスです。
このアドレスは、UniswapV2プラットフォーム上でのトークンのスワップや流動性の提供を実行するために使用されます。
UniswapV2ルーターコントラクトは、トークン間のスワップ取引を効率的に処理し、流動性を提供するためのトランザクションを実行する際に重要な機能を果たします。

関数

WRITE

migrate

UniswapV1からUniswapV2に流動性を移行する関数。

引数

  • token
    • 移行するトークンのアドレス。
  • amountTokenMin
    • 移行時にユーザーが受け取りたい最小量のトークン。
  • amountETHMin
    • 移行時にユーザーが受け取りたい最小量のETH。
  • to
    • 移行された流動性が送られるアドレス。
  • deadline
    • 移行操作が行われる期限。

この関数は、最初に指定されたトークンに対するUniswapV1取引所コントラクトをfactoryV1.getExchange関数を使用して取得します。
次に、関数は呼び出し元のUniswapV1取引所コントラクトの残高をチェックします。

その後、UniswapV1取引所コントラクトから呼び出し元の流動性を現在のコントラクトにtransferFrom関数を使用して移動します。

その後、removeLiquidity関数を使用してUniswapV1取引所コントラクトから流動性を取り出します。

safeApprove関数を使用してamountTokenV1トークンのUniswapV2ルーターコントラクトへの送金を承認します。

次に、関数はルーターコントラクトのaddLiquidityETH関数を使用してUniswapV2に流動性を追加します。
amountETHV1ETHが流動性の追加に使用されます。

もしamountTokenV1amountTokenV2よりも大きい場合、関数はsafeApprove関数を使用してトークンの承認を0にリセットし、safeTransfer関数を使用して余剰のトークンを呼び出し元に送金します。

もしamountETHV1amountETHV2よりも大きい場合、関数はsafeTransferETH関数を使用して余剰のETHを呼び出し元に送金します。

このように、ユーザーがUniswapV1からUniswapV2に流動性を移行し、UniswapV2が提供する機能と利点を活用するために使用されます。

イベント

特になし。

コード

IUniswapV2Migrator

IUniswapV2Migrator
pragma solidity =0.6.6;

interface IUniswapV2Migrator {
    function migrate(address token, uint amountTokenMin, uint amountETHMin, address to, uint deadline) external;
}

IUniswapV1Factory

IUniswapV1Factory
interface IUniswapV1Factory {
    function getExchange(address) external view returns (address);
}

IUniswapV1Exchange

IUniswapV1Exchange
interface IUniswapV1Exchange {
    function balanceOf(address owner) external view returns (uint);
    function transferFrom(address from, address to, uint value) external returns (bool);
    function removeLiquidity(uint, uint, uint, uint) external returns (uint, uint);
    function tokenToEthSwapInput(uint, uint, uint) external returns (uint);
    function ethToTokenSwapInput(uint, uint) external payable returns (uint);
}

IUniswapV2Router01

IUniswapV2Router01
interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint 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 returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

IERC20

IERC20
interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

UniswapV2Migrator

UniswapV2Migrator
contract UniswapV2Migrator is IUniswapV2Migrator {
    IUniswapV1Factory immutable factoryV1;
    IUniswapV2Router01 immutable router;

    constructor(address _factoryV1, address _router) public {
        factoryV1 = IUniswapV1Factory(_factoryV1);
        router = IUniswapV2Router01(_router);
    }

    // needs to accept ETH from any v1 exchange and the router. ideally this could be enforced, as in the router,
    // but it's not possible because it requires a call to the v1 factory, which takes too much gas
    receive() external payable {}

    function migrate(address token, uint amountTokenMin, uint amountETHMin, address to, uint deadline)
        external
        override
    {
        IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(token));
        uint liquidityV1 = exchangeV1.balanceOf(msg.sender);
        require(exchangeV1.transferFrom(msg.sender, address(this), liquidityV1), 'TRANSFER_FROM_FAILED');
        (uint amountETHV1, uint amountTokenV1) = exchangeV1.removeLiquidity(liquidityV1, 1, 1, uint(-1));
        TransferHelper.safeApprove(token, address(router), amountTokenV1);
        (uint amountTokenV2, uint amountETHV2,) = router.addLiquidityETH{value: amountETHV1}(
            token,
            amountTokenV1,
            amountTokenMin,
            amountETHMin,
            to,
            deadline
        );
        if (amountTokenV1 > amountTokenV2) {
            TransferHelper.safeApprove(token, address(router), 0); // be a good blockchain citizen, reset allowance to 0
            TransferHelper.safeTransfer(token, msg.sender, amountTokenV1 - amountTokenV2);
        } else if (amountETHV1 > amountETHV2) {
            // addLiquidityETH guarantees that all of amountETHV1 or amountTokenV1 will be used, hence this else is safe
            TransferHelper.safeTransferETH(msg.sender, amountETHV1 - amountETHV2);
        }
    }
}

TransferHelper

TransferHelper
pragma solidity =0.6.6;

interface IUniswapV2Migrator {
    function migrate(address token, uint amountTokenMin, uint amountETHMin, address to, uint deadline) external;
}

interface IUniswapV1Factory {
    function getExchange(address) external view returns (address);
}

interface IUniswapV1Exchange {
    function balanceOf(address owner) external view returns (uint);
    function transferFrom(address from, address to, uint value) external returns (bool);
    function removeLiquidity(uint, uint, uint, uint) external returns (uint, uint);
    function tokenToEthSwapInput(uint, uint, uint) external returns (uint);
    function ethToTokenSwapInput(uint, uint) external payable returns (uint);
}

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint 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 returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

contract UniswapV2Migrator is IUniswapV2Migrator {
    IUniswapV1Factory immutable factoryV1;
    IUniswapV2Router01 immutable router;

    constructor(address _factoryV1, address _router) public {
        factoryV1 = IUniswapV1Factory(_factoryV1);
        router = IUniswapV2Router01(_router);
    }

    // needs to accept ETH from any v1 exchange and the router. ideally this could be enforced, as in the router,
    // but it's not possible because it requires a call to the v1 factory, which takes too much gas
    receive() external payable {}

    function migrate(address token, uint amountTokenMin, uint amountETHMin, address to, uint deadline)
        external
        override
    {
        IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(token));
        uint liquidityV1 = exchangeV1.balanceOf(msg.sender);
        require(exchangeV1.transferFrom(msg.sender, address(this), liquidityV1), 'TRANSFER_FROM_FAILED');
        (uint amountETHV1, uint amountTokenV1) = exchangeV1.removeLiquidity(liquidityV1, 1, 1, uint(-1));
        TransferHelper.safeApprove(token, address(router), amountTokenV1);
        (uint amountTokenV2, uint amountETHV2,) = router.addLiquidityETH{value: amountETHV1}(
            token,
            amountTokenV1,
            amountTokenMin,
            amountETHMin,
            to,
            deadline
        );
        if (amountTokenV1 > amountTokenV2) {
            TransferHelper.safeApprove(token, address(router), 0); // be a good blockchain citizen, reset allowance to 0
            TransferHelper.safeTransfer(token, msg.sender, amountTokenV1 - amountTokenV2);
        } else if (amountETHV1 > amountETHV2) {
            // addLiquidityETH guarantees that all of amountETHV1 or amountTokenV1 will be used, hence this else is safe
            TransferHelper.safeTransferETH(msg.sender, amountETHV1 - amountETHV2);
        }
    }
}

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
    }

    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
    }

    function safeTransferETH(address to, uint value) internal {
        (bool success,) = to.call{value:value}(new bytes(0));
        require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
    }
}

最後に

今回の記事では、Bunzzの新機能『DeCipher』を使用して、UniswapV2の「UniswapV2Migrator」のコントラクトを見てきました。
いかがだったでしょうか?
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。

普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!

https://chaldene.net/

https://qiita.com/cardene

DeCipher |"Read me" for All of Contracts

Discussion