はじめに
初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回はBunzzの新機能『DeCipher』を使用して、UniswapV3の「V3Migrator」のコントラクトを見てみようと思います。
『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。
詳しい使い方に関しては以下の記事を参考にしてください!
この記事はあくまで個人が「DeCipher」を使用してみての記事になります。 間違った部分などもあるかと思いますが、その際はコメントなどで教えてください。
今回使用する『DeCipher』のリンクは以下になります。
Etherscanのリンクは以下になります。
概要
Uniswap V3プロトコルの一部であるV3Migratorコントラクトは、、ユーザーが以前のUniswap V2の流動性を新しいUniswap V3に移すのを支援する役割を果たしています。
Uniswap V1からUniswap V2への流動性の移行については以下にまとめられています。
主な役割としては以下の点が挙げられます。
- Uniswap V3の中核となるコントラクトと連携し、古いUniswap V2から新しいUniswap V3への流動性の移行を処理します。 これにより、ユーザーは新しいプロトコルを利用しながら、以前の流動性を活用できます。
- LowGasSafeMathライブラリを活用して、数学的な計算を効率的かつガス消費の少ない方法で行い、コストを抑えつつ安全な操作が実現されます。
- Uniswap V2のペアコントラクトと連携し、Uniswap V2のプロトコルから流動性情報を取得し、新しいプロトコルに移すための処理を行います。
- NonfungiblePositionManagerコントラクトと連携して、移行中の流動性ポジションを管理・操作します。
- TransferHelperライブラリを使用して、トークンの転送を安全に行い、トークン移動に関するリスクを最小限に抑えます。
- IV3Migratorインターフェースを実装することで、他のコントラクトとの互換性と標準化が確保されます。 これにより、異なるコントラクト間での連携がスムーズに行えます。
- さまざまなベースコントラクトからの機能を取り入れることで、V3Migratorコントラクトの機能性とセキュリティが向上し、安全かつ強力な機能が提供されます。
- 外部コントラクトと連携し、異なるトークン規格に対応するためのインターフェースを提供し、さまざまなトークンを移行プロセスで扱えるようになります。
使い方
V3Migratorコントラクトは、Uniswap V2からUniswap V3への流動性の移行をサポートするスマートコントラクトです。 同時に、移行された流動性の管理や操作にも関する便利な機能を提供します。 これにより、流動性プロバイダーは、Uniswap V3が提供する集中型の流動性や複数の手数料レベルといった強化された特徴を活用できます。
手順
具体的な手順は以下の通りです。
- 流動性移行を管理するためのスマートコントラクトであるV3Migratorコントラクトをデプロイします。
- コントラクトを必要なパラメータで初期化します。
- Uniswap V2のプールから流動性トークンをV3Migratorコントラクトが使用できるように承認し、トークンの移行を可能にします。
- V3Migratorコントラクト上で移行関数を呼び出し、流動性移行が開始されます。
- 移行プロセスを監視し、イベントやエラーを処理しながら、移行がスムーズに進行することを確認します。
- 移行された流動性をUniswap V3のプールで操作するために、提供された関数やイベントを使用し、新しいプールでの流動性の管理が可能になります。
関数呼び出し
Uniswap V2からUniswap V3への流動性移行には、以下の関数を呼び出す必要があります。
constructor
V3Migratorコントラクトを必要なパラメータで初期化します。
approve
Uniswap V2プールから、V3Migratorコントラクトが指定した量の流動性トークンを使用できるように承認し、流動性を移行する準備を整えます。
migrate
Uniswap V2からUniswap V3へ指定された流動性を移行します。
使い方
Uniswap V2からUniswap V3への流動性の移行。
- V3Migratorコントラクトをデプロイします。
- コントラクトを必要なパラメータで初期化します。
- Uniswap V2のプールから、移行する流動性トークンの指定された量をV3Migratorコントラクトが使用できるように承認します。
- V3Migratorコントラクト上で
migrate関数を呼び出し、移行に必要なパラメータを提供し、Uniswap V2からV3への流動性移行が実行されます。
Uniswap V3で移行された流動性とのやり取り。
- 提供された関数を使用して、Uniswap V3プール内の移行された流動性とやり取りし、新しいプール内での流動性の操作を可能にします。
- V3Migratorコントラクトが発行するイベントを監視し、移行された流動性に関するアップデートや変更に適切に対処します。
関連EIP/ERC
- ERC20: ファンジブルトークンの標準インターフェースを定義。
- ERC721: ノンファンジブルトークンの標準インターフェースを定義。
- ERC165: コントラクトのインスペクションのための標準インターフェースを定義。
- ERC721Metadata: 追加のメタデータ関数を持つERC721インターフェースを拡張。
- ERC721Enumerable: 追加の列挙関数を持つERC721インターフェースを拡張。
- ERC20Permit: パーミット機能を持つERC20トークンの標準インターフェースを定義。
- IUniswapV2Pair: Uniswap V2ペアコントラクトのインターフェース。
- INonfungiblePositionManager: Uniswap V3非ファンジブルポジションマネージャーコントラクトのインターフェース。
- IUniswapV3Factory: Uniswap V3ファクトリーコントラクトのインターフェース。
- IUniswapV3Pool: Uniswap V3プールコントラクトのインターフェース。
- IUniswapV3PoolImmutables: Uniswap V3プールの不変データのインターフェース。
- IUniswapV3PoolState: Uniswap V3プールの状態のインターフェース。
- IUniswapV3PoolDerivedState: Uniswap V3プールの派生状態のインターフェース。
- IUniswapV3PoolActions: **Uniswap **V3プールのアクションのインターフェース。
- IUniswapV3PoolOwnerActions: Uniswap V3プールのオーナーアクションのインターフェース。
- IUniswapV3PoolEvents: Uniswap V3プールのイベントのインターフェース。
パラメーター
_factory
Uniswap V3のファクトリーコントラクトのアドレス。 このコントラクトはUniswap V3のプールを生成する役割を担っています。 つまり、新しいトレーディングペアのプールを作成する際にこのアドレスを参照します。
_WETH9
WETH9コントラクトのアドレスを指します。 WETH(Wrapped Ether) は、EthereumのネイティブトークンであるEther(ETH) をEthereumのスマートコントラクトでトレードや操作するためにラップしたものです。 このアドレスはWETH9トークンの操作に使用されます。
_nonfungiblePositionManager
非ファンジブルポジションマネージャーコントラクトのアドレス。 Uniswap V3の非ファンジブルポジションマネージャーコントラクトは、ユーザーがUniswap V3のポジションを管理し、移動するための機能を提供します。 このアドレスはそのコントラクトの操作に使用されます。
コントラクト
V3Migrator
nonfungiblePositionManager
nonfungiblePositionManager
address public immutable nonfungiblePositionManager;概要 非ファンジブルポジションマネージャーコントラクトのアドレス。
詳細
Uniswap V3の非ファンジブルポジションマネージャーコントラクトのアドレスを格納します。
非ファンジブルポジションマネージャーコントラクトは、Uniswap V3の流動性ポジションを管理するための機能を提供します。
immutableが使用されているため、コントラクトのデプロイ後に値を変更することはできません。
receive
receive
receive() external payable {
require(msg.sender == WETH9, 'Not WETH9');
}概要 コントラクトがETHを受け取る際に呼び出される関数。
詳細 ETHがコントラクトに送信される際に自動的に呼び出される関数。 関数内で、送信者がWETH9コントラクトであることを確認し、それ以外のアドレスからのETH送信を拒否します。
migrate
migrate
function migrate(MigrateParams calldata params) external override {
require(params.percentageToMigrate > 0, 'Percentage too small');
require(params.percentageToMigrate <= 100, 'Percentage too large');
// burn v2 liquidity to this address
IUniswapV2Pair(params.pair).transferFrom(msg.sender, params.pair, params.liquidityToMigrate);
(uint256 amount0V2, uint256 amount1V2) = IUniswapV2Pair(params.pair).burn(address(this));
// calculate the amounts to migrate to v3
uint256 amount0V2ToMigrate = amount0V2.mul(params.percentageToMigrate) / 100;
uint256 amount1V2ToMigrate = amount1V2.mul(params.percentageToMigrate) / 100;
// approve the position manager up to the maximum token amounts
TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, amount0V2ToMigrate);
TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, amount1V2ToMigrate);
// mint v3 position
(, , uint256 amount0V3, uint256 amount1V3) =
INonfungiblePositionManager(nonfungiblePositionManager).mint(
INonfungiblePositionManager.MintParams({
token0: params.token0,
token1: params.token1,
fee: params.fee,
tickLower: params.tickLower,
tickUpper: params.tickUpper,
amount0Desired: amount0V2ToMigrate,
amount1Desired: amount1V2ToMigrate,
amount0Min: params.amount0Min,
amount1Min: params.amount1Min,
recipient: params.recipient,
deadline: params.deadline
})
);
// if necessary, clear allowance and refund dust
if (amount0V3 < amount0V2) {
if (amount0V3 < amount0V2ToMigrate) {
TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, 0);
}
uint256 refund0 = amount0V2 - amount0V3;
if (params.refundAsETH && params.token0 == WETH9) {
IWETH9(WETH9).withdraw(refund0);
TransferHelper.safeTransferETH(msg.sender, refund0);
} else {
TransferHelper.safeTransfer(params.token0, msg.sender, refund0);
}
}
if (amount1V3 < amount1V2) {
if (amount1V3 < amount1V2ToMigrate) {
TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, 0);
}
uint256 refund1 = amount1V2 - amount1V3;
if (params.refundAsETH && params.token1 == WETH9) {
IWETH9(WETH9).withdraw(refund1);
TransferHelper.safeTransferETH(msg.sender, refund1);
} else {
TransferHelper.safeTransfer(params.token1, msg.sender, refund1);
}
}
}概要 Uniswap V2からUniswap V3への流動性移行を実行する関数。
詳細 指定されたパラメータに基づいてUniswap V2からUniswap V3への流動性移行プロセスを実行します。 移行に必要なパラメータを受け取り、移行の実行前に一定のバリデーションを行います。 移行が完了すると、Uniswap V3の流動性が生成され、必要に応じてアセットの払い戻しも行われます。
Multicall
multicall
multicall
function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
results[i] = result;
}
}概要 複数のコントラクト関数を一括して呼び出すためのメカニズムを提供する関数。 指定されたデータの配列を使用して、デリゲートコールを行い、複数の関数呼び出しの結果を返します。
詳細
この関数は、複数のコントラクト関数を一括して呼び出すことができる便利な機能を提供します。data配列には、呼び出す関数のデータ(コールデータ)が含まれています。
関数呼び出しの結果は、results配列に格納されて返されます。
delegatecallを使用して関数を呼び出し、その結果と成功ステータスを取得します。
引数
data- 関数呼び出しのためのコールデータの配列。
戻り値
results- 各関数呼び出しの結果が格納されたバイトデータの配列。
SelfPermit
selfPermit
selfPermit
function selfPermit(
address token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public payable override {
IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s);
}概要 トークンの所有者がSelfPermitコントラクトに対してトークンの送付許可を与えるメカニズムを提供する関数。
詳細 トークンの所有者が署名したメッセージを使用して、トークンの送付許可をSelfPermitコントラクトに与えるために使用されます。
引数
token- トークンのアドレス。
value- 許可するトークンの数量。
deadline- 署名の有効期限。
v,r,s- 署名のパラメータ。
selfPermitIfNecessary
selfPermitIfNecessary
function selfPermitIfNecessary(
address token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external payable override {
if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s);
}概要 必要な場合にのみトークンの許可をSelfPermitコントラクトに与えるメカニズムを提供する関数。
詳細
トークンの許可が要求された数量を満たしていない場合にのみ、selfPermit関数を呼び出してトークンの送付許可を与えるために使用されます。
トークンの許可をチェックし、要求された数量を満たしていない場合にはselfPermit関数を呼び出します。
引数
token- トークンのアドレス。
value- 許可するトークンの数量。
deadline- 署名の有効期限。
v,r,s- 署名のパラメータ。
selfPermitAllowed
selfPermitAllowed
function selfPermitAllowed(
address token,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) public payable override {
IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s);
}概要 トークンの所有者が送付許可をSelfPermitコントラクトに与えるための拡張メカニズムを提供する関数。
詳細 トークン所有者は、署名を使用して関数を呼び出すことによって、指定された条件でトークンの許可をコントラクトに与えることができます。
引数
token- トークンのアドレス。
nonce- トランザクションのユニーク性を確保するための数値。
expiry- 署名の有効期限。
v,r,s- 署名のパラメータ。
selfPermitAllowedIfNecessary
selfPermitAllowedIfNecessary
function selfPermitAllowedIfNecessary(
address token,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) external payable override {
if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max)
selfPermitAllowed(token, nonce, expiry, v, r, s);
}概要 必要な場合にのみトークンの送付許可をSelfPermitコントラクトに与える拡張メカニズムを提供する関数。
詳細
トークンの許可がすでに最大値を超えていない場合にのみ、selfPermitAllowed関数を呼び出してトークンの許可を与えるために使用されます。
トークンの許可をチェックし、最大値を超えていない場合にはselfPermitAllowed関数を呼び出します。
引数
token- トークンのアドレス。
nonce- トランザクションのユニーク性を確保するための数値。
expiry- 署名の有効期限。
v,r,s- 署名のパラメータ。
PoolInitializer
createAndInitializePoolIfNecessary
createAndInitializePoolIfNecessary
function createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external payable override returns (address pool) {
require(token0 < token1);
pool = IUniswapV3Factory(factory).getPool(token0, token1, fee);
if (pool == address(0)) {
pool = IUniswapV3Factory(factory).createPool(token0, token1, fee);
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
} else {
(uint160 sqrtPriceX96Existing, , , , , , ) = IUniswapV3Pool(pool).slot0();
if (sqrtPriceX96Existing == 0) {
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
}
}
}概要 必要に応じてUniswap V3プールを作成および初期化するためのメカニズムを提供する関数。
詳細 この関数は、指定されたトークンと手数料率に対応するUniswap V3プールが存在するかどうかをチェックし、存在しない場合は新しいプールを作成して初期化します。 既に存在するプールがある場合は、そのプールのスロット0情報を取得し、初期化が行われているかを確認します。 初期化が行われていない場合は、プールを初期化します。
引数
token0- トークン0のアドレス。
token1- トークン1のアドレス。
fee- 手数料率。
sqrtPriceX96- 初期の平方根プライス。
戻り値
pool- 作成または初期化されたUniswap V3プールのアドレス。
sqrtPriceX96 は、Uniswap V3プール内の価格情報を表す重要な値です。
Uniswap V3では、価格をsqrtPriceX96という形で表現しています。
sqrtPriceX96とは何か?
sqrtPriceX96は、Uniswap V3プール内の価格情報を表す数値です。
具体的には、トークン0とトークン1の間の取引ペアの価格を表しています。
この値は、X * Y = Kという定数乗算価格関数を使用して計算されます。
ここで、XとYはトークン0とトークン1の残高であり、Kは定数です。
sqrtPriceX96は、価格を平方根として表現したものです。
これは、価格が線形ではなく、価格帯ごとに定義されるUniswap V3の特性に適しています。
sqrtPriceX96の意味と使い方
sqrtPriceX96の値が高いほど、トークン0の価格がトークン1の価格に比べて高くなります。sqrtPriceX96の値が低いほど、トークン0の価格がトークン1の価格に比べて低くなります。- プール内の価格変動は、
sqrtPriceX96の変動によって示されます。 - 価格帯が変わると、
sqrtPriceX96も変化します。
sqrtPriceX96はUniswap V3プールの価格情報を表す数値であり、価格変動のトラッキングに使用されます。
プール内での取引価格を理解し、効果的な取引を行うために重要な指標となります。
TransferHelper
safeTransferFrom
safeTransferFrom
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
}概要
指定されたトークンをfromアドレスからtoアドレスに安全に転送する関数。
詳細
この関数では、tokenアドレスのトークンコントラクトに対してIERC20.transferFrom関数を呼び出してトークンを転送しています。
転送が成功したかどうかはsuccess変数で確認し、転送が正常に行われなかった場合には、STFエラーが発生します。
引数
token- トークンのコントラクトアドレス。
from- トークンの転送元アドレス。
to- トークンの転送先アドレス。
value- 転送するトークンの量。
safeTransfer
safeTransfer
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
}概要
msg.senderから指定されたアドレスtoにトークンを安全に転送する関数。
詳細
tokenアドレスのトークンコントラクトに対してIERC20.transfer関数を呼び出してトークンを転送しています。
転送が成功したかどうかは success変数で確認し、転送が正常に行われなかった場合には、STエラーが発生します。
引数
token- トークンのコントラクトアドレス。
to- トークンの転送先アドレス。
value- 転送するトークンの量。
safeApprove
safeApprove
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
}概要
指定されたアドレスtoに対してトークンの承認を安全に行う関数。
詳細
この関数では、tokenアドレスのトークンコントラクトに対してIERC20.approve関数を呼び出して承認を行っています。
承認が成功したかどうかはsuccess変数で確認し、承認が正常に行われなかった場合には、SAエラーが発生します。
引数 or パラメータ
token- トークンのコントラクトアドレス。
to- 承認対象のアドレス。
value- 承認するトークンの量。
safeTransferETH
safeTransferETH
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'STE');
}概要
指定されたアドレスtoにETHを安全に転送する関数。
詳細
この関数では、toアドレスに対して指定した量のETHを転送しています。
転送が成功したかどうかはsuccess変数で確認し、転送が正常に行われなかった場合には、STEエラーが発生します。
引数
to- ETHの転送先アドレス。
value- 転送するETHの量。
PoolAddress
POOL_INIT_CODE_HASH
POOL_INIT_CODE_HASH
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;概要 はプールのイニシャライズコードのハッシュ値を表す定数。
PoolKey
PoolKey
struct PoolKey {
address token0;
address token1;
uint24 fee;
}概要 プールの識別キーを表す構造体。 この構造体はプールのトークンと手数料レベルを格納します。
getPoolKey
getPoolKey
function getPoolKey(
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (PoolKey memory) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
}概要 プールキーを取得する関数。 与えられたトークンと手数料レベルを元に、トークンを昇順に並べたプールキーを生成します。
引数
tokenA- プールの最初のトークン。
tokenB- プールの2番目のトークン。
fee- プールの手数料レベル。
戻り値
PoolKeyPoolKey構造体。- 昇順に並べられたトークンと手数料レベルが含まれます。
computeAddress
computeAddress
function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) {
require(key.token0 < key.token1);
pool = address(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
)
);
}概要 プールのアドレスを計算する関数。 ファクトリーのアドレスとプールキーを元に、プールのアドレスを決定的に計算します。
引数
factory- Uniswap V3ファクトリーコントラクトのアドレス。
key- プールキー。
戻り値 プールのアドレスを返します。
イベント
なし。
コード
インターフェース
contracts/interfaces
ライブラリ
contracts/libraries
ベースコントラクト
contracts/base
最後に
今回の記事では、Bunzzの新機能『DeCipher』を使用して、UniswapV3の「V3Migrator」のコントラクトを見てきました。 いかがだったでしょうか? 今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。
普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!



