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/Lunastrategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⠚⠉⠩⠍⠩⠍⢩⣶⣦⣤⡀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⡠⡲⠑⢈⣨⠵⠊⠁⠀⠀⠀⠈⠲⢌⡻⣿⣶⣄⡀⠀⠀⠀⠀
⠀⠀⠀⣠⡾⠊⠈⠉⠉⣑⣀⣀⠀⠀⠀⠀⠀⡶⢄⡈⢻⣿⠟⠻⣄⠀⠀⠀
⠀⠀⡐⡑⠁⢀⠏⢠⢞⠕⠚⠉⢻⣏⠀⠀⠀⠑⠀⢱⠀⠉⢇⠀⢹⣦⠀⠀ - Luna Strategy -
⠀⠰⣼⠀⠀⠀⢰⡎⠁⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠃⠀⠀⠈⠘⡟⢿⡇⠀ https://x.com/lunastrategy
⠀⢷⡿⢰⠓⠀⠀⢣⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠐⠀⢄⠄⠄⢣⣸⡿⠀ https://lunastrategy.xyz/
⠘⣸⠁⠸⠔⢀⡀⠀⠳⠦⢤⡶⠂⠀⠀⠀⠀⠀⠀⠀⠀⡀⠣⡆⠀⣿⣷⠂
⠀⣿⠀⠀⠀⠈⠁⠀⠀⠀⠀⠓⠀⠀⠀⠀⠀⠀⠀⠐⠁⠀⣀⢀⠿⢿⣿⠀
⠀⠸⡇⢀⠀⠀⡀⠀⠀⠀⠀⢄⠀⠀⠀⡄⠀⠀⠀⠀⢀⡞⢁⠄⠀⣼⡇⠀
⠀⠀⠻⡌⢆⠰⡠⠐⠈⠀⣤⠜⠒⢢⠀⠀⠀⠢⠄⢀⣈⣄⢾⢴⡿⡟⠀⠀
⠀⠀⠀⠹⣌⡿⢄⠀⠀⠀⠣⣄⢀⠶⠃⠀⢀⣀⣀⣤⣿⢿⣶⣯⠊⠀⠀⠀
⠀⠀⠀⠀⠈⠛⢷⣝⡢⢔⡀⠈⠂⠤⠤⠀⢉⣹⠿⣫⣴⡿⠛⠁⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠉⠛⠲⠤⣷⣦⣶⣶⣞⣛⠛⠿⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
*/
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
interface IUniversalRouter {
function execute(
bytes calldata commands,
bytes[] calldata inputs,
uint256 deadline
) external payable;
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
interface IUniswapV2Router02 {
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function getAmountsOut(uint amountIn, address[] calldata path)
external view returns (uint[] memory amounts);
function WETH() external pure returns (address);
}
library Commands {
uint256 internal constant V4_SWAP = 0x10;
}
contract Lunastrategy is ERC20, Ownable {
using StateLibrary for IPoolManager;
using PoolIdLibrary for PoolKey;
/* ═══════════════════════════════════════════════════ */
/* CONSTANTS */
/* ═══════════════════════════════════════════════════ */
IPositionManager private immutable POSM;
IAllowanceTransfer private immutable PERMIT2;
IUniversalRouter private constant UNIVERSAL_ROUTER =
IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af);
IERC20 private constant LUNA = IERC20(0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9);
IUniswapV2Router02 private constant UNISWAP_V2_ROUTER = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address private constant LUNA_WETH_POOL = 0x60a39010e4892b862d1bb6bdDe908215Ac5af6F3;
uint24 private constant POOL_FEE = 10000; // 1%
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;
/* ═══════════════════════════════════════════════════ */
/* STATE VARIABLES */
/* ═══════════════════════════════════════════════════ */
// Uniswap V4 Pool variables
bool public loadingLiquidity;
bool public poolInitialized;
PoolKey public poolKey;
PoolId public poolId;
int24 public tickLower;
int24 public tickUpper;
uint256 public positionTokenId;
// Fee tracking
uint256 public currentFees;
struct LUNAOrder {
uint256 amount; // Amount of LUNA tokens
uint256 buyPriceX96; // Price at purchase (sqrtPriceX96)
uint256 timestamp; // When the order was created
bool active; // Whether the order is still active
}
uint256 public nextOrderId;
mapping(uint256 => LUNAOrder) public LUNAOrders;
/* ═══════════════════════════════════════════════════ */
/* EVENTS */
/* ═══════════════════════════════════════════════════ */
event LUNABought(
uint256 indexed orderId,
uint256 amountLUNA,
uint256 ethSpent,
uint256 buyPriceX96
);
event LUNASold(
uint256 indexed orderId,
uint256 amountLUNA,
uint256 ethReceived,
uint256 sellPriceX96,
uint256 buyPriceX96
);
constructor(
IPositionManager _posm,
IAllowanceTransfer _permit2
) {
POSM = _posm;
PERMIT2 = _permit2;
_initializeOwner(msg.sender);
_mint(address(this), MAX_SUPPLY);
}
function symbol() public pure override returns (string memory) {
return "LSTR";
}
function name() public pure override returns (string memory) {
return "Luna Strategy";
}
/// @notice Load initial liquidity into the pool
/// @dev Must be called after contract deployment
function loadliq() external onlyOwner {
loadingLiquidity = true;
// Create the pool with ETH (currency0) and TOKEN (currency1)
Currency currency0 = Currency.wrap(address(0)); // ETH
Currency currency1 = Currency.wrap(address(this)); // TOKEN
uint24 lpFee = 100000; // 10% fee
int24 tickSpacing = 200;
uint256 token0Amount = 0; // 0 ETH
uint256 token1Amount = MAX_SUPPLY;
uint160 startingPrice = 2045645379722529521098596513701367;
tickLower = int24(-887200);
tickUpper = int24(203000);
PoolKey memory key = PoolKey(
currency0,
currency1,
lpFee,
tickSpacing,
IHooks(address(0))
);
// Store pool information
poolKey = key;
poolId = key.toId();
bytes memory hookData = new bytes(0);
uint128 liquidity = 39095916497508424169487;
(
bytes memory actions,
bytes[] memory mintParams
) = _mintLiquidityParams(
key,
tickLower,
tickUpper,
liquidity,
token0Amount,
token1Amount,
address(this),
hookData
);
bytes[] memory params = new bytes[](2);
params[0] = abi.encodeWithSelector(
POSM.initializePool.selector,
key,
startingPrice,
hookData
);
params[1] = abi.encodeWithSelector(
POSM.modifyLiquidities.selector,
abi.encode(actions, mintParams),
block.timestamp + 60
);
uint256 valueToPass = token0Amount;
// Approve Permit2 to spend our tokens
_approve(address(this), address(PERMIT2), type(uint256).max);
PERMIT2.approve(
address(this),
address(POSM),
type(uint160).max,
type(uint48).max
);
// Get the next token ID before minting
positionTokenId = POSM.nextTokenId();
POSM.multicall{value: valueToPass}(params);
loadingLiquidity = false;
poolInitialized = true;
poolKey = key;
poolId = key.toId();
}
/// @notice Renounces ownership of the contract, making it uncontrollable
/// @dev This is irreversible - use with extreme caution
function renounceOwnership() public payable virtual override onlyOwner {
_setOwner(address(0));
}
/// @notice Creates parameters for minting liquidity in Uniswap V4
function _mintLiquidityParams(
PoolKey memory key,
int24 _tickLower,
int24 _tickUpper,
uint256 liquidity,
uint256 amount0Max,
uint256 amount1Max,
address recipient,
bytes memory hookData
) internal pure returns (bytes memory, bytes[] memory) {
bytes memory actions = abi.encodePacked(
uint8(Actions.MINT_POSITION),
uint8(Actions.SETTLE_PAIR)
);
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(
key,
_tickLower,
_tickUpper,
liquidity,
amount0Max,
amount1Max,
recipient,
hookData
);
params[1] = abi.encode(key.currency0, key.currency1);
return (actions, params);
}
/// @notice Buy LUNA tokens using all contract's ETH balance via Uniswap V2
/// @dev Swaps all available ETH for LUNA on Uniswap V2 with 20% slippage protection
/// @dev Requires minimum 1 ETH in contract
function buyLUNA() external {
// Use entire ETH balance of the contract
uint256 amountIn = address(this).balance;
require(amountIn >= 1 ether, "Minimum 1 ETH required in contract");
// Record LUNA balance before swap
uint256 LUNABalanceBefore = LUNA.balanceOf(address(this));
// Set up the swap path: ETH -> WETH -> LUNA
address[] memory path = new address[](2);
path[0] = UNISWAP_V2_ROUTER.WETH();
path[1] = address(LUNA);
// Get expected output amount from Uniswap V2
uint[] memory expectedAmounts = UNISWAP_V2_ROUTER.getAmountsOut(amountIn, path);
uint256 expectedLUNAOut = expectedAmounts[1];
// Calculate minimum output with 20% slippage protection
uint256 minAmountOut = (expectedLUNAOut * 80) / 100; // 20% slippage tolerance
// Execute ETH -> LUNA swap on Uniswap V2
uint[] memory amounts = UNISWAP_V2_ROUTER.swapExactETHForTokens{value: amountIn}(
minAmountOut,
path,
address(this),
block.timestamp + 300 // 5 minutes deadline
);
// Verify minimum LUNA tokens were received with slippage protection
uint256 LUNABalanceAfter = LUNA.balanceOf(address(this));
uint256 actualLUNAReceived = LUNABalanceAfter - LUNABalanceBefore;
require(
actualLUNAReceived >= minAmountOut,
"Insufficient LUNA received (slippage protection)"
);
// Create sell order if balance >= 1 LUNA
if (LUNABalanceAfter >= 1e18) {
// Record current pool price
(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(LUNA_WETH_POOL).slot0();
// Store order details
LUNAOrders[nextOrderId] = LUNAOrder({
amount: LUNABalanceAfter,
buyPriceX96: uint256(sqrtPriceX96),
timestamp: block.timestamp,
active: true
});
emit LUNABought(
nextOrderId,
LUNABalanceAfter,
amountIn,
uint256(sqrtPriceX96)
);
nextOrderId++;
}
}
/// @notice Emergency function to withdraw all ETH and LUNA tokens from the contract
/// @dev Only callable by owner. Exists in case of a critical issue where tokens get stuck in the contract
function emergencyWithdraw() external onlyOwner {
// Withdraw ETH balance
uint256 contractBalance = address(this).balance;
if (contractBalance > 0) {
(bool success, ) = payable(owner()).call{value: contractBalance}("");
require(success, "Emergency ETH withdrawal failed");
}
// Withdraw LUNA token balance
uint256 LUNABalance = LUNA.balanceOf(address(this));
if (LUNABalance > 0) {
bool LUNASuccess = LUNA.transfer(owner(), LUNABalance);
require(LUNASuccess, "Emergency LUNA withdrawal failed");
}
require(contractBalance > 0 || LUNABalance > 0, "No ETH or LUNA to withdraw");
}
/// @notice Sell LUNA tokens if price has +10% value since purchase
/// @dev Checks if current price >= 110% of purchase price, then sells via Uniswap V2 and burns
/// @param orderId The ID of the order to check and potentially sell
function sellLUNA(uint256 orderId) external {
LUNAOrder storage order = LUNAOrders[orderId];
require(order.active, "Order is not active");
require(order.amount > 0, "Order amount is zero");
// Fetch current pool price
(uint160 currentSqrtPriceX96, , , , , , ) = IUniswapV3Pool(LUNA_WETH_POOL).slot0();
uint256 sqrt110PercentX96 = 83076749736557242056487941;
uint256 targetSqrtPriceX96 = (order.buyPriceX96 * sqrt110PercentX96) >> 96;
require(
uint256(currentSqrtPriceX96) >= targetSqrtPriceX96,
"Price has not increased by 10% yet"
);
// Deactivate order
order.active = false;
// Record ETH balance before swap
uint256 ethBalanceBefore = address(this).balance;
// Approve Uniswap V2 Router to spend LUNA tokens
LUNA.approve(address(UNISWAP_V2_ROUTER), order.amount);
// Set up the swap path: LUNA -> WETH -> ETH
address[] memory path = new address[](2);
path[0] = address(LUNA);
path[1] = UNISWAP_V2_ROUTER.WETH();
// Execute LUNA -> ETH swap on Uniswap V2
UNISWAP_V2_ROUTER.swapExactTokensForETH(
order.amount,
0, // Accept any amount of ETH
path,
address(this),
block.timestamp + 300 // 5 minutes deadline
);
// Calculate ETH proceeds from sale
uint256 ethBalanceAfter = address(this).balance;
uint256 ethReceived = ethBalanceAfter - ethBalanceBefore;
emit LUNASold(
orderId,
order.amount,
ethReceived,
uint256(currentSqrtPriceX96),
order.buyPriceX96
);
// Buy and burn strategy tokens with ETH proceeds
if (ethReceived > 0) {
buyAndBurnToken(ethReceived);
}
}
/// @notice Get current LUNA price from Uniswap V3 pool
/// @return sqrtPriceX96 Current sqrt price
function getCurrentLUNAPrice() external view returns (uint160 sqrtPriceX96) {
(sqrtPriceX96, , , , , , ) = IUniswapV3Pool(LUNA_WETH_POOL).slot0();
return sqrtPriceX96;
}
/// @notice Get accumulated fees in the pool using StateLibrary
/// @param _poolKey The pool key to check fees for
/// @return fees0 Global fee growth for token0 (ETH)
/// @return fees1 Global fee growth for token1 (Our token)
function getFees(
PoolKey memory _poolKey
) external view returns (uint256 fees0, uint256 fees1) {
// Use our contract's position token ID and tick range
uint256 tokenId = positionTokenId;
int24 _tickLower = tickLower;
int24 _tickUpper = tickUpper;
// Get pool manager
IPoolManager poolManager = POSM.poolManager();
PoolId _poolId = _poolKey.toId();
// Get position info from pool manager
// The position is owned by POSM (position manager) with tokenId as salt
(
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128
) = poolManager.getPositionInfo(
_poolId,
address(POSM),
_tickLower,
_tickUpper,
bytes32(tokenId)
);
// Get current fee growth inside the position range using StateLibrary
(
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) = poolManager.getFeeGrowthInside(_poolId, _tickLower, _tickUpper);
// Calculate fees owed using the same formula as Uniswap
fees0 =
((feeGrowthInside0X128 - feeGrowthInside0LastX128) * liquidity) /
(1 << 128);
fees1 =
((feeGrowthInside1X128 - feeGrowthInside1LastX128) * liquidity) /
(1 << 128);
return (fees0, fees1);
}
function collectFees() external returns (uint256 ethFees, uint256 tokenFees) {
(ethFees, tokenFees) = this.getFees(poolKey);
// If no fees, no need to collect
if (ethFees == 0 && tokenFees == 0) {
return (0, 0);
}
// In V4, we collect fees by decreasing liquidity and collecting the fees
// from our LP position since we are the LP provider
// Use our contract's position token ID directly
uint256 tokenId = positionTokenId;
// Use DECREASE_LIQUIDITY with 0 liquidity to collect fees only
bytes memory actions = abi.encodePacked(
uint8(Actions.DECREASE_LIQUIDITY),
uint8(Actions.TAKE_PAIR)
);
bytes[] memory params = new bytes[](2);
// Parameters for DECREASE_LIQUIDITY (collect fees without removing liquidity)
params[0] = abi.encode(
tokenId,
0, // liquidityDelta = 0 (don't remove liquidity, just collect fees)
0, // amount0Min = 0
0, // amount1Min = 0
"" // hookData
);
// Parameters for TAKE_PAIR - transfer fees to this contract
params[1] = abi.encode(
poolKey.currency0, // ETH
poolKey.currency1, // Our token (this contract)
address(this) // recipient
);
// Execute the fee collection through Position Manager
POSM.modifyLiquidities(
abi.encode(actions, params),
block.timestamp + 60
);
// Distribute ETH fees: 90% to contract, 10% to owner
if (ethFees > 0) {
uint256 ownerShare = (ethFees * 10) / 100; // 10%
uint256 contractShare = ethFees - ownerShare; // 90%
// Send 10% to owner
if (ownerShare > 0) {
(bool success, ) = payable(owner()).call{value: ownerShare}("");
require(success, "Transfer to owner failed");
}
// 90% stays in the contract (contractShare remains in address(this))
currentFees += contractShare;
}
// Send 100% of token fees to owner
if (tokenFees > 0) {
_transfer(address(this), owner(), tokenFees);
}
return (ethFees, tokenFees);
}
/* ═══════════════════════════════════════════════════ */
/* INTERNAL FUNCTIONS */
/* ═══════════════════════════════════════════════════ */
/// @notice Buys strategy tokens with ETH and burns them via Universal Router V4
/// @param amountIn Amount of ETH to use for buying and burning tokens
function buyAndBurnToken(uint256 amountIn) internal {
if (amountIn == 0) return;
require(amountIn <= address(this).balance, "Insufficient contract balance");
// Exit if pool not initialized
if (!poolInitialized) {
return;
}
uint256 minTokensOut = (amountIn * 9) / 10; // 10% slippage tolerance
// Record token balance before swap
uint256 contractBalanceBefore = balanceOf(address(this));
uint128 safeAmountIn = uint128(amountIn);
uint128 safeMinTokensOut = uint128(minTokensOut);
_swapExactInputSingleV4(
poolKey,
safeAmountIn,
safeMinTokensOut
);
uint256 contractBalanceAfter = balanceOf(address(this));
uint256 tokensReceived = contractBalanceAfter - contractBalanceBefore;
// Burn received tokens
if (tokensReceived > 0) {
_transfer(address(this), DEADADDRESS, tokensReceived);
}
}
/// @notice Executes a V4 swap via Universal Router
function _swapExactInputSingleV4(
PoolKey memory key,
uint128 amountIn,
uint128 minAmountOut
) internal returns (uint256 amountOut) {
// Encode the Universal Router command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Encode V4Router actions
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE),
uint8(Actions.SETTLE_ALL),
uint8(Actions.TAKE_ALL)
);
// Prepare parameters for each action
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: key,
zeroForOne: true, // ETH (currency0) -> TOKEN (currency1)
amountIn: amountIn,
amountOutMinimum: minAmountOut,
hookData: ""
})
);
params[1] = abi.encode(key.currency0, amountIn);
params[2] = abi.encode(key.currency1, minAmountOut);
// Combine actions and params into inputs
inputs[0] = abi.encode(actions, params);
// Execute swap with deadline protection (5 minutes)
uint256 deadline = block.timestamp + 300;
UNIVERSAL_ROUTER.execute{value: amountIn}(commands, inputs, deadline);
// Verify output meets minimum requirement
amountOut = balanceOf(address(this));
require(amountOut >= minAmountOut, "Insufficient output amount");
return amountOut;
}
/// @notice Allows the contract to receive ETH
receive() external payable {}
}"
},
"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
/// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
/// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
/// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
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/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/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-periphery/src/interfaces/IV4Router.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PathKey} from "../libraries/PathKey.sol";
import {IImmutableState} from "./IImmutableState.sol";
/// @title IV4Router
/// @notice Interface for the V4Router contract
interface IV4Router is IImmutableState {
/// @notice Emitted when an exactInput swap does not receive its minAmountOut
error V4TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived);
/// @notice Emitted when an exactOutput is asked for more than its maxAmountIn
error V4TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested);
/// @notice Parameters for a single-hop exact-input swap
struct ExactInputSingleParams {
PoolKey poolKey;
bool zeroForOne;
uint128 amountIn;
uint128 amountOutMinimum;
bytes hookData;
}
/// @notice Parameters for a multi-hop exact-input swap
struct ExactInputParams {
Currency currencyIn;
PathKey[] path;
uint128 amountIn;
uint128 amountOutMinimum;
}
/// @notice Parameters for a single-hop exact-output swap
struct ExactOutputSingleParams {
PoolKey poolKey;
bool zeroForOne;
uint128 amountOut;
uint128 amountInMaximum;
bytes hookData;
}
/// @notice Parameters for a multi-hop exact-output swap
struct ExactOutputParams {
Currency currencyOut;
PathKey[] path;
uint128 amountOut;
uint128 amountInMaximum;
}
}
"
},
"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.
* @dev Note that feeGrowthGlobal can be artificially inflated
* For pools with a single liquidity position, actors can donate to themselves to freely inflate feeGrowthGlobal
* atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
*/
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/solady/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is inv
Submitted on: 2025-10-07 14:21:36
Comments
Log in to comment.
No comments yet.