Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/UniswapV4StateMulticall.sol": {
"content": "// SPDX-License-Identifier: ISC
pragma solidity 0.8.26;
pragma abicoder v2;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import "./interfaces/IUniswapV4StateMulticall.sol";
contract UniswapV4StateMulticall is IUniswapV4StateMulticall {
using StateLibrary for IPoolManager;
using PoolIdLibrary for PoolKey;
function getFullState(IPoolManager poolManager, PoolKey calldata key, int16 tickBitmapStart, int16 tickBitmapEnd)
external
view
override
returns (StateResult memory state)
{
require(tickBitmapEnd >= tickBitmapStart, "tickBitmapEnd < tickBitmapStart");
state = _fillStateWithoutTicks(poolManager, key, tickBitmapStart, tickBitmapEnd);
state.ticks = _calcTicksFromBitMap(poolManager, key, state.tickBitmap);
}
function getFullStateWithoutTicks(
IPoolManager poolManager,
PoolKey calldata key,
int16 tickBitmapStart,
int16 tickBitmapEnd
) external view override returns (StateResult memory state) {
require(tickBitmapEnd >= tickBitmapStart, "tickBitmapEnd < tickBitmapStart");
return _fillStateWithoutTicks(poolManager, key, tickBitmapStart, tickBitmapEnd);
}
function getFullStateWithRelativeBitmaps(
IPoolManager poolManager,
PoolKey calldata key,
int16 leftBitmapAmount,
int16 rightBitmapAmount
) external view override returns (StateResult memory state) {
require(leftBitmapAmount > 0, "leftBitmapAmount <= 0");
require(rightBitmapAmount > 0, "rightBitmapAmount <= 0");
state = _fillStateWithoutBitmapsAndTicks(poolManager, key);
int16 currentBitmapIndex = _getBitmapIndexFromTick(state.slot0.tick / key.tickSpacing);
state.tickBitmap = _calcTickBitmaps(
poolManager, key, currentBitmapIndex - leftBitmapAmount, currentBitmapIndex + rightBitmapAmount
);
state.ticks = _calcTicksFromBitMap(poolManager, key, state.tickBitmap);
}
function getAdditionalBitmapWithTicks(
IPoolManager poolManager,
PoolKey calldata key,
int16 tickBitmapStart,
int16 tickBitmapEnd
) external view override returns (TickBitMapMappings[] memory tickBitmap, TickInfoMappings[] memory ticks) {
require(tickBitmapEnd >= tickBitmapStart, "tickBitmapEnd < tickBitmapStart");
tickBitmap = _calcTickBitmaps(poolManager, key, tickBitmapStart, tickBitmapEnd);
ticks = _calcTicksFromBitMap(poolManager, key, tickBitmap);
}
function getAdditionalBitmapWithoutTicks(
IPoolManager poolManager,
PoolKey calldata key,
int16 tickBitmapStart,
int16 tickBitmapEnd
) external view override returns (TickBitMapMappings[] memory tickBitmap) {
require(tickBitmapEnd >= tickBitmapStart, "tickBitmapEnd < tickBitmapStart");
return _calcTickBitmaps(poolManager, key, tickBitmapStart, tickBitmapEnd);
}
function _fillStateWithoutTicks(
IPoolManager poolManager,
PoolKey calldata key,
int16 tickBitmapStart,
int16 tickBitmapEnd
) internal view returns (StateResult memory state) {
state = _fillStateWithoutBitmapsAndTicks(poolManager, key);
state.tickBitmap = _calcTickBitmaps(poolManager, key, tickBitmapStart, tickBitmapEnd);
}
function _fillStateWithoutBitmapsAndTicks(IPoolManager poolManager, PoolKey calldata key)
internal
view
returns (StateResult memory state)
{
PoolId poolId = key.toId();
// Check if pool exists by trying to get liquidity
state.liquidity = poolManager.getLiquidity(poolId);
state.poolManager = poolManager;
state.poolId = poolId;
state.key = key;
state.blockTimestamp = block.timestamp;
state.tickSpacing = key.tickSpacing;
// Get slot0 data
(state.slot0.sqrtPriceX96, state.slot0.tick, state.slot0.protocolFee, state.slot0.lpFee) =
poolManager.getSlot0(poolId);
// Get fee growth globals
(state.feeGrowthGlobal0X128, state.feeGrowthGlobal1X128) = poolManager.getFeeGrowthGlobals(poolId);
}
function _calcTickBitmaps(
IPoolManager poolManager,
PoolKey calldata key,
int16 tickBitmapStart,
int16 tickBitmapEnd
) internal view returns (TickBitMapMappings[] memory tickBitmap) {
PoolId poolId = key.toId();
uint256 numberOfPopulatedBitmaps = 0;
for (int256 i = tickBitmapStart; i <= tickBitmapEnd; i++) {
uint256 bitmap = poolManager.getTickBitmap(poolId, int16(i));
if (bitmap == 0) continue;
numberOfPopulatedBitmaps++;
}
tickBitmap = new TickBitMapMappings[](numberOfPopulatedBitmaps);
uint256 globalIndex = 0;
for (int256 i = tickBitmapStart; i <= tickBitmapEnd; i++) {
int16 index = int16(i);
uint256 bitmap = poolManager.getTickBitmap(poolId, index);
if (bitmap == 0) continue;
tickBitmap[globalIndex] = TickBitMapMappings({index: index, value: bitmap});
globalIndex++;
}
}
function _calcTicksFromBitMap(
IPoolManager poolManager,
PoolKey calldata key,
TickBitMapMappings[] memory tickBitmap
) internal view returns (TickInfoMappings[] memory ticks) {
PoolId poolId = key.toId();
uint256 numberOfPopulatedTicks = 0;
for (uint256 i = 0; i < tickBitmap.length; i++) {
uint256 bitmap = tickBitmap[i].value;
for (uint256 j = 0; j < 256; j++) {
if (bitmap & (1 << j) > 0) numberOfPopulatedTicks++;
}
}
ticks = new TickInfoMappings[](numberOfPopulatedTicks);
int24 tickSpacing = key.tickSpacing;
uint256 globalIndex = 0;
for (uint256 i = 0; i < tickBitmap.length; i++) {
uint256 bitmap = tickBitmap[i].value;
for (uint256 j = 0; j < 256; j++) {
if (bitmap & (1 << j) > 0) {
int24 populatedTick = ((int24(tickBitmap[i].index) << 8) + int24(uint24(j))) * tickSpacing;
ticks[globalIndex].index = populatedTick;
// Get tick info from pool manager
(
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128
) = poolManager.getTickInfo(poolId, populatedTick);
TickInfo memory newTickInfo = ticks[globalIndex].value;
newTickInfo.liquidityGross = liquidityGross;
newTickInfo.liquidityNet = liquidityNet;
newTickInfo.feeGrowthOutside0X128 = feeGrowthOutside0X128;
newTickInfo.feeGrowthOutside1X128 = feeGrowthOutside1X128;
newTickInfo.initialized = liquidityGross != 0;
ticks[globalIndex].value = newTickInfo;
globalIndex++;
}
}
}
}
function _getBitmapIndexFromTick(int24 tick) internal pure returns (int16) {
return int16(tick >> 8);
}
// Additional helper function to get pool state by tokens and fee
function getPoolStateByTokens(
IPoolManager poolManager,
Currency currency0,
Currency currency1,
uint24 fee,
int24 tickSpacing,
IHooks hooks,
int16 tickBitmapStart,
int16 tickBitmapEnd
) external view returns (StateResult memory state) {
PoolKey memory key =
PoolKey({currency0: currency0, currency1: currency1, fee: fee, tickSpacing: tickSpacing, hooks: hooks});
return this.getFullState(poolManager, key, tickBitmapStart, tickBitmapEnd);
}
}
"
},
"lib/v4-core/src/interfaces/IPoolManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "./IHooks.sol";
import {IERC6909Claims} from "./external/IERC6909Claims.sol";
import {IProtocolFees} from "./IProtocolFees.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {IExtsload} from "./IExtsload.sol";
import {IExttload} from "./IExttload.sol";
/// @notice Interface for the PoolManager
interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
/// @notice Thrown when a currency is not netted out after the contract is unlocked
error CurrencyNotSettled();
/// @notice Thrown when trying to interact with a non-initialized pool
error PoolNotInitialized();
/// @notice Thrown when unlock is called, but the contract is already unlocked
error AlreadyUnlocked();
/// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
error ManagerLocked();
/// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
error TickSpacingTooLarge(int24 tickSpacing);
/// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
error TickSpacingTooSmall(int24 tickSpacing);
/// @notice PoolKey must have currencies where address(currency0) < address(currency1)
error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);
/// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
/// or on a pool that does not have a dynamic swap fee.
error UnauthorizedDynamicLPFeeUpdate();
/// @notice Thrown when trying to swap amount of 0
error SwapAmountCannotBeZero();
///@notice Thrown when native currency is passed to a non native settlement
error NonzeroNativeValue();
/// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
error MustClearExactPositiveDelta();
/// @notice Emitted when a new pool is initialized
/// @param id The abi encoded hash of the pool key struct for the new pool
/// @param currency0 The first currency of the pool by address sort order
/// @param currency1 The second currency 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 minimum number of ticks between initialized ticks
/// @param hooks The hooks contract address for the pool, or address(0) if none
/// @param sqrtPriceX96 The price of the pool on initialization
/// @param tick The initial tick of the pool corresponding to the initialized price
event Initialize(
PoolId indexed id,
Currency indexed currency0,
Currency indexed currency1,
uint24 fee,
int24 tickSpacing,
IHooks hooks,
uint160 sqrtPriceX96,
int24 tick
);
/// @notice Emitted when a liquidity position is modified
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that modified the pool
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param liquidityDelta The amount of liquidity that was added or removed
/// @param salt The extra data to make positions unique
event ModifyLiquidity(
PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
);
/// @notice Emitted for swaps between currency0 and currency1
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that initiated the swap call, and that received the callback
/// @param amount0 The delta of the currency0 balance of the pool
/// @param amount1 The delta of the currency1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of the price of the pool after the swap
/// @param fee The swap fee in hundredths of a bip
event Swap(
PoolId indexed id,
address indexed sender,
int128 amount0,
int128 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick,
uint24 fee
);
/// @notice Emitted for donations
/// @param id The abi encoded hash of the pool key struct for the pool that was donated to
/// @param sender The address that initiated the donate call
/// @param amount0 The amount donated in currency0
/// @param amount1 The amount donated in currency1
event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);
/// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
/// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
/// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
/// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
/// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
function unlock(bytes calldata data) external returns (bytes memory);
/// @notice Initialize the state for a given pool ID
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The pool key for the pool to initialize
/// @param sqrtPriceX96 The initial square root price
/// @return tick The initial tick of the pool
function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
struct ModifyLiquidityParams {
// the lower and upper tick of the position
int24 tickLower;
int24 tickUpper;
// how to modify the liquidity
int256 liquidityDelta;
// a value to set if you want unique liquidity positions at the same range
bytes32 salt;
}
/// @notice Modify the liquidity for the given pool
/// @dev Poke by calling with a zero liquidityDelta
/// @param key The pool to modify liquidity in
/// @param params The parameters for modifying the liquidity
/// @param hookData The data to pass through to the add/removeLiquidity hooks
/// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
/// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
external
returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);
struct SwapParams {
/// Whether to swap token0 for token1 or vice versa
bool zeroForOne;
/// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
int256 amountSpecified;
/// The sqrt price at which, if reached, the swap will stop executing
uint160 sqrtPriceLimitX96;
}
/// @notice Swap against the given pool
/// @param key The pool to swap in
/// @param params The parameters for swapping
/// @param hookData The data to pass through to the swap hooks
/// @return swapDelta The balance delta of the address swapping
/// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
/// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
/// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external
returns (BalanceDelta swapDelta);
/// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
/// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
/// Donors should keep this in mind when designing donation mechanisms.
/// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
/// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
/// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
/// Read the comments in `Pool.swap()` for more information about this.
/// @param key The key of the pool to donate to
/// @param amount0 The amount of currency0 to donate
/// @param amount1 The amount of currency1 to donate
/// @param hookData The data to pass through to the donate hooks
/// @return BalanceDelta The delta of the caller after the donate
function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
external
returns (BalanceDelta);
/// @notice Writes the current ERC20 balance of the specified currency to transient storage
/// This is used to checkpoint balances for the manager and derive deltas for the caller.
/// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
/// for native tokens because the amount to settle is determined by the sent value.
/// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
/// native funds, this function can be called with the native currency to then be able to settle the native currency
function sync(Currency currency) external;
/// @notice Called by the user to net out some value owed to the user
/// @dev Will revert if the requested amount is not available, consider using `mint` instead
/// @dev Can also be used as a mechanism for free flash loans
/// @param currency The currency to withdraw from the pool manager
/// @param to The address to withdraw to
/// @param amount The amount of currency to withdraw
function take(Currency currency, address to, uint256 amount) external;
/// @notice Called by the user to pay what is owed
/// @return paid The amount of currency settled
function settle() external payable returns (uint256 paid);
/// @notice Called by the user to pay on behalf of another address
/// @param recipient The address to credit for the payment
/// @return paid The amount of currency settled
function settleFor(address recipient) external payable returns (uint256 paid);
/// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
/// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
/// @dev This could be used to clear a balance that is considered dust.
/// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
function clear(Currency currency, uint256 amount) external;
/// @notice Called by the user to move value into ERC6909 balance
/// @param to The address to mint the tokens to
/// @param id The currency address to mint to ERC6909s, as a uint256
/// @param amount The amount of currency to mint
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function mint(address to, uint256 id, uint256 amount) external;
/// @notice Called by the user to move value from ERC6909 balance
/// @param from The address to burn the tokens from
/// @param id The currency address to burn from ERC6909s, as a uint256
/// @param amount The amount of currency to burn
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function burn(address from, uint256 id, uint256 amount) external;
/// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The key of the pool to update dynamic LP fees for
/// @param newDynamicLPFee The new dynamic pool LP fee
function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
}
"
},
"lib/v4-core/src/types/PoolKey.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";
using PoolIdLibrary for PoolKey global;
/// @notice Returns the key for identifying a pool
struct PoolKey {
/// @notice The lower currency of the pool, sorted numerically
Currency currency0;
/// @notice The higher currency of the pool, sorted numerically
Currency currency1;
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
uint24 fee;
/// @notice Ticks that involve positions must be a multiple of tick spacing
int24 tickSpacing;
/// @notice The hooks of the pool
IHooks hooks;
}
"
},
"lib/v4-core/src/types/PoolId.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}
"
},
"lib/v4-core/src/libraries/StateLibrary.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolId} from "../types/PoolId.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {Position} from "./Position.sol";
/// @notice A helper library to provide state getters that use extsload
library StateLibrary {
/// @notice index of pools mapping in the PoolManager
bytes32 public constant POOLS_SLOT = bytes32(uint256(6));
/// @notice index of feeGrowthGlobal0X128 in Pool.State
uint256 public constant FEE_GROWTH_GLOBAL0_OFFSET = 1;
// feeGrowthGlobal1X128 offset in Pool.State = 2
/// @notice index of liquidity in Pool.State
uint256 public constant LIQUIDITY_OFFSET = 3;
/// @notice index of TicksInfo mapping in Pool.State: mapping(int24 => TickInfo) ticks;
uint256 public constant TICKS_OFFSET = 4;
/// @notice index of tickBitmap mapping in Pool.State
uint256 public constant TICK_BITMAP_OFFSET = 5;
/// @notice index of Position.State mapping in Pool.State: mapping(bytes32 => Position.State) positions;
uint256 public constant POSITIONS_OFFSET = 6;
/**
* @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee
* @dev Corresponds to pools[poolId].slot0
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision.
* @return tick The current tick of the pool.
* @return protocolFee The protocol fee of the pool.
* @return lpFee The swap fee of the pool.
*/
function getSlot0(IPoolManager manager, PoolId poolId)
internal
view
returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
bytes32 data = manager.extsload(stateSlot);
// 24 bits |24bits|24bits |24 bits|160 bits
// 0x000000 |000bb8|000000 |ffff75 |0000000000000000fe3aa841ba359daa0ea9eff7
// ---------- | fee |protocolfee | tick | sqrtPriceX96
assembly ("memory-safe") {
// bottom 160 bits of data
sqrtPriceX96 := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
// next 24 bits of data
tick := signextend(2, shr(160, data))
// next 24 bits of data
protocolFee := and(shr(184, data), 0xFFFFFF)
// last 24 bits of data
lpFee := and(shr(208, data), 0xFFFFFF)
}
}
/**
* @notice Retrieves the tick information of a pool at a specific tick.
* @dev Corresponds to pools[poolId].ticks[tick]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve information for.
* @return liquidityGross The total position liquidity that references this tick
* @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
* @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
* @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
*/
function getTickInfo(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128
)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
// read all 3 words of the TickInfo struct
bytes32[] memory data = manager.extsload(slot, 3);
assembly ("memory-safe") {
let firstWord := mload(add(data, 32))
liquidityNet := sar(128, firstWord)
liquidityGross := and(firstWord, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
feeGrowthOutside0X128 := mload(add(data, 64))
feeGrowthOutside1X128 := mload(add(data, 96))
}
}
/**
* @notice Retrieves the liquidity information of a pool at a specific tick.
* @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve liquidity for.
* @return liquidityGross The total position liquidity that references this tick
* @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
*/
function getTickLiquidity(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (uint128 liquidityGross, int128 liquidityNet)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
bytes32 value = manager.extsload(slot);
assembly ("memory-safe") {
liquidityNet := sar(128, value)
liquidityGross := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
/**
* @notice Retrieves the fee growth outside a tick range of a pool
* @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve fee growth for.
* @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
* @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
*/
function getTickFeeGrowthOutside(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
// offset by 1 word, since the first word is liquidityGross + liquidityNet
bytes32[] memory data = manager.extsload(bytes32(uint256(slot) + 1), 2);
assembly ("memory-safe") {
feeGrowthOutside0X128 := mload(add(data, 32))
feeGrowthOutside1X128 := mload(add(data, 64))
}
}
/**
* @notice Retrieves the global fee growth of a pool.
* @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return feeGrowthGlobal0 The global fee growth for token0.
* @return feeGrowthGlobal1 The global fee growth for token1.
*/
function getFeeGrowthGlobals(IPoolManager manager, PoolId poolId)
internal
view
returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State, `uint256 feeGrowthGlobal0X128`
bytes32 slot_feeGrowthGlobal0X128 = bytes32(uint256(stateSlot) + FEE_GROWTH_GLOBAL0_OFFSET);
// read the 2 words of feeGrowthGlobal
bytes32[] memory data = manager.extsload(slot_feeGrowthGlobal0X128, 2);
assembly ("memory-safe") {
feeGrowthGlobal0 := mload(add(data, 32))
feeGrowthGlobal1 := mload(add(data, 64))
}
}
/**
* @notice Retrieves total the liquidity of a pool.
* @dev Corresponds to pools[poolId].liquidity
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return liquidity The liquidity of the pool.
*/
function getLiquidity(IPoolManager manager, PoolId poolId) internal view returns (uint128 liquidity) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `uint128 liquidity`
bytes32 slot = bytes32(uint256(stateSlot) + LIQUIDITY_OFFSET);
liquidity = uint128(uint256(manager.extsload(slot)));
}
/**
* @notice Retrieves the tick bitmap of a pool at a specific tick.
* @dev Corresponds to pools[poolId].tickBitmap[tick]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve the bitmap for.
* @return tickBitmap The bitmap of the tick.
*/
function getTickBitmap(IPoolManager manager, PoolId poolId, int16 tick)
internal
view
returns (uint256 tickBitmap)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(int16 => uint256) tickBitmap;`
bytes32 tickBitmapMapping = bytes32(uint256(stateSlot) + TICK_BITMAP_OFFSET);
// slot id of the mapping key: `pools[poolId].tickBitmap[tick]
bytes32 slot = keccak256(abi.encodePacked(int256(tick), tickBitmapMapping));
tickBitmap = uint256(manager.extsload(slot));
}
/**
* @notice Retrieves the position information of a pool without needing to calculate the `positionId`.
* @dev Corresponds to pools[poolId].positions[positionId]
* @param poolId The ID of the pool.
* @param owner The owner of the liquidity position.
* @param tickLower The lower tick of the liquidity range.
* @param tickUpper The upper tick of the liquidity range.
* @param salt The bytes32 randomness to further distinguish position state.
* @return liquidity The liquidity of the position.
* @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
* @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
*/
function getPositionInfo(
IPoolManager manager,
PoolId poolId,
address owner,
int24 tickLower,
int24 tickUpper,
bytes32 salt
) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) {
// positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
bytes32 positionKey = Position.calculatePositionKey(owner, tickLower, tickUpper, salt);
(liquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) = getPositionInfo(manager, poolId, positionKey);
}
/**
* @notice Retrieves the position information of a pool at a specific position ID.
* @dev Corresponds to pools[poolId].positions[positionId]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param positionId The ID of the position.
* @return liquidity The liquidity of the position.
* @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
* @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
*/
function getPositionInfo(IPoolManager manager, PoolId poolId, bytes32 positionId)
internal
view
returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
{
bytes32 slot = _getPositionInfoSlot(poolId, positionId);
// read all 3 words of the Position.State struct
bytes32[] memory data = manager.extsload(slot, 3);
assembly ("memory-safe") {
liquidity := mload(add(data, 32))
feeGrowthInside0LastX128 := mload(add(data, 64))
feeGrowthInside1LastX128 := mload(add(data, 96))
}
}
/**
* @notice Retrieves the liquidity of a position.
* @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieiving liquidity as compared to getPositionInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param positionId The ID of the position.
* @return liquidity The liquidity of the position.
*/
function getPositionLiquidity(IPoolManager manager, PoolId poolId, bytes32 positionId)
internal
view
returns (uint128 liquidity)
{
bytes32 slot = _getPositionInfoSlot(poolId, positionId);
liquidity = uint128(uint256(manager.extsload(slot)));
}
/**
* @notice Calculate the fee growth inside a tick range of a pool
* @dev pools[poolId].feeGrowthInside0LastX128 in Position.State is cached and can become stale. This function will calculate the up to date feeGrowthInside
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tickLower The lower tick of the range.
* @param tickUpper The upper tick of the range.
* @return feeGrowthInside0X128 The fee growth inside the tick range for token0.
* @return feeGrowthInside1X128 The fee growth inside the tick range for token1.
*/
function getFeeGrowthInside(IPoolManager manager, PoolId poolId, int24 tickLower, int24 tickUpper)
internal
view
returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128)
{
(uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = getFeeGrowthGlobals(manager, poolId);
(uint256 lowerFeeGrowthOutside0X128, uint256 lowerFeeGrowthOutside1X128) =
getTickFeeGrowthOutside(manager, poolId, tickLower);
(uint256 upperFeeGrowthOutside0X128, uint256 upperFeeGrowthOutside1X128) =
getTickFeeGrowthOutside(manager, poolId, tickUpper);
(, int24 tickCurrent,,) = getSlot0(manager, poolId);
unchecked {
if (tickCurrent < tickLower) {
feeGrowthInside0X128 = lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
} else if (tickCurrent >= tickUpper) {
feeGrowthInside0X128 = upperFeeGrowthOutside0X128 - lowerFeeGrowthOutside0X128;
feeGrowthInside1X128 = upperFeeGrowthOutside1X128 - lowerFeeGrowthOutside1X128;
} else {
feeGrowthInside0X128 = feeGrowthGlobal0X128 - lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = feeGrowthGlobal1X128 - lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
}
}
}
function _getPoolStateSlot(PoolId poolId) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PoolId.unwrap(poolId), POOLS_SLOT));
}
function _getTickInfoSlot(PoolId poolId, int24 tick) internal pure returns (bytes32) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(int24 => TickInfo) ticks`
bytes32 ticksMappingSlot = bytes32(uint256(stateSlot) + TICKS_OFFSET);
// slot key of the tick key: `pools[poolId].ticks[tick]
return keccak256(abi.encodePacked(int256(tick), ticksMappingSlot));
}
function _getPositionInfoSlot(PoolId poolId, bytes32 positionId) internal pure returns (bytes32) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(bytes32 => Position.State) positions;`
bytes32 positionMapping = bytes32(uint256(stateSlot) + POSITIONS_OFFSET);
// slot of the mapping key: `pools[poolId].positions[positionId]
return keccak256(abi.encodePacked(positionId, positionMapping));
}
}
"
},
"lib/v4-core/src/types/Currency.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";
type Currency is address;
using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;
function equals(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(other);
}
function greaterThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) > Currency.unwrap(other);
}
function lessThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) < Currency.unwrap(other);
}
function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) >= Currency.unwrap(other);
}
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ("memory-safe") {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
}
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}
function balanceOfSelf(Currency currency) internal view returns (uint256) {
if (currency.isAddressZero()) {
return address(this).balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
function toId(Currency currency) internal pure returns (uint256) {
return uint160(Currency.unwrap(currency));
}
// If the upper 12 bytes are non-zero, they will be zero-ed out
// Therefore, fromId() and toId() are not inverses of each other
function fromId(uint256 id) internal pure returns (Currency) {
return Currency.wrap(address(uint160(id)));
}
}
"
},
"lib/v4-core/src/interfaces/IHooks.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {IPoolManager} from "./IPoolManager.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external returns (bytes4, BeforeSwapDelta, uint24);
/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external returns (bytes4, int128);
/// @notice The hook called before donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
}
"
},
"lib/v4-core/src/libraries/TickMath.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
using CustomRevert for bytes4;
/// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
error InvalidTick(int24 tick);
/// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
error InvalidSqrtPrice(uint160 sqrtPriceX96);
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MAX_TICK = 887272;
/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
/// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
int24 internal constant MAX_TICK_SPACING = type(int16).max;
/// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MAX_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Given a tickSpacing, compute the minimum usable tick
function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MIN_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
/// at the given tick
function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick;
assembly ("memory-safe") {
tick := signextend(2, tick)
// mask = 0 if tick >= 0 else -1 (all 1s)
let mask := sar(255, tick)
// if tick >= 0, |tick| = tick = 0 ^ tick
// if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
// either way, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);
// The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
// is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer
// Equivalent to:
// price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
// or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
uint256 price;
assembly ("memory-safe") {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
assembly ("memory-safe") {
// if (tick > 0) price = type(uint256).max / price;
if sgt(tick, 0) { price := div(not(0), price) }
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtPrice of the output price is always consistent
// `sub(shl(32, 1), 1)` is `type(uint32).max`
// `price + type(uint32).max` will not overflow because `price` fits in 192 bits
sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
}
}
}
/// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be >= because the price can never reach the price at the max tick
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}
uint256 price = uint256(sqrtPriceX96) << 32;
uint256 r = price;
uint256 msb = BitMath.mostSignificantBit(r);
if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly ("memory-safe") {
Submitted on: 2025-10-02 19:45:01
Comments
Log in to comment.
No comments yet.