Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/UniswapV3PositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "lib/v3-core/contracts/libraries/TickMath.sol";
import "lib/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "src/IUniswapV3PositionManager.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
contract UniswapV3PositionManager is IUniswapV3PositionManager, AccessControl {
using Address for address;
using SafeMath for uint256;
struct CompoundState {
uint256 amount0;
uint256 amount1;
uint256 maxAddAmount0;
uint256 maxAddAmount1;
uint256 priceX96;
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
}
struct FeeCalculationState {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
int24 tickCurrent;
uint128 liquidity;
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
uint128 tokensOwed0;
uint128 tokensOwed1;
uint256 feeGrowthGlobal0X128;
uint256 feeGrowthGlobal1X128;
uint256 feeGrowthOutside0LowerX128;
uint256 feeGrowthOutside1LowerX128;
uint256 feeGrowthOutside0UpperX128;
uint256 feeGrowthOutside1UpperX128;
uint256 feeGrowthInside0X128;
uint256 feeGrowthInside1X128;
}
struct NewPositionState {
uint256 amount0;
uint256 amount1;
uint256 amount0Desired;
uint256 amount1Desired;
}
struct SwapState {
uint256 rewardAmount0;
uint256 rewardAmount1;
uint256 positionAmount0;
uint256 positionAmount1;
int24 tick;
int24 otherTick;
uint160 sqrtPriceX96;
uint160 sqrtPriceX96Lower;
uint160 sqrtPriceX96Upper;
uint256 amountRatioX96;
uint256 delta0;
uint256 delta1;
bool sell0;
bool twapOk;
uint256 totalReward0;
}
struct SwapParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0;
uint256 amount1;
uint256 deadline;
bool doSwap;
}
struct PositionConfig {
uint32 maxTWAPTickDifference; // 4 bytes
uint32 TWAPSeconds; // 4 bytes
int24 defaultTickLower; // 3 bytes
int24 defaultTickUpper; // 3 bytes
// Total: 14 bytes in first slot (saves 18 bytes vs separate storage)
}
uint128 constant Q96 = 2 ** 96;
bytes32 public constant MULTI_CALL_ROLE = keccak256("MULTI_CALL_ROLE");
address public immutable override anchorToken;
address public immutable override otherToken;
address public immutable override token0;
address public immutable override token1;
uint24 public immutable override feeTier;
IUniswapV3Pool public immutable pool;
// Uniswap V3 Components
IUniswapV3Factory public immutable override factory;
INonfungiblePositionManager
public immutable
override nonfungiblePositionManager;
ISwapRouter public immutable swapRouter;
PositionConfig public config;
// managing position
uint256 public override positionTokenID;
modifier onlyAdminOrMulticallRoles() {
require(
hasRole(DEFAULT_ADMIN_ROLE, msg.sender) ||
hasRole(MULTI_CALL_ROLE, msg.sender),
"Not admin or multicall"
);
_;
}
modifier onlyAdmin() {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");
_;
}
constructor(
IUniswapV3Factory _factory,
INonfungiblePositionManager _nonFungiblePositionManager,
address admin,
address multiCallContract,
address anchorTokenAddress,
address otherTokenAddress,
uint24 _feeTier,
ISwapRouter _swapRouter,
int24 defaultLowerTick,
int24 defaultUpperTick
) {
require(address(_factory) != address(0), "Invalid factory address");
require(
address(_nonFungiblePositionManager) != address(0),
"Invalid non-fungible position manager"
);
require(admin != address(0), "Invalid admin address");
require(
multiCallContract != address(0),
"Invalid multicall contract address"
);
require(
anchorTokenAddress != address(0),
"Invalid anchor token address"
);
require(otherTokenAddress != address(0), "Invalid other token address");
require(_feeTier != 0, "Invalid fee tier");
require(
address(_swapRouter) != address(0),
"Invalid swap router address"
);
require(
defaultUpperTick > defaultLowerTick &&
defaultLowerTick != defaultUpperTick,
"Invalid Upper Lower Tick"
);
config.defaultTickLower = defaultLowerTick;
config.defaultTickUpper = defaultUpperTick;
config.maxTWAPTickDifference = 100;
config.TWAPSeconds = 0; // Disabled for testing
factory = _factory;
anchorToken = anchorTokenAddress;
otherToken = otherTokenAddress;
feeTier = _feeTier;
address t0 = anchorTokenAddress < otherTokenAddress
? anchorTokenAddress
: otherTokenAddress;
address t1 = anchorTokenAddress < otherTokenAddress
? otherTokenAddress
: anchorTokenAddress;
token0 = t0;
token1 = t1;
address poolAddress = _factory.getPool(t0, t1, _feeTier);
require(poolAddress != address(0), "Pool does not exist");
pool = IUniswapV3Pool(poolAddress);
nonfungiblePositionManager = _nonFungiblePositionManager;
_setupRole(DEFAULT_ADMIN_ROLE, admin);
_setupRole(MULTI_CALL_ROLE, multiCallContract);
swapRouter = _swapRouter;
}
function setDefaultTicks(
int24 _lower,
int24 _upper
) external override onlyAdmin {
config.defaultTickLower = _lower;
config.defaultTickUpper = _upper;
}
function setTWAPSettings(
uint32 _seconds,
uint32 _maxTickDifference
) external override onlyAdmin {
config.TWAPSeconds = _seconds;
config.maxTWAPTickDifference = _maxTickDifference;
}
// Getter functions for config struct fields to maintain interface compatibility
function defaultTickLower() external view override returns (int24) {
return config.defaultTickLower;
}
function defaultTickUpper() external view override returns (int24) {
return config.defaultTickUpper;
}
function TWAPSeconds() external view returns (uint32) {
return config.TWAPSeconds;
}
function maxTWAPTickDifference() external view returns (uint32) {
return config.maxTWAPTickDifference;
}
function maintenance() external override {
// if we have balances of either token but not token id open a new position
if (positionTokenID == 0) {
uint256 amount0 = IERC20(token0).balanceOf(address(this));
uint256 amount1 = IERC20(token1).balanceOf(address(this));
if (amount0 == 0 && amount1 == 0) {
return;
}
_openNewPosition(amount0, amount1);
} else {
// otherwise compound existing position
_compoundPosition();
}
}
/**
* @notice Removes liquidity from the position based on a specified amount of the anchor token
* @param amount the amount of token to remove in terms of the anchor token
* @param tokenRecipient the address the tokens will go to
* @param slippage the maximum slippage allowed in basis points (e.g., 100 = 1%)
* @return anchorAmountReceived the amount of anchor token received
*/
function removeLiquidityByAnchorTokenAmount(
uint256 amount,
address tokenRecipient,
uint16 slippage
)
external
override
onlyAdminOrMulticallRoles
returns (uint256 anchorAmountReceived)
{
require(positionTokenID != 0, "no position");
if (tokenRecipient == address(0)) tokenRecipient = address(this);
uint16 basisPoints = _calculateBasisPointsForAmount(amount);
// remove liquidity by that percentage - call internal function
(, , anchorAmountReceived) = _removeLiquidityByBasisPoints(
basisPoints,
tokenRecipient,
slippage
);
return anchorAmountReceived;
}
/**
* @notice Internal helper to calculate basis points for a given anchor token amount
* @param amount The amount of anchor token to remove
* @return basisPoints The basis points (0-10000) representing the percentage of liquidity to remove
*/
function _calculateBasisPointsForAmount(
uint256 amount
) internal view returns (uint16 basisPoints) {
// get the position data
(, , , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager
.positions(positionTokenID);
require(liquidity > 0, "no liq");
// get the current price of the pool
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
// get the amounts claimable if we removed 100% (estimation at current price)
(uint256 full0, uint256 full1) = LiquidityAmounts
.getAmountsForLiquidity(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(config.defaultTickLower),
TickMath.getSqrtRatioAtTick(config.defaultTickUpper),
liquidity
);
// convert both amounts to anchor token terms
uint256 priceX96 = uint256(sqrtPriceX96).mul(uint256(sqrtPriceX96)).div(
Q96
);
uint256 fullAnchorAmount;
if (token0 == anchorToken) {
fullAnchorAmount = full0.add(full1.mul(priceX96).div(Q96));
} else {
fullAnchorAmount = full1.add(full0.mul(Q96).div(priceX96));
}
// calculate the percentage of liquidity to remove
uint256 calculatedBasisPoints = amount.mul(10000).div(fullAnchorAmount);
// Cap at 10000 (100%) if the requested amount exceeds position value
basisPoints = uint16(
calculatedBasisPoints > 10000 ? 10000 : calculatedBasisPoints
);
}
/**
* @notice Removes a percentage of liquidity from the position based on basis points
* @dev Basis points: 10000 = 100%, 5000 = 50%, 2500 = 25%, etc.
* @param basisPoints The percentage of liquidity to remove in basis points (0-10000)
* @return amount0 The amount of token0 received
* @return amount1 The amount of token1 received
*/
function removeLiquidityByBasisPoints(
uint16 basisPoints,
address tokenRecipient,
uint16 slippage
)
external
override
onlyAdminOrMulticallRoles
returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut)
{
return
_removeLiquidityByBasisPoints(
basisPoints,
tokenRecipient,
slippage
);
}
/**
* @notice Internal function to remove liquidity by basis points
* @dev Basis points: 10000 = 100%, 5000 = 50%, 2500 = 25%, etc.
* @param basisPoints The percentage of liquidity to remove in basis points (0-10000)
* @return amount0 The amount of token0 received
* @return amount1 The amount of token1 received
* @return anchorAmountOut The amount of anchor token received after swapping
*/
function _removeLiquidityByBasisPoints(
uint16 basisPoints,
address tokenRecipient,
uint16 slippage
)
internal
returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut)
{
require(positionTokenID != 0, "No position exists");
if (tokenRecipient == address(0)) {
tokenRecipient = address(this);
}
(, , , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager
.positions(positionTokenID);
uint256 liquidityToRemove = uint256(liquidity).mul(basisPoints).div(
10000
);
// Get current pool price
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
// calculate expected output amounts from liquidity amounts
// get balance of position
(uint256 amount0Expected, uint256 amount1Expected) = LiquidityAmounts
.getAmountsForLiquidity(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(config.defaultTickLower),
TickMath.getSqrtRatioAtTick(config.defaultTickUpper),
uint128(liquidityToRemove)
);
// decrease liquidity
nonfungiblePositionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: positionTokenID,
liquidity: uint128(liquidityToRemove),
amount0Min: amount0Expected,
amount1Min: amount1Expected,
deadline: type(uint256).max
})
);
// Collect the tokens to this contract first (we need to swap)
(amount0, amount1) = nonfungiblePositionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: positionTokenID,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
emit LiquidityRemoved(
positionTokenID,
uint128(liquidityToRemove),
amount0,
amount1
);
uint256 amountOut;
uint256 minAmountOut;
// swap the other token that we got for anchor token if needed
if (token0 == anchorToken && amount1 > 0) {
minAmountOut = amount1.mul(uint256(10000).sub(slippage)).div(10000);
amountOut = _swap(
abi.encodePacked(token1, feeTier, token0),
amount1,
minAmountOut
);
anchorAmountOut = amountOut.add(amount0);
} else if (token1 == anchorToken && amount0 > 0) {
minAmountOut = amount0.mul(uint256(10000).sub(slippage)).div(10000);
amountOut = _swap(
abi.encodePacked(token0, feeTier, token1),
amount0,
minAmountOut
);
anchorAmountOut = amountOut.add(amount1);
}
// Transfer the anchor token to the recipient
if (tokenRecipient != address(this) && anchorAmountOut > 0) {
_safeTransfer(anchorToken, tokenRecipient, anchorAmountOut);
}
}
/**
* @notice Transfer any ERC20 tokens held by this contract to a recipient
* @param token The address of the ERC20 token to transfer
* @param recipient The address to receive the tokens
* @param amount The amount of tokens to transfer
*/
function transferERC20(
address token,
address recipient,
uint256 amount
) external override onlyAdmin {
require(recipient != address(0), "Invalid recipient address");
_safeTransfer(token, recipient, amount);
}
function delegate(
address target,
bytes calldata data
) external override onlyAdmin returns (bytes memory) {
(bool success, bytes memory result) = target.delegatecall(data);
require(success, "Delegatecall failed");
return result;
}
/**
* @notice Handles the receipt of an NFT
*/
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external view override returns (bytes4) {
require(
msg.sender == address(nonfungiblePositionManager),
"Not Uniswap V3 Position Manager"
);
return this.onERC721Received.selector;
}
/// @notice Get the uncollected fee amounts for the current position
/// @return amount0 The amount of token0 fees owed
/// @return amount1 The amount of token1 fees owed
function getPositionFees()
external
view
override
returns (uint128 amount0, uint128 amount1)
{
require(positionTokenID != 0, "No position exists");
FeeCalculationState memory vars;
// Get position info - split to avoid stack too deep
(
,
,
vars.token0,
vars.token1,
vars.fee,
vars.tickLower,
vars.tickUpper,
vars.liquidity,
,
,
,
) = nonfungiblePositionManager.positions(positionTokenID);
// If there's no liquidity, get tokens owed and return
if (vars.liquidity == 0) {
(
,
,
,
,
,
,
,
,
,
,
vars.tokensOwed0,
vars.tokensOwed1
) = nonfungiblePositionManager.positions(positionTokenID);
return (vars.tokensOwed0, vars.tokensOwed1);
}
// Get fee growth tracking variables separately
(
,
,
,
,
,
,
,
,
vars.feeGrowthInside0LastX128,
vars.feeGrowthInside1LastX128,
vars.tokensOwed0,
vars.tokensOwed1
) = nonfungiblePositionManager.positions(positionTokenID);
// Get the pool to calculate current fee growth
IUniswapV3Pool positionPool = IUniswapV3Pool(
factory.getPool(vars.token0, vars.token1, vars.fee)
);
// Get current global fee growth
vars.feeGrowthGlobal0X128 = positionPool.feeGrowthGlobal0X128();
vars.feeGrowthGlobal1X128 = positionPool.feeGrowthGlobal1X128();
// Get tick info for lower and upper ticks
(
,
,
vars.feeGrowthOutside0LowerX128,
vars.feeGrowthOutside1LowerX128,
,
,
,
) = positionPool.ticks(vars.tickLower);
(
,
,
vars.feeGrowthOutside0UpperX128,
vars.feeGrowthOutside1UpperX128,
,
,
,
) = positionPool.ticks(vars.tickUpper);
// Get current tick
(, vars.tickCurrent, , , , , ) = positionPool.slot0();
// Calculate fee growth inside using the Uniswap V3 formula
vars.feeGrowthInside0X128 = _getFeeGrowthInside(
vars.feeGrowthGlobal0X128,
vars.feeGrowthOutside0LowerX128,
vars.feeGrowthOutside0UpperX128,
vars.tickCurrent,
vars.tickLower,
vars.tickUpper
);
vars.feeGrowthInside1X128 = _getFeeGrowthInside(
vars.feeGrowthGlobal1X128,
vars.feeGrowthOutside1LowerX128,
vars.feeGrowthOutside1UpperX128,
vars.tickCurrent,
vars.tickLower,
vars.tickUpper
);
// Calculate uncollected fees
amount0 =
vars.tokensOwed0 +
uint128(
uint256(vars.liquidity)
.mul(
vars.feeGrowthInside0X128.sub(
vars.feeGrowthInside0LastX128
)
)
.div(2 ** 128)
);
amount1 =
vars.tokensOwed1 +
uint128(
uint256(vars.liquidity)
.mul(
vars.feeGrowthInside1X128.sub(
vars.feeGrowthInside1LastX128
)
)
.div(2 ** 128)
);
}
/// @dev Calculate fee growth inside a tick range
function _getFeeGrowthInside(
uint256 feeGrowthGlobalX128,
uint256 feeGrowthOutsideLowerX128,
uint256 feeGrowthOutsideUpperX128,
int24 tickCurrent,
int24 tickLower,
int24 tickUpper
) private pure returns (uint256 feeGrowthInsideX128) {
uint256 feeGrowthBelowX128;
uint256 feeGrowthAboveX128;
// Calculate fee growth below
if (tickCurrent >= tickLower) {
feeGrowthBelowX128 = feeGrowthOutsideLowerX128;
} else {
feeGrowthBelowX128 = feeGrowthGlobalX128.sub(
feeGrowthOutsideLowerX128
);
}
// Calculate fee growth above
if (tickCurrent < tickUpper) {
feeGrowthAboveX128 = feeGrowthOutsideUpperX128;
} else {
feeGrowthAboveX128 = feeGrowthGlobalX128.sub(
feeGrowthOutsideUpperX128
);
}
// Fee growth inside = global - below - above
feeGrowthInsideX128 = feeGrowthGlobalX128.sub(feeGrowthBelowX128).sub(
feeGrowthAboveX128
);
}
function _ensureApprovals() internal {
// Approve tokens for position manager
_ensureTokenApproval(token0, address(nonfungiblePositionManager));
_ensureTokenApproval(token1, address(nonfungiblePositionManager));
// Approve tokens for swap router
_ensureTokenApproval(token0, address(swapRouter));
_ensureTokenApproval(token1, address(swapRouter));
}
function _ensureTokenApproval(address token, address spender) internal {
try IERC20(token).allowance(address(this), spender) returns (
uint256 currentAllowance
) {
if (currentAllowance < type(uint256).max / 2) {
// For USDT compatibility: reset to 0 first if non-zero
if (currentAllowance > 0) {
_safeApprove(token, spender, 0);
}
_safeApprove(token, spender, type(uint256).max);
}
} catch {
// If allowance check fails, reset to 0 first for USDT compatibility
_safeApprove(token, spender, 0);
_safeApprove(token, spender, type(uint256).max);
}
}
function _openNewPosition(
uint256 amount0,
uint256 amount1
) internal returns (uint256 tokenId) {
// Ensure approvals are set
_ensureApprovals();
// Swap if needed to get desired amounts of both tokens
NewPositionState memory state;
// Swap to optimal ratio and get max amounts to add
uint256 priceX96;
(state.amount0, state.amount1, priceX96) = _swapToPriceRatio(
SwapParams({
token0: token0,
token1: token1,
fee: feeTier,
tickLower: config.defaultTickLower,
tickUpper: config.defaultTickUpper,
amount0: amount0,
amount1: amount1,
deadline: type(uint256).max,
doSwap: true
})
);
(tokenId, , , ) = nonfungiblePositionManager.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: feeTier,
tickLower: config.defaultTickLower,
tickUpper: config.defaultTickUpper,
amount0Desired: state.amount0,
amount1Desired: state.amount1,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: type(uint256).max
})
);
positionTokenID = tokenId;
}
function _compoundPosition() internal {
CompoundState memory state;
// collect fees
nonfungiblePositionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: positionTokenID,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
// Ensure approvals are set
_ensureApprovals();
state.token0 = token0;
state.token1 = token1;
state.fee = feeTier;
state.tickLower = config.defaultTickLower;
state.tickUpper = config.defaultTickUpper;
state.amount0 = IERC20(state.token0).balanceOf(address(this));
state.amount1 = IERC20(state.token1).balanceOf(address(this));
// if there are balances to work with start the compounding process
if (state.amount0 > 0 || state.amount1 > 0) {
// checks oracle for fair price - swaps to position ratio - calculates the max amount to be added
(state.amount0, state.amount1, state.priceX96) = _swapToPriceRatio(
SwapParams({
token0: state.token0,
token1: state.token1,
fee: state.fee,
tickLower: state.tickLower,
tickUpper: state.tickUpper,
amount0: state.amount0,
amount1: state.amount1,
deadline: type(uint256).max,
doSwap: true
})
);
state.maxAddAmount0 = state.amount0;
state.maxAddAmount1 = state.amount1;
}
// deposit liquidity into tokenId
if (state.maxAddAmount0 > 0 || state.maxAddAmount1 > 0) {
nonfungiblePositionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: positionTokenID,
amount0Desired: state.maxAddAmount0,
amount1Desired: state.maxAddAmount1,
amount0Min: 0,
amount1Min: 0,
deadline: type(uint256).max
})
);
}
}
function _getTWAPTick(
uint32 twapPeriod
) internal view returns (int24 twapTick, bool ok) {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 0;
secondsAgos[1] = twapPeriod;
try pool.observe(secondsAgos) returns (
int56[] memory tickCumulatives,
uint160[] memory
) {
return (
int24(
(tickCumulatives[0] - tickCumulatives[1]) /
int56(uint56(twapPeriod))
),
true
);
} catch {
return (0, false);
}
}
function _requireMaxTickDifference(
int24 tick,
int24 other,
uint32 maxDifference
) internal pure {
require(
(other > tick && (uint48(other - tick) < maxDifference)) ||
(other <= tick && (uint48(tick - other) < maxDifference)),
"TWAP price deviation exceeds maximum allowed difference"
);
}
function _swapToPriceRatio(
SwapParams memory params
) internal returns (uint256 amount0, uint256 amount1, uint256 priceX96) {
SwapState memory state;
amount0 = params.amount0;
amount1 = params.amount1;
(state.sqrtPriceX96, state.tick, , , , , ) = pool.slot0();
// seconds needed for TWAP
uint32 tSecs = config.TWAPSeconds;
if (tSecs > 0) {
// check that price is not too far from TWAP
(state.otherTick, state.twapOk) = _getTWAPTick(tSecs);
if (state.twapOk) {
_requireMaxTickDifference(
state.tick,
state.otherTick,
config.maxTWAPTickDifference
);
} else {
params.doSwap = false;
}
}
// (sqrtPriceX96 * sqrtPriceX96) / Q96 = priceX96
priceX96 = uint256(state.sqrtPriceX96)
.mul(uint256(state.sqrtPriceX96))
.div(Q96);
if (params.doSwap) {
//calculate ideal position amounts
state.sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(
params.tickLower
);
state.sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(
params.tickUpper
);
(state.positionAmount0, state.positionAmount1) = LiquidityAmounts
.getAmountsForLiquidity(
state.sqrtPriceX96,
state.sqrtPriceX96Lower,
state.sqrtPriceX96Upper,
Q96
);
// calculate amount needed to be converted to the other token
if (state.positionAmount0 == 0) {
state.delta0 = 0;
state.sell0 = true;
} else if (state.positionAmount1 == 0) {
state.delta0 = amount1.mul(Q96).div(priceX96);
state.sell0 = false;
} else {
state.amountRatioX96 = state.positionAmount0.mul(Q96).div(
state.positionAmount1
);
state.sell0 =
state.amountRatioX96.mul(amount1) < amount0.mul(Q96);
if (state.sell0) {
state.delta0 = amount0
.mul(Q96)
.sub(state.amountRatioX96.mul(amount1))
.div(
state.amountRatioX96.mul(priceX96).div(Q96).add(Q96)
);
} else {
state.delta0 = state
.amountRatioX96
.mul(amount1)
.sub(amount0.mul(Q96))
.div(
state.amountRatioX96.mul(priceX96).div(Q96).add(Q96)
);
}
}
if (state.delta0 > 0) {
if (state.sell0) {
uint256 amountOut = _swap(
abi.encodePacked(
params.token0,
params.fee,
params.token1
),
state.delta0,
0
);
amount0 = amount0.sub(state.delta0);
amount1 = amount1.add(amountOut);
} else {
state.delta1 = state.delta0.mul(priceX96).div(Q96);
if (state.delta1 > 0) {
uint256 amountOut = _swap(
abi.encodePacked(
params.token1,
params.fee,
params.token0
),
state.delta1,
0
);
amount0 = amount0.add(amountOut);
amount1 = amount1.sub(state.delta1);
}
}
}
}
}
function _swap(
bytes memory swapPath,
uint256 amount,
uint256 amountOutMinimum
) internal returns (uint256 amountOut) {
if (amount > 0) {
amountOut = swapRouter.exactInput(
ISwapRouter.ExactInputParams(
swapPath, //path
address(this), //recipient
type(uint256).max, //deadline
amount, //amountIn
amountOutMinimum //amountOutMinimum
)
);
}
}
// Safe transfer for tokens like USDT that don't return bool
function _safeTransfer(address token, address to, uint256 amount) internal {
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(IERC20.transfer.selector, to, amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"Transfer failed"
);
}
// Safe approve for tokens like USDT that don't return bool
function _safeApprove(
address token,
address spender,
uint256 amount
) internal {
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(IERC20.approve.selector, spender, amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"Approve failed"
);
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/EnumerableSet.sol";
import "../utils/Address.sol";
import "../utils/Context.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context {
using EnumerableSet for EnumerableSet.AddressSet;
using Address for address;
struct RoleData {
EnumerableSet.AddressSet members;
bytes32 adminRole;
}
mapping (bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roles[role].members.contains(account);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
return _roles[role].members.length();
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
return _roles[role].members.at(index);
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
emit RoleAdminChanged(role, _roles[role].adminRole, adminRole);
_roles[role].adminRole = adminRole;
}
function _grantRole(bytes32 role, address account) private {
if (_roles[role].members.add(account)) {
emit RoleGranted(role, account, _msgSender());
}
}
function _revokeRole(bytes32 role, address account) private {
if (_roles[role].members.remove(account)) {
emit RoleRevoked(role, account, _msgSender());
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
"
},
"lib/v3-core/contracts/libraries/TickMath.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0 <0.8.0;
/// @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 {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
/// @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 ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(MAX_TICK), 'T');
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
// 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 getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
// second inequality must be < because the price can never reach the price at the max tick
require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R');
uint256 ratio = uint256(sqrtPriceX96) << 32;
uint256 r = ratio;
uint256 msb = 0;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = ratio >> (msb - 127);
else r = ratio << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
"
},
"lib/v3-periphery/contracts/libraries/LiquidityAmounts.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol';
/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
/// @notice Downcasts uint256 to uint128
/// @param x The uint258 to be downcasted
/// @return y The passed value, downcasted to uint128
function toUint128(uint256 x) private pure returns (uint128 y) {
require((y = uint128(x)) == x);
}
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96));
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
}
}
/// @notice Computes the amount of token0 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0
function getAmount0ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return
FullMath.mulDiv(
uint256(liquidity) << FixedPoint96.RESOLUTION,
sqrtRatioBX96 - sqrtRatioAX96,
sqrtRatioBX96
) / sqrtRatioAX96;
}
/// @notice Computes the amount of token1 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount1 The amount of token1
function getAmount1ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
}
/// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function getAmountsForLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
} else {
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}
}
}
"
},
"src/IUniswapV3PositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "lib/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "lib/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "lib/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "lib/v3-periphery/contracts/interfaces/ISwapRouter.sol";
interface IUniswapV3PositionManager is IERC721Receiver {
/// @notice Emitted when liquidity is removed from a position
event LiquidityRemoved(
uint256 indexed tokenId,
uint128 liquidityRemoved,
uint256 amount0,
uint256 amount1
);
/// @notice Returns the address of the anchor token
function anchorToken() external view returns (address);
/// @notice Returns the Uniswap V3 factory address
function factory() external view returns (IUniswapV3Factory);
/// @notice Returns the nonfungible position manager address
function nonfungiblePositionManager()
external
view
returns (INonfungiblePositionManager);
/// @notice Returns the current position token ID (0 if no position)
function positionTokenID() external view returns (uint256);
/// @notice Returns the other token address (paired with anchor token)
function otherToken() external view returns (address);
/// @notice Returns token0 address (lower address of the pair)
function token0() external view returns (address);
/// @notice Returns token1 address (higher address of the pair)
function token1() external view returns (address);
/// @notice Returns the fee tier for the pool
function feeTier() external view returns (uint24);
/// @notice Returns the default lower tick for positions
function defaultTickLower() external view returns (int24);
/// @notice Returns the default upper tick for positions
function defaultTickUpper() external view returns (int24);
/// @notice Sets the default tick range for new positions
/// @param _lower The lower tick boundary
/// @param _upper The upper tick boundary
function setDefaultTicks(int24 _lower, int24 _upper) external;
/// @notice Sets TWAP validation settings
/// @param _seconds The TWAP period in seconds (0 to disable)
/// @param _maxTickDifference The maximum allowed tick difference
function setTWAPSettings(
uint32 _seconds,
uint32 _maxTickDifference
) external;
/// @notice Creates a new position or compounds an existing one
function maintenance() external;
function removeLiquidityByAnchorTokenAmount(
uint256 anchorTokenAmount,
address tokenRecipient,
uint16 slippage
) external returns (uint256 anchorAmountOut);
/// @notice Removes liquidity from the position by basis points
/// @param basisPoints The percentage to remove (10000 = 100%)
/// @param tokenRecipient The address to receive the tokens
/// @param slippage Maximum slippage in basis points (e.g., 100 = 1%)
/// @return amount0 The amount of token0 received
/// @return amount1 The amount of token1 received
/// @return anchorAmountOut The total amount received in anchor token terms
function removeLiquidityByBasisPoints(
uint16 basisPoints,
address tokenRecipient,
uint16 slippage
)
external
returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut);
/// @notice Transfers ERC20 tokens from the contract
/// @param token The token address to transfer
/// @param recipient The recipient address
/// @param amount The amount to transfer
function transferERC20(
address token,
address recipient,
uint256 amount
) external;
/// @notice Executes a delegatecall to another contract
/// @param target The target contract address
/// @param data The calldata to execute
/// @return The return data from the delegatecall
function delegate(
address target,
bytes calldata data
) external returns (bytes memory);
/// @notice Gets the uncollected fees for the current position
/// @return amount0 The amount of token0 fees owed
/// @return amount1 The amount of token1 fees owed
function getPositionFees()
external
view
returns (uint128 amount0, uint128 amount1);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of
Submitted on: 2025-11-01 20:10:59
Comments
Log in to comment.
No comments yet.