[Bunzz Decipher] UniswapV3Factoryコントラクトを理解しよう!
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回はBunzzの新機能『DeCipher』を使用して、UniswapV3の「UniswapV3Factory」のコントラクトを見てみようと思います。
『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。
詳しい使い方に関しては以下の記事を参考にしてください!
今回使用する『DeCipher』のリンクは以下になります。
Etherscanのリンクは以下になります。
概要
UniswapV3Factoryコントラクトは、Uniswap V3プロトコルの中で重要な機能です。
このコントラクトは、ユーザーが流動性プールを作成し、それに参加するためのプラットフォームを提供しています。
このコントラクトの主な目的は、ユーザーが異なるトークン同士の取引を行ったり、流動性(資金)を提供したりできるようにすることです。
例えば、ユーザーがトークンAとトークンBの取引を行いたい場合、このコントラクトを通じてそれらのトークンの流動性プールを作成し、取引に参加することができます。
UniswapV3Factoryコントラクトの主な役割は以下になります。
プールの展開
新しい流動性プールを作成する際、このコントラクトは必要な設定やパラメータを適切に初期化してプールを展開します。
例えば、取引手数料の設定やトークンのペアなどがこれに該当します。
プールの管理
既存のプールの設定や状態を管理します。
ユーザーが手数料の変更やプール情報の取得を行えるようにする役割を果たします。
プールとのやり取り
ユーザーがプールとやり取りできるように、取引や流動性の提供、取り出しを容易にします。
これにより、ユーザーは取引を行い、流動性を提供することができます。
セキュリティ
ユーザーの資金を守るため、不正行為を防ぐためのセキュリティ対策を実装しています。
認証されたユーザー以外がプールの設定変更などを行えないようにアクセス制御が行われます。
また、数学的な計算に関するセキュリティも考慮されています。
使い方
概要
UniswapV3Factoryコントラクトは、Uniswap V3プールの作成と管理を行うスマートコントラクトです。
このコントラクトを使用することで、新しいプールの作成や既存のプールに関する情報の取得などの様々な操作を行うことができます。
コントラクトの目標
UniswapV3Factoryコントラクトの目標は、Uniswap V3プールの作成と管理のための分散型プラットフォームを提供することです。
ユーザーはトークンのペアや手数料ティアなどのパラメータをカスタマイズして新しいプールを作成し、作成されたプールと対話するための関数を利用することができます。
目標達成の手順
Uniswap V3プールの作成と管理の目標を達成するためには、次の手順が必要です。
- UniswapV3Factoryコントラクトを展開する。
-
createPool
関数を呼び出して、指定したパラメータで新しいプールを作成する。 - UniswapV3Factoryコントラクトが提供するさまざまな関数を使用して、作成されたプールとやり取りを行う。
このようにして、UniswapV3Factoryコントラクトを使用してユーザーは新しいプールの作成や管理を行うことができ、Uniswap V3プロトコルの恩恵を受けることができます。
使い方
新しいプールの作成
新しいプールを作成するには、以下の手順に従ってください。
- UniswapV3Factoryコントラクトを展開します。
-
createPool
関数を以下のパラメータで呼び出します。
-
tokenA
- トークンペアの最初のトークンのアドレス。
-
tokenB
- トークンペアの2番目のトークンのアドレス。
-
fee
- プールの手数料ティア。
-
sqrtPriceX96
- トークンペアの初期平方根価格。
createPool
関数は新しいUniswapV3Poolコントラクトをデプロイし、そのアドレスを返します。
- トークンペアの初期平方根価格。
プール情報の取得
既存のプールに関する情報を取得するには、以下の関数を使用します。
-
getPool
- 特定のトークンペアと手数料ティアに対するUniswapV3Poolコントラクトのアドレスを返します。
-
allPools
- ファクトリによって作成されたすべてのプールアドレスの配列を返します。
プールとのやり取り
プールが作成されると、UniswapV3Poolコントラクトが提供する関数を使用してプールと対話できます。
主な関数には次のものがあります。
-
swap
- プール内でトークンをスワップします。
-
mint
- プール内で新しい流動性を追加します。
-
burn
- プールから流動性を削減します。
-
collect
- プールから手数料を収集します。
このようにして、UniswapV3Factoryコントラクトを使用して新しいプールを作成し、既存のプールに関する情報を取得し、プールとやり取りすることができます。
関連EIP/ERC
UniswapV3Factoryコントラクトは以下のEIP/ERCに関連しています。
- ERC-173: Ownership Standard
- ERC-721: Non-Fungible Token Standard
- ERC-1155: Multi-Token Standard
- ERC-20: Token Standard
- ERC-721: Non-Fungible Token Standard
UniswapV3Factoryコントラクトとそれに関連するコントラクト、ライブラリの詳細については、それぞれのソースコードファイルとドキュメントを参照してください。
パラメータ
特になし。
コントラクト
UniswapV3Factory
UniswapV3Factory
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;
import './interfaces/IUniswapV3Factory.sol';
import './UniswapV3PoolDeployer.sol';
import './NoDelegateCall.sol';
import './UniswapV3Pool.sol';
/// @title Canonical Uniswap V3 factory
/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees
contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall {
/// @inheritdoc IUniswapV3Factory
address public override owner;
/// @inheritdoc IUniswapV3Factory
mapping(uint24 => int24) public override feeAmountTickSpacing;
/// @inheritdoc IUniswapV3Factory
mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;
constructor() {
owner = msg.sender;
emit OwnerChanged(address(0), msg.sender);
feeAmountTickSpacing[500] = 10;
emit FeeAmountEnabled(500, 10);
feeAmountTickSpacing[3000] = 60;
emit FeeAmountEnabled(3000, 60);
feeAmountTickSpacing[10000] = 200;
emit FeeAmountEnabled(10000, 200);
}
/// @inheritdoc IUniswapV3Factory
function createPool(
address tokenA,
address tokenB,
uint24 fee
) external override noDelegateCall returns (address pool) {
require(tokenA != tokenB);
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0));
int24 tickSpacing = feeAmountTickSpacing[fee];
require(tickSpacing != 0);
require(getPool[token0][token1][fee] == address(0));
pool = deploy(address(this), token0, token1, fee, tickSpacing);
getPool[token0][token1][fee] = pool;
// populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses
getPool[token1][token0][fee] = pool;
emit PoolCreated(token0, token1, fee, tickSpacing, pool);
}
/// @inheritdoc IUniswapV3Factory
function setOwner(address _owner) external override {
require(msg.sender == owner);
emit OwnerChanged(owner, _owner);
owner = _owner;
}
/// @inheritdoc IUniswapV3Factory
function enableFeeAmount(uint24 fee, int24 tickSpacing) public override {
require(msg.sender == owner);
require(fee < 1000000);
// tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that
// TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick
// 16384 ticks represents a >5x price change with ticks of 1 bips
require(tickSpacing > 0 && tickSpacing < 16384);
require(feeAmountTickSpacing[fee] == 0);
feeAmountTickSpacing[fee] = tickSpacing;
emit FeeAmountEnabled(fee, tickSpacing);
}
}
owner
概要
UniswapV3Factoryコントラクトのオーナーアドレスを保持する変数。
詳細
オーナーは新しいプールの作成や設定変更などを制御する権限を持ちます。
feeAmountTickSpacing
概要
手数料ティアに対するtickスペーシング(価格間隔)を保持するマッピング配列。
詳細
このマッピングは、各手数料ティアに対して設定されたtickスペーシング(価格間隔)を格納します。tickスペーシングは、価格の変動を制御し、トレードの精度を調整します。
引数
-
fee
- 手数料ティア(手数料率)。
戻り値
-
int24
- 指定された手数料ティアに対するtickスペーシング。
getPool
概要
トークンペアと手数料ティアに対するUniswapV3Poolコントラクトのアドレスを保持する3次元マッピング配列。
詳細
この3次元マッピングは、特定のトークンペアと手数料ティアに対して、対応するUniswapV3Poolコントラクトのアドレスを格納します。
これにより、特定のトークンの組み合わせと手数料ティアに対するプールのアドレスを簡単に取得できます。
引数
-
tokenA
- トークンペアの1つ目のトークンのアドレス。
-
tokenB
- トークンペアの2つ目のトークンのアドレス。
-
fee
- 手数料ティア。
戻り値
-
address
- 指定されたトークンペアと手数料ティアに対するUniswapV3Poolコントラクトのアドレス。
createPool
概要
新しいプールを作成する関数。
引数
-
tokenA
- トークンペアの1つ目のトークンのアドレス。
-
tokenB
- トークンペアの2つ目のトークンのアドレス。
-
fee
- 手数料ティア。
戻り値
-
address
- 作成されたUniswap V3プールのアドレス。
setOwner
概要
オーナーアドレスを設定する関数。
詳細
コントラクトのオーナーを設定します。
オーナーは特定の操作を行う権限を持ちます。
ただし、オーナー自身のみがオーナーを変更できます。
引数
-
_owner
- 新しいオーナーアドレス。
戻り値
なし
enableFeeAmount
概要
手数料ティアに対するtickスペーシングを設定する関数。
詳細
手数料ティアに対してtickスペーシング(価格間隔)を設定します。
オーナーのみがこの関数を使用でき、設定されたtickスペーシングによって価格変動の制御が行われます。
引数
-
fee
- 手数料ティア。
-
tickSpacing
- tickスペーシング(価格間隔)。
戻り値
なし
UniswapV3PoolDeployer
UniswapV3PoolDeployer
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;
import './interfaces/IUniswapV3PoolDeployer.sol';
import './UniswapV3Pool.sol';
contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer {
struct Parameters {
address factory;
address token0;
address token1;
uint24 fee;
int24 tickSpacing;
}
/// @inheritdoc IUniswapV3PoolDeployer
Parameters public override parameters;
/// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then
/// clearing it after deploying the pool.
/// @param factory The contract address of the Uniswap V3 factory
/// @param token0 The first token of the pool by address sort order
/// @param token1 The second token of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The spacing between usable ticks
function deploy(
address factory,
address token0,
address token1,
uint24 fee,
int24 tickSpacing
) internal returns (address pool) {
parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing});
pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());
delete parameters;
}
}
Parameters
概要
プールのデプロイに関するパラメータを保持する構造体。
パラメータ
-
factory
- Uniswap V3ファクトリのコントラクトアドレス。
-
token0
- トークンペアの1つ目のトークンのアドレス。
-
token1
- トークンペアの2つ目のトークンのアドレス。
-
fee
- 手数料ティア。
-
tickSpacing
- 価格間隔(tickスペーシング)。
戻り値
なし
parameters
概要
プールのデプロイに関するパラメータを保持する変数。
詳細
Parameters
という構造体のインスタンスを格納するため、プールのデプロイに関する情報が含まれます。
戻り値
-
Parameters
- プールのデプロイに関するパラメータが含まれる構造体のインスタンス。
deploy
概要
プールをデプロイするための関数。
詳細
指定されたパラメータを使用してUniswap V3プールをデプロイします。
デプロイ時には、パラメータをstorage slotに一時的に設定し、プールのデプロイ後に削除します。
引数
-
factory
- Uniswap V3ファクトリのコントラクトアドレス。
-
token0
- トークンペアの1つ目のトークンのアドレス。
-
token1
- トークンペアの2つ目のトークンのアドレス。
-
fee
- 手数料ティア。
-
tickSpacing
- 価格間隔(tickスペーシング)。
戻り値
-
address
- デプロイされたUniswap V3プールのアドレス。
NoDelegateCall
概要
デリゲートコール(delegatecall
)を子コントラクトのメソッドに対して防止するためのコントラクト。
詳細
この抽象コントラクトは、delegatecall
を使用して別のコントラクトのメソッドを呼び出すことを防ぐための修飾子を提供します。
delegatecall
は、他のコントラクトのコードを呼び出すメカニズムであり、この基本コントラクトを使用することで、意図しないコントラクト間の干渉を防ぐことができます。
NoDelegateCall
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;
/// @title Prevents delegatecall to a contract
/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract
abstract contract NoDelegateCall {
/// @dev The original address of this contract
address private immutable original;
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
original = address(this);
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function checkNotDelegateCall() private view {
require(address(this) == original);
}
/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
}
original
概要
このコントラクトの元のアドレスを保持する変数。
詳細
この変数は、コントラクトがデプロイされた際の元のアドレスを格納します。
イミュータブル(不変)変数であり、デプロイ後に変更されることはありません。
delegatecall
の実行元のアドレスと比較することで、delegatecall
を防止します。
戻り値
-
address
- このコントラクトの元のアドレス。
checkNotDelegateCall
概要
デリゲートコールでないことを確認するためのプライベート関数。
詳細
デリゲートコールが行われていないことを確認します。
デリゲートコールを行っている場合、この関数のrequire
文によってエラーが発生します。
デリゲートコールが検出されると、意図せぬコントラクト間の呼び出しを防ぐことができます。
戻り値
なし
noDelegateCall
概要
修飾されたメソッドに対するデリゲートコールを防止するための修飾子。
詳細
修飾されたメソッドがデリゲートコールによって呼び出されないことを確認します。
デリゲートコールを防ぐことで、コントラクト間の意図しない相互作用を防止します。
UniswapV3Pool
UniswapV3Pool
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;
import './interfaces/IUniswapV3Pool.sol';
import './NoDelegateCall.sol';
import './libraries/LowGasSafeMath.sol';
import './libraries/SafeCast.sol';
import './libraries/Tick.sol';
import './libraries/TickBitmap.sol';
import './libraries/Position.sol';
import './libraries/Oracle.sol';
import './libraries/FullMath.sol';
import './libraries/FixedPoint128.sol';
import './libraries/TransferHelper.sol';
import './libraries/TickMath.sol';
import './libraries/LiquidityMath.sol';
import './libraries/SqrtPriceMath.sol';
import './libraries/SwapMath.sol';
import './interfaces/IUniswapV3PoolDeployer.sol';
import './interfaces/IUniswapV3Factory.sol';
import './interfaces/IERC20Minimal.sol';
import './interfaces/callback/IUniswapV3MintCallback.sol';
import './interfaces/callback/IUniswapV3SwapCallback.sol';
import './interfaces/callback/IUniswapV3FlashCallback.sol';
contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
using SafeCast for uint256;
using SafeCast for int256;
using Tick for mapping(int24 => Tick.Info);
using TickBitmap for mapping(int16 => uint256);
using Position for mapping(bytes32 => Position.Info);
using Position for Position.Info;
using Oracle for Oracle.Observation[65535];
/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override factory;
/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override token0;
/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override token1;
/// @inheritdoc IUniswapV3PoolImmutables
uint24 public immutable override fee;
/// @inheritdoc IUniswapV3PoolImmutables
int24 public immutable override tickSpacing;
/// @inheritdoc IUniswapV3PoolImmutables
uint128 public immutable override maxLiquidityPerTick;
struct Slot0 {
// the current price
uint160 sqrtPriceX96;
// the current tick
int24 tick;
// the most-recently updated index of the observations array
uint16 observationIndex;
// the current maximum number of observations that are being stored
uint16 observationCardinality;
// the next maximum number of observations to store, triggered in observations.write
uint16 observationCardinalityNext;
// the current protocol fee as a percentage of the swap fee taken on withdrawal
// represented as an integer denominator (1/x)%
uint8 feeProtocol;
// whether the pool is locked
bool unlocked;
}
/// @inheritdoc IUniswapV3PoolState
Slot0 public override slot0;
/// @inheritdoc IUniswapV3PoolState
uint256 public override feeGrowthGlobal0X128;
/// @inheritdoc IUniswapV3PoolState
uint256 public override feeGrowthGlobal1X128;
// accumulated protocol fees in token0/token1 units
struct ProtocolFees {
uint128 token0;
uint128 token1;
}
/// @inheritdoc IUniswapV3PoolState
ProtocolFees public override protocolFees;
/// @inheritdoc IUniswapV3PoolState
uint128 public override liquidity;
/// @inheritdoc IUniswapV3PoolState
mapping(int24 => Tick.Info) public override ticks;
/// @inheritdoc IUniswapV3PoolState
mapping(int16 => uint256) public override tickBitmap;
/// @inheritdoc IUniswapV3PoolState
mapping(bytes32 => Position.Info) public override positions;
/// @inheritdoc IUniswapV3PoolState
Oracle.Observation[65535] public override observations;
/// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance
/// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because
/// we use balance checks to determine the payment status of interactions such as mint, swap and flash.
modifier lock() {
require(slot0.unlocked, 'LOK');
slot0.unlocked = false;
_;
slot0.unlocked = true;
}
/// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner()
modifier onlyFactoryOwner() {
require(msg.sender == IUniswapV3Factory(factory).owner());
_;
}
constructor() {
int24 _tickSpacing;
(factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
tickSpacing = _tickSpacing;
maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
}
/// @dev Common checks for valid tick inputs.
function checkTicks(int24 tickLower, int24 tickUpper) private pure {
require(tickLower < tickUpper, 'TLU');
require(tickLower >= TickMath.MIN_TICK, 'TLM');
require(tickUpper <= TickMath.MAX_TICK, 'TUM');
}
/// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests.
function _blockTimestamp() internal view virtual returns (uint32) {
return uint32(block.timestamp); // truncation is desired
}
/// @dev Get the pool's balance of token0
/// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
/// check
function balance0() private view returns (uint256) {
(bool success, bytes memory data) =
token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
require(success && data.length >= 32);
return abi.decode(data, (uint256));
}
/// @dev Get the pool's balance of token1
/// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
/// check
function balance1() private view returns (uint256) {
(bool success, bytes memory data) =
token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
require(success && data.length >= 32);
return abi.decode(data, (uint256));
}
/// @inheritdoc IUniswapV3PoolDerivedState
function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
external
view
override
noDelegateCall
returns (
int56 tickCumulativeInside,
uint160 secondsPerLiquidityInsideX128,
uint32 secondsInside
)
{
checkTicks(tickLower, tickUpper);
int56 tickCumulativeLower;
int56 tickCumulativeUpper;
uint160 secondsPerLiquidityOutsideLowerX128;
uint160 secondsPerLiquidityOutsideUpperX128;
uint32 secondsOutsideLower;
uint32 secondsOutsideUpper;
{
Tick.Info storage lower = ticks[tickLower];
Tick.Info storage upper = ticks[tickUpper];
bool initializedLower;
(tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = (
lower.tickCumulativeOutside,
lower.secondsPerLiquidityOutsideX128,
lower.secondsOutside,
lower.initialized
);
require(initializedLower);
bool initializedUpper;
(tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = (
upper.tickCumulativeOutside,
upper.secondsPerLiquidityOutsideX128,
upper.secondsOutside,
upper.initialized
);
require(initializedUpper);
}
Slot0 memory _slot0 = slot0;
if (_slot0.tick < tickLower) {
return (
tickCumulativeLower - tickCumulativeUpper,
secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128,
secondsOutsideLower - secondsOutsideUpper
);
} else if (_slot0.tick < tickUpper) {
uint32 time = _blockTimestamp();
(int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
observations.observeSingle(
time,
0,
_slot0.tick,
_slot0.observationIndex,
liquidity,
_slot0.observationCardinality
);
return (
tickCumulative - tickCumulativeLower - tickCumulativeUpper,
secondsPerLiquidityCumulativeX128 -
secondsPerLiquidityOutsideLowerX128 -
secondsPerLiquidityOutsideUpperX128,
time - secondsOutsideLower - secondsOutsideUpper
);
} else {
return (
tickCumulativeUpper - tickCumulativeLower,
secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128,
secondsOutsideUpper - secondsOutsideLower
);
}
}
/// @inheritdoc IUniswapV3PoolDerivedState
function observe(uint32[] calldata secondsAgos)
external
view
override
noDelegateCall
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s)
{
return
observations.observe(
_blockTimestamp(),
secondsAgos,
slot0.tick,
slot0.observationIndex,
liquidity,
slot0.observationCardinality
);
}
/// @inheritdoc IUniswapV3PoolActions
function increaseObservationCardinalityNext(uint16 observationCardinalityNext)
external
override
lock
noDelegateCall
{
uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event
uint16 observationCardinalityNextNew =
observations.grow(observationCardinalityNextOld, observationCardinalityNext);
slot0.observationCardinalityNext = observationCardinalityNextNew;
if (observationCardinalityNextOld != observationCardinalityNextNew)
emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew);
}
/// @inheritdoc IUniswapV3PoolActions
/// @dev not locked because it initializes unlocked
function initialize(uint160 sqrtPriceX96) external override {
require(slot0.sqrtPriceX96 == 0, 'AI');
int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
(uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp());
slot0 = Slot0({
sqrtPriceX96: sqrtPriceX96,
tick: tick,
observationIndex: 0,
observationCardinality: cardinality,
observationCardinalityNext: cardinalityNext,
feeProtocol: 0,
unlocked: true
});
emit Initialize(sqrtPriceX96, tick);
}
struct ModifyPositionParams {
// the address that owns the position
address owner;
// the lower and upper tick of the position
int24 tickLower;
int24 tickUpper;
// any change in liquidity
int128 liquidityDelta;
}
/// @dev Effect some changes to a position
/// @param params the position details and the change to the position's liquidity to effect
/// @return position a storage pointer referencing the position with the given owner and tick range
/// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient
/// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient
function _modifyPosition(ModifyPositionParams memory params)
private
noDelegateCall
returns (
Position.Info storage position,
int256 amount0,
int256 amount1
)
{
checkTicks(params.tickLower, params.tickUpper);
Slot0 memory _slot0 = slot0; // SLOAD for gas optimization
position = _updatePosition(
params.owner,
params.tickLower,
params.tickUpper,
params.liquidityDelta,
_slot0.tick
);
if (params.liquidityDelta != 0) {
if (_slot0.tick < params.tickLower) {
// current tick is below the passed range; liquidity can only become in range by crossing from left to
// right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
amount0 = SqrtPriceMath.getAmount0Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
} else if (_slot0.tick < params.tickUpper) {
// current tick is inside the passed range
uint128 liquidityBefore = liquidity; // SLOAD for gas optimization
// write an oracle entry
(slot0.observationIndex, slot0.observationCardinality) = observations.write(
_slot0.observationIndex,
_blockTimestamp(),
_slot0.tick,
liquidityBefore,
_slot0.observationCardinality,
_slot0.observationCardinalityNext
);
amount0 = SqrtPriceMath.getAmount0Delta(
_slot0.sqrtPriceX96,
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
amount1 = SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
_slot0.sqrtPriceX96,
params.liquidityDelta
);
liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
} else {
// current tick is above the passed range; liquidity can only become in range by crossing from right to
// left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
amount1 = SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
);
}
}
}
/// @dev Gets and updates a position with the given liquidity delta
/// @param owner the owner of the position
/// @param tickLower the lower tick of the position's tick range
/// @param tickUpper the upper tick of the position's tick range
/// @param tick the current tick, passed to avoid sloads
function _updatePosition(
address owner,
int24 tickLower,
int24 tickUpper,
int128 liquidityDelta,
int24 tick
) private returns (Position.Info storage position) {
position = positions.get(owner, tickLower, tickUpper);
uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization
uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization
// if we need to update the ticks, do it
bool flippedLower;
bool flippedUpper;
if (liquidityDelta != 0) {
uint32 time = _blockTimestamp();
(int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
observations.observeSingle(
time,
0,
slot0.tick,
slot0.observationIndex,
liquidity,
slot0.observationCardinality
);
flippedLower = ticks.update(
tickLower,
tick,
liquidityDelta,
_feeGrowthGlobal0X128,
_feeGrowthGlobal1X128,
secondsPerLiquidityCumulativeX128,
tickCumulative,
time,
false,
maxLiquidityPerTick
);
flippedUpper = ticks.update(
tickUpper,
tick,
liquidityDelta,
_feeGrowthGlobal0X128,
_feeGrowthGlobal1X128,
secondsPerLiquidityCumulativeX128,
tickCumulative,
time,
true,
maxLiquidityPerTick
);
if (flippedLower) {
tickBitmap.flipTick(tickLower, tickSpacing);
}
if (flippedUpper) {
tickBitmap.flipTick(tickUpper, tickSpacing);
}
}
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128);
position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);
// clear any tick data that is no longer needed
if (liquidityDelta < 0) {
if (flippedLower) {
ticks.clear(tickLower);
}
if (flippedUpper) {
ticks.clear(tickUpper);
}
}
}
/// @inheritdoc IUniswapV3PoolActions
/// @dev noDelegateCall is applied indirectly via _modifyPosition
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external override lock returns (uint256 amount0, uint256 amount1) {
require(amount > 0);
(, int256 amount0Int, int256 amount1Int) =
_modifyPosition(
ModifyPositionParams({
owner: recipient,
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: int256(amount).toInt128()
})
);
amount0 = uint256(amount0Int);
amount1 = uint256(amount1Int);
uint256 balance0Before;
uint256 balance1Before;
if (amount0 > 0) balance0Before = balance0();
if (amount1 > 0) balance1Before = balance1();
IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data);
if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');
emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
}
/// @inheritdoc IUniswapV3PoolActions
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external override lock returns (uint128 amount0, uint128 amount1) {
// we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1}
Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);
amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;
if (amount0 > 0) {
position.tokensOwed0 -= amount0;
TransferHelper.safeTransfer(token0, recipient, amount0);
}
if (amount1 > 0) {
position.tokensOwed1 -= amount1;
TransferHelper.safeTransfer(token1, recipient, amount1);
}
emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
}
/// @inheritdoc IUniswapV3PoolActions
/// @dev noDelegateCall is applied indirectly via _modifyPosition
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external override lock returns (uint256 amount0, uint256 amount1) {
(Position.Info storage position, int256 amount0Int, int256 amount1Int) =
_modifyPosition(
ModifyPositionParams({
owner: msg.sender,
tickLower: tickLower,
tickUpper: tickUpper,
liquidityDelta: -int256(amount).toInt128()
})
);
amount0 = uint256(-amount0Int);
amount1 = uint256(-amount1Int);
if (amount0 > 0 || amount1 > 0) {
(position.tokensOwed0, position.tokensOwed1) = (
position.tokensOwed0 + uint128(amount0),
position.tokensOwed1 + uint128(amount1)
);
}
emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1);
}
struct SwapCache {
// the protocol fee for the input token
uint8 feeProtocol;
// liquidity at the beginning of the swap
uint128 liquidityStart;
// the timestamp of the current block
uint32 blockTimestamp;
// the current value of the tick accumulator, computed only if we cross an initialized tick
int56 tickCumulative;
// the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick
uint160 secondsPerLiquidityCumulativeX128;
// whether we've computed and cached the above two accumulators
bool computedLatestObservation;
}
// the top level state of the swap, the results of which are recorded in storage at the end
struct SwapState {
// the amount remaining to be swapped in/out of the input/output asset
int256 amountSpecifiedRemaining;
// the amount already swapped out/in of the output/input asset
int256 amountCalculated;
// current sqrt(price)
uint160 sqrtPriceX96;
// the tick associated with the current price
int24 tick;
// the global fee growth of the input token
uint256 feeGrowthGlobalX128;
// amount of input token paid as protocol fee
uint128 protocolFee;
// the current liquidity in range
uint128 liquidity;
}
struct StepComputations {
// the price at the beginning of the step
uint160 sqrtPriceStartX96;
// the next tick to swap to from the current tick in the swap direction
int24 tickNext;
// whether tickNext is initialized or not
bool initialized;
// sqrt(price) for the next tick (1/0)
uint160 sqrtPriceNextX96;
// how much is being swapped in in this step
uint256 amountIn;
// how much is being swapped out
uint256 amountOut;
// how much fee is being paid in
uint256 feeAmount;
}
/// @inheritdoc IUniswapV3PoolActions
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override noDelegateCall returns (int256 amount0, int256 amount1) {
require(amountSpecified != 0, 'AS');
Slot0 memory slot0Start = slot0;
require(slot0Start.unlocked, 'LOK');
require(
zeroForOne
? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO
: sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO,
'SPL'
);
slot0.unlocked = false;
SwapCache memory cache =
SwapCache({
liquidityStart: liquidity,
blockTimestamp: _blockTimestamp(),
feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4),
secondsPerLiquidityCumulativeX128: 0,
tickCumulative: 0,
computedLatestObservation: false
});
bool exactInput = amountSpecified > 0;
SwapState memory state =
SwapState({
amountSpecifiedRemaining: amountSpecified,
amountCalculated: 0,
sqrtPriceX96: slot0Start.sqrtPriceX96,
tick: slot0Start.tick,
feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128,
protocolFee: 0,
liquidity: cache.liquidityStart
});
// continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
StepComputations memory step;
step.sqrtPriceStartX96 = state.sqrtPriceX96;
(step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord(
state.tick,
tickSpacing,
zeroForOne
);
// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
if (step.tickNext < TickMath.MIN_TICK) {
step.tickNext = TickMath.MIN_TICK;
} else if (step.tickNext > TickMath.MAX_TICK) {
step.tickNext = TickMath.MAX_TICK;
}
// get the price for the next tick
step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);
// compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
state.sqrtPriceX96,
(zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
? sqrtPriceLimitX96
: step.sqrtPriceNextX96,
state.liquidity,
state.amountSpecifiedRemaining,
fee
);
if (exactInput) {
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256());
} else {
state.amountSpecifiedRemaining += step.amountOut.toInt256();
state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256());
}
// if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee
if (cache.feeProtocol > 0) {
uint256 delta = step.feeAmount / cache.feeProtocol;
step.feeAmount -= delta;
state.protocolFee += uint128(delta);
}
// update global fee tracker
if (state.liquidity > 0)
state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity);
// shift tick if we reached the next price
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
// if the tick is initialized, run the tick transition
if (step.initialized) {
// check for the placeholder value, which we replace with the actual value the first time the swap
// crosses an initialized tick
if (!cache.computedLatestObservation) {
(cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle(
cache.blockTimestamp,
0,
slot0Start.tick,
slot0Start.observationIndex,
cache.liquidityStart,
slot0Start.observationCardinality
);
cache.computedLatestObservation = true;
}
int128 liquidityNet =
ticks.cross(
step.tickNext,
(zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128),
(zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128),
cache.secondsPerLiquidityCumulativeX128,
cache.tickCumulative,
cache.blockTimestamp
);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if (zeroForOne) liquidityNet = -liquidityNet;
state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
}
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
}
}
// update tick and write an oracle entry if the tick change
if (state.tick != slot0Start.tick) {
(uint16 observationIndex, uint16 observationCardinality) =
observations.write(
slot0Start.observationIndex,
cache.blockTimestamp,
slot0Start.tick,
cache.liquidityStart,
slot0Start.observationCardinality,
slot0Start.observationCardinalityNext
);
(slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = (
state.sqrtPriceX96,
state.tick,
observationIndex,
observationCardinality
);
} else {
// otherwise just update the price
slot0.sqrtPriceX96 = state.sqrtPriceX96;
}
// update liquidity if it changed
if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity;
// update fee growth global and, if necessary, protocol fees
// overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees
if (zeroForOne) {
feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee;
} else {
feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee;
}
(amount0, amount1) = zeroForOne == exactInput
? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated)
: (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining);
// do the transfers and collect payment
if (zeroForOne) {
if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1));
uint256 balance0Before = balance0();
IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA');
} else {
if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0));
uint256 balance1Before = balance1();
IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA');
}
emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick);
slot0.unlocked = true;
}
/// @inheritdoc IUniswapV3PoolActions
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external override lock noDelegateCall {
uint128 _liquidity = liquidity;
require(_liquidity > 0, 'L');
uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6);
uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6);
uint256 balance0Before = balance0();
uint256 balance1Before = balance1();
if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0);
if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1);
IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
uint256 balance0After = balance0();
uint256 balance1After = balance1();
require(balance0Before.add(fee0) <= balance0After, 'F0');
require(balance1Before.add(fee1) <= balance1After, 'F1');
// sub is safe because we know balanceAfter is gt balanceBefore by at least fee
uint256 paid0 = balance0After - balance0Before;
uint256 paid1 = balance1After - balance1Before;
if (paid0 > 0) {
uint8 feeProtocol0 = slot0.feeProtocol % 16;
uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0;
if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0);
feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity);
}
if (paid1 > 0) {
uint8 feeProtocol1 = slot0.feeProtocol >> 4;
uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1;
if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1);
feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity);
}
emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1);
}
/// @inheritdoc IUniswapV3PoolOwnerActions
function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner {
require(
(feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) &&
(feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10))
);
uint8 feeProtocolOld = slot0.feeProtocol;
slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4);
emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1);
}
/// @inheritdoc IUniswapV3PoolOwnerActions
function collectProtocol(
address recipient,
uint128 amount0Requested,
uint128 amount1Requested
) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) {
amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested;
amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested;
if (amount0 > 0) {
if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings
protocolFees.token0 -= amount0;
TransferHelper.safeTransfer(token0, recipient, amount0);
}
if (amount1 > 0) {
if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings
protocolFees.token1 -= amount1;
TransferHelper.safeTransfer(token1, recipient, amount1);
}
emit CollectProtocol(msg.sender, recipient, amount0, amount1);
}
}
申し訳ありません。以下に変数やイベント、関数名をH3で示し、それぞれの要素について説明します。
factory
概要
コントラクトのアドレスを保持する変数。
詳細
UniswapV3Poolコントラクトをデプロイしたファクトリーコントラクトのアドレスを格納します。
token0
概要
トークン0のアドレスを保持する変数。
詳細
トークン0は取引ペアの一方であり、この変数はそのアドレスを格納します。
token1
概要
トークン1のアドレスを保持する変数。
詳細
トークン1は取引ペアのもう一方であり、この変数はそのアドレスを格納します。
fee
概要
スワップ手数料のレートを保持する変数。
詳細
スワップ時に発生する手数料の割合です。
tickSpacing
概要
ティックの間隔を保持する変数。
詳細
ティックは価格レベルを表し、隣接するティック間の価格差を示します。
maxLiquidityPerTick
概要
ティックごとの最大流動性を保持する変数。
詳細
1つのティックごとに許容される最大流動性を示します。
slot0
概要
スロット0の情報を保持する構造体。
詳細
現在の価格やティック、オブザベーションなどのプールの状態を格納します。
パラメータ
-
sqrtPriceX96
- 現在の価格の平方根を表す固定小数点数。
-
tick
- 現在のティックの値。
-
observationIndex
- 最も最近に更新されたオブザベーション配列のインデックス。
-
observationCardinality
- 現在格納されているオブザベーションの最大数。
-
observationCardinalityNext
- 次に格納するオブザベーションの最大数。
-
feeProtocol
- プロトコル手数料の割合を示す整数分母。
-
unlocked
- プールのロック状態を示すブール値。
feeGrowthGlobal0X128
概要
トークン0の累積手数料成長を保持する変数。
詳細
トークン0の累積手数料成長を128
倍精度で表現します。
feeGrowthGlobal1X128
概要
トークン1の累積手数料成長を保持する変数。
詳細
トークン1の累積手数料成長を128
倍精度で表現します。
protocolFees
概要
プロトコル手数料の情報を保持する構造体。
詳細
トークン0およびトークン1のプロトコル手数料を格納します。
パラメータ
-
token0
- トークン0に累積されたプロトコル手数料を表す固定小数点数。
-
token1
- トークン1に累積されたプロトコル手数料を表す固定小数点数。
liquidity
概要
現在の総流動性を保持する変数。
詳細
プール全体の総流動性を示します。
ticks
概要
ティック情報を保持するマッピング配列。
詳細
各ティックの詳細な情報を格納します。
tickBitmap
概要
ティックのビットマップを保持するマッピング配列。
詳細
ティックが存在することを示すビットを格納します。
positions
概要
ポジション情報を保持するマッピング配列。
詳細
ポジションの詳細情報を格納します。
observations
概要
オブザベーション情報を保持する配列。
詳細
価格のオブザベーション情報を格納します。
lock
概要
リエントラント攻撃から保護するための修飾子。
詳細
特定の関数を呼び出す前にプールがロックされていることを確認し、呼び出し後にプールをアンロックします。
onlyFactoryOwner
概要
ファクトリーオーナーのみが呼び出せることを確認するための修飾子。
詳細
関数がUniswapV3ファクトリーのオーナーによってのみ呼び出されることを確認します。
もちろんです。以下に提供いただいたコード内の各変数、constructor、イベント、関数の説明を示します。
constructor
概要
コントラクトのコンストラクタ。
詳細
コントラクトがデプロイされる際に実行される初期化関数です。_tickSpacing
を取得し、factory
、token0
、token1
、fee
、tickSpacing
、maxLiquidityPerTick
を初期化します。
checkTicks
概要
ティックの妥当性をチェックする内部関数。
詳細
引数の値が問題ないかチェックします。
tickLower
がtickUpper
よりも小さく、かつ最小ティックと最大ティックの範囲内であることを確認します。
引数
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
snapshotCumulativesInside
概要
指定したティック範囲内の累積データをスナップショットする関数。
詳細
引数で指定されたティック範囲内の累積ティックカウントや時間データをスナップショットします。
引数
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
戻り値
-
tickCumulativeInside
- ティック範囲内の累積ティックカウント。
-
secondsPerLiquidityInsideX128
- ティック範囲内の流動性ごとの秒数(
128
倍精度)。
- ティック範囲内の流動性ごとの秒数(
-
secondsInside
- ティック範囲内の秒数。
observe
概要
過去の時間に対する累積データを取得する関数。
詳細
引数で指定された過去の時間に対する累積ティックカウントや時間データを取得します。
引数
-
secondsAgos
- 過去の時間(秒単位)の配列。
戻り値
-
tickCumulatives
- 過去の時間に対する累積ティックカウントの配列。
-
secondsPerLiquidityCumulativeX128s
- 過去の時間に対する流動性ごとの秒数(
128
倍精度)の配列。
- 過去の時間に対する流動性ごとの秒数(
increaseObservationCardinalityNext
概要
次のオブザベーションカーディナリティを増やす関数。
詳細
次のオブザベーションカーディナリティを増やし、変更があった場合にイベントを発行します。
引数
-
observationCardinalityNext
- 新しいオブザベーションカーディナリティ。
initialize
概要
プールを初期化する関数。
詳細
- プールがまだ初期化されていない場合にプールを初期化し、
slot0
を設定します。
引数
-
sqrtPriceX96
- 初期化する価格の平方根(固定小数点数)。
ModifyPositionParams
概要
- ポジションを変更するためのパラメータを格納する構造体。
詳細
- ポジションのオーナーアドレス、ティック範囲、流動性の変化などのパラメータを格納します。
引数
-
owner
: ポジションのオーナーアドレス -
tickLower
: 下限ティック値 -
tickUpper
: 上限ティック値 -
liquidityDelta
: 流動性の変化量
_modifyPosition
概要
ポジションを変更するための内部関数。
詳細
ポジションの変更に伴って発生するamount0
と量amount1
を計算し、ポジション情報を更新します。
引数
-
params
- ポジションの変更パラメータを含む構造体。
戻り値
-
position
- 変更されたポジションの情報へのストレージポインタ。
-
amount0
- プールから徴収されるトークン0の量(負の値の場合はプールが受け取る)。
-
amount1
- プールから徴収されるトークン1の量(負の値の場合はプールが受け取る)。
_updatePosition
概要
ポジション情報を更新するための内部関数。
詳細
ポジション情報を更新し、ティック情報や流動性の変更も行います。
引数
-
owner
- ポジションのオーナーアドレス。
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
-
liquidityDelta
- 流動性の変化量。
-
tick
- 現在のティック値。
戻り値
-
position
- 更新されたポジションの情報へのストレージポインタ。
mint
概要
ポジションを増やして新しい流動性を提供する関数。
詳細
指定された範囲のティックに新しい流動性を追加し、プールに対してトークン0とトークン1の量を返します。
引数
-
recipient
- 流動性を提供するユーザーのアドレス。
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
-
amount
- 提供する流動性の量。
-
data
- データ。
戻り値
-
amount0
- プールから返されるトークン0の量。
-
amount1
- プールから返されるトークン1の量。
collect
概要
ポジションからトークンを受け取るための関数。
詳細
ポジションからトークン0とトークン1を受け取り、受け取った量を返します。
引数
-
recipient
- トークンを受け取るユーザーのアドレス。
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
-
amount0Requested
- 受け取るトークン0の要求量。
-
amount1Requested
- 受け取るトークン1の要求量。
戻り値
-
amount0
- 受け取ったトークン0の実際の量。
-
amount1
- 受け取ったトークン1の実際の量。
burn
概要
ポジションから流動性を減少させるための関数。
詳細
ポジションから指定された範囲のティックに従って流動性を減少させ、減少したトークン0とトークン1の量を計算します。
引数
-
tickLower
- 下限ティック値。
-
tickUpper
- 上限ティック値。
-
amount
- 減少させる流動性の量。
戻り値
-
amount0
- 減少したトークン0の量。
-
amount1
- 減少したトークン1の量。
SwapCache
概要
- スワップに関するキャッシュ情報を格納する構造体。
詳細
- スワップの実行に関する情報をキャッシュし、計算された値やフラグを保持します。
引数 or パラメータ
-
feeProtocol
: 入力トークンのプロトコル料金(プロトコルの手数料) -
liquidityStart
: スワップの開始時の流動性 -
blockTimestamp
: 現在のブロックのタイムスタンプ -
tickCumulative
: 現在のティック累積値(初期化済みティックを跨いだ場合にのみ計算) -
secondsPerLiquidityCumulativeX128
: 現在の流動性累積の秒数(初期化済みティックを跨いだ場合にのみ計算) -
computedLatestObservation
: ティック累積値と流動性累積の計算が行われたかどうかのフラグ
SwapState
概要
スワップのトップレベルの状態を格納する構造体。
詳細
スワップのトップレベルの状態を保持し、トークンのスワップ情報や現在の価格などを管理します。
パラメータ
-
amountSpecifiedRemaining
- スワップされるべき残りの入力/出力トークンの量。
-
amountCalculated
- 既にスワップされた出力/入力トークンの量。
-
sqrtPriceX96
- 現在の平方根価格(プライス)。
-
tick
- 現在のティック値。
-
feeGrowthGlobalX128
- 入力トークンのグローバル料金成長(フィー)。
-
protocolFee
- プロトコル料金として支払われる入力トークンの量。
-
liquidity
- 現在の範囲内の流動性。
StepComputations
概要
ステップの計算情報を格納する構造体。
詳細
スワップの各ステップにおける計算に関する情報を保持します。
パラメータ
-
sqrtPriceStartX96
- ステップの開始時の平方根価格(プライス)。
-
tickNext
- スワップ方向の現在のティックから次のティックへの移動先。
-
initialized
-
tickNext
が初期化されているかどうかのフラグ。
-
-
sqrtPriceNextX96
- 次のティックの平方根価格(プライス)。
-
amountIn
- このステップで入力されるトークンの量。
-
amountOut
- このステップで出力されるトークンの量。
-
feeAmount
- このステップで支払われる手数料の量。
swap
概要
トークンのスワップを実行する関数。
詳細
指定された条件に基づいて、トークンのスワップを実行します。
引数
-
recipient
- トークンを受け取るアドレス。
-
zeroForOne
- トークンのスワップ方向(
true
: トークン0 → トークン1、false
: トークン1 → トークン0)。
- トークンのスワップ方向(
-
amountSpecified
- スワップするトークンの量。
-
sqrtPriceLimitX96
- スワップの価格制限。
-
data
- データ。
戻り値
-
amount0
- スワップで得られたトークン0の量。
-
amount1
- スワップで得られたトークン1の量。
flash
概要
フラッシュスワップを実行する関数。
詳細
フラッシュスワップによって指定されたトークンの量をスワップし、手数料を支払い、追加のコールバック処理を行います。
フラッシュスワップにおける手数料や処理後の残高などを計算し、関連するイベントを発行します。
引数:
-
recipient
- スワップの結果を受け取るアドレス。
-
amount0
- スワップするトークン0の量。
-
amount1
- スワップするトークン1の量。
-
data
- 追加のデータ。
戻り値
なし
setFeeProtocol
概要
手数料プロトコルを設定する関数。
詳細
スワッププールの手数料プロトコルを設定するための関数。
手数料プロトコルはトークン0とトークン1それぞれに対して設定されます。
プロトコル所有者のみがこの関数を呼び出すことができます。
手数料プロトコルは、スワップ手数料を制御するために使用されます。
引数:
-
feeProtocol0
- トークン0の手数料プロトコル。
-
feeProtocol1
- トークン1の手数料プロトコル。
戻り値
なし
collectProtocol
概要
プロトコル手数料を回収する関数。
詳細
スワッププールのプロトコル手数料を回収するための関数。
プロトコル所有者のみがこの関数を呼び出すことができます。
指定されたトークン0とトークン1の量に基づいて、プロトコル手数料を回収します。
回収された手数料はプロトコル所有者に送信されます。
引数 or パラメータ:
-
recipient
- 手数料を受け取るアドレス。
-
amount0Requested
- 回収するトークン0の量。
-
amount1Requested
- 回収するトークン1の量。
戻り値:
-
amount0
- 実際に回収されたトークン0の量。
- `amount1:
- 実際に回収されたトークン1の量。
イベント
OwnerChanged
概要
ファクトリーのオーナーが変更された時に発行されるイベント。
パラメータ:
-
oldOwner
- オーナー変更前のアドレス。
-
newOwner
- オーナー変更後のアドレス。
PoolCreated
概要
新しいプールが作成された時に発行されるイベント。
パラメータ:
-
token0
- プール内の最初のトークンのアドレス(アドレスのソート順に基づく)。
-
token1
- プール内の2番目のトークンのアドレス(アドレスのソート順に基づく)。
-
fee
- プール内での各スワップにおける手数料(0.01%単位で表現)。
-
tickSpacing
- 初期化されたティック間の最小数のティック。
-
pool
- 作成されたプールのアドレス。
FeeAmountEnabled
概要
ファクトリーを通じてプールの作成に使用される新しい手数料が有効になったときに発行されるイベント。
パラメータ:
-
fee
- 有効になった手数料(0.01%単位で表現)。
-
tickSpacing
- 手数料に対応する初期化ティック間隔。
コード
インターフェース
-
interfaces
ディレクトリ。
ライブラリ
-
libraries
ディレクトリ。
最後に
今回の記事では、Bunzzの新機能『DeCipher』を使用して、UniswapV3の「UniswapV3Factory」のコントラクトを見てきました。
いかがだったでしょうか?
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。
普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!
Discussion