Skip to content

Commit

Permalink
feat: include slipstream sugar in leaf deployments (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
airtoonricardo authored Dec 17, 2024
1 parent d9a2969 commit 1bc6f6c
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 0 deletions.
278 changes: 278 additions & 0 deletions contracts/sugar/SlipstreamSugar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import {Math} from "@openzeppelin/contracts/math/Math.sol";
import {SqrtPriceMath} from "../core/libraries/SqrtPriceMath.sol";
import {LiquidityAmounts} from "../periphery/libraries/LiquidityAmounts.sol";
import {PositionValue} from "../periphery/libraries/PositionValue.sol";
import {FullMath} from "../core/libraries/FullMath.sol";
import {TickMath} from "../core/libraries/TickMath.sol";
import {FixedPoint128} from "../core/libraries/FixedPoint128.sol";
import {ICLPool} from "../core/interfaces/ICLPool.sol";
import {INonfungiblePositionManager} from "../periphery/interfaces/INonfungiblePositionManager.sol";
import {ISlipstreamSugar} from "./interfaces/ISlipstreamSugar.sol";

/// @notice Expose on-chain helpers for liquidity math
contract SlipstreamSugar is ISlipstreamSugar {
/// @dev Maximum number of Bitmaps that can be processed per call
uint256 constant MAX_BITMAPS = 5;

///
/// Wrappers for LiquidityAmounts
///

function getAmountsForLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) external pure override returns (uint256 amount0, uint256 amount1) {
return LiquidityAmounts.getAmountsForLiquidity({
sqrtRatioX96: sqrtRatioX96,
sqrtRatioAX96: sqrtRatioAX96,
sqrtRatioBX96: sqrtRatioBX96,
liquidity: liquidity
});
}

function getLiquidityForAmounts(
uint256 amount0,
uint256 amount1,
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96
) external pure returns (uint256 liquidity) {
return LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1);
}

/// @notice Computes the amount of token0 for a given amount of token1 and price range
/// @param amount1 Amount of token1 to estimate liquidity
/// @param pool Address of the pool to be used
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param tickLow Lower tick boundary
/// @param tickLow Upper tick boundary
/// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool
/// @return amount0 Estimated amounnt of token0
function estimateAmount0(uint256 amount1, address pool, uint160 sqrtRatioX96, int24 tickLow, int24 tickHigh)
external
view
override
returns (uint256 amount0)
{
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLow);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickHigh);

if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

if (sqrtRatioX96 <= sqrtRatioAX96 || sqrtRatioX96 >= sqrtRatioBX96) {
return 0;
}

// @dev If a pool is provided, fetch updated `sqrtPriceX96`
if (pool != address(0)) {
(sqrtRatioX96,,,,,) = ICLPool(pool).slot0();
}
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
amount0 = SqrtPriceMath.getAmount0Delta(sqrtRatioX96, sqrtRatioBX96, liquidity, false);
}

/// @notice Computes the amount of token1 for a given amount of token0 and price range
/// @param amount0 Amount of token0 to estimate liquidity
/// @param pool Address of the pool to be used
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param tickLow Lower tick boundary
/// @param tickLow Upper tick boundary
/// @dev If the given pool address is not the zero address, will fetch `sqrtRatioX96` from pool
/// @return amount1 Estimated amounnt of token0
function estimateAmount1(uint256 amount0, address pool, uint160 sqrtRatioX96, int24 tickLow, int24 tickHigh)
external
view
override
returns (uint256 amount1)
{
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLow);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickHigh);

if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

if (sqrtRatioX96 <= sqrtRatioAX96 || sqrtRatioX96 >= sqrtRatioBX96) {
return 0;
}

// @dev If a pool is provided, fetch updated `sqrtPriceX96`
if (pool != address(0)) {
(sqrtRatioX96,,,,,) = ICLPool(pool).slot0();
}
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
amount1 = SqrtPriceMath.getAmount1Delta(sqrtRatioAX96, sqrtRatioX96, liquidity, false);
}

///
/// Wrappers for SqrtPriceMath
///

function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp)
external
pure
returns (uint256)
{
return SqrtPriceMath.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp);
}

function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp)
external
pure
returns (uint256)
{
return SqrtPriceMath.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity, roundUp);
}

function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity)
external
pure
returns (int256)
{
return SqrtPriceMath.getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}

function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity)
external
pure
returns (int256)
{
return SqrtPriceMath.getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}

///
/// Wrappers for PositionValue
///

function principal(INonfungiblePositionManager positionManager, uint256 tokenId, uint160 sqrtRatioX96)
external
view
override
returns (uint256 amount0, uint256 amount1)
{
return PositionValue.principal({positionManager: positionManager, tokenId: tokenId, sqrtRatioX96: sqrtRatioX96});
}

function fees(INonfungiblePositionManager positionManager, uint256 tokenId)
external
view
override
returns (uint256 amount0, uint256 amount1)
{
return PositionValue.fees({positionManager: positionManager, tokenId: tokenId});
}

///
/// Wrappers for TickMath
///

function getSqrtRatioAtTick(int24 tick) external pure override returns (uint160 sqrtRatioX96) {
return TickMath.getSqrtRatioAtTick(tick);
}

function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure override returns (int24 tick) {
return TickMath.getTickAtSqrtRatio(sqrtPriceX96);
}

///
/// PoolFees Helper
///

function poolFees(address pool, uint128 liquidity, int24 tickCurrent, int24 tickLower, int24 tickUpper)
external
view
returns (uint256 amount0, uint256 amount1)
{
(,,, uint256 lowerFeeGrowthOutside0X128, uint256 lowerFeeGrowthOutside1X128,,,,,) =
ICLPool(pool).ticks(tickLower);
(,,, uint256 upperFeeGrowthOutside0X128, uint256 upperFeeGrowthOutside1X128,,,,,) =
ICLPool(pool).ticks(tickUpper);

uint256 feeGrowthInside0X128;
uint256 feeGrowthInside1X128;
if (tickCurrent < tickLower) {
feeGrowthInside0X128 = lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
} else if (tickCurrent < tickUpper) {
uint256 feeGrowthGlobal0X128 = ICLPool(pool).feeGrowthGlobal0X128();
uint256 feeGrowthGlobal1X128 = ICLPool(pool).feeGrowthGlobal1X128();
feeGrowthInside0X128 = feeGrowthGlobal0X128 - lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = feeGrowthGlobal1X128 - lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
} else {
feeGrowthInside0X128 = upperFeeGrowthOutside0X128 - lowerFeeGrowthOutside0X128;
feeGrowthInside1X128 = upperFeeGrowthOutside1X128 - lowerFeeGrowthOutside1X128;
}

amount0 = FullMath.mulDiv(feeGrowthInside0X128, liquidity, FixedPoint128.Q128);

amount1 = FullMath.mulDiv(feeGrowthInside1X128, liquidity, FixedPoint128.Q128);
}

///
/// TickLens Helper
///

/// @notice Fetches Tick Data for all populated Ticks in given bitmaps
/// @param pool Address of the pool from which to fetch data
/// @param startTick Tick from which the first bitmap will be fetched
/// @dev The number of bitmaps fetched by this function should always be `MAX_BITMAPS`,
/// unless there are less than `MAX_BITMAPS` left to iterate through
/// @return populatedTicks Array of all Populated Ticks in the provided bitmaps
function getPopulatedTicks(address pool, int24 startTick)
external
view
override
returns (PopulatedTick[] memory populatedTicks)
{
// fetch all bitmaps, starting at bitmap where the given `startTick` is located
int24 tickSpacing = ICLPool(pool).tickSpacing();
int16 startBitmapIndex = int16((startTick / tickSpacing) >> 8);
uint256 maxBitmaps = Math.min(MAX_BITMAPS, uint256(type(int16).max - startBitmapIndex) + 1);

// get all `maxBitmaps` starting from the given tick's bitmap index
uint256 bitmap;
uint256 numberOfPopulatedTicks;
uint256[] memory bitmaps = new uint256[](maxBitmaps);
for (uint256 j = 0; j < maxBitmaps; j++) {
// calculate the number of populated ticks
bitmap = ICLPool(pool).tickBitmap(startBitmapIndex + int16(j));
numberOfPopulatedTicks += countSetBits(bitmap);
bitmaps[j] = bitmap;
}

// fetch populated tick data
populatedTicks = new PopulatedTick[](numberOfPopulatedTicks);

int24 populatedTick;
int24 tickBitmapIndex;
for (uint256 j = 0; j < maxBitmaps; j++) {
bitmap = bitmaps[j];
tickBitmapIndex = startBitmapIndex + int16(j);
for (uint256 i = 0; i < 256; i++) {
if (bitmap & (1 << i) > 0) {
populatedTick = ((tickBitmapIndex << 8) + int24(i)) * tickSpacing;

(uint128 liquidityGross, int128 liquidityNet,,,,,,,,) = ICLPool(pool).ticks(populatedTick);

populatedTicks[--numberOfPopulatedTicks] = PopulatedTick({
tick: populatedTick,
sqrtRatioX96: TickMath.getSqrtRatioAtTick(populatedTick),
liquidityNet: liquidityNet,
liquidityGross: liquidityGross
});
}
}
}
}

function countSetBits(uint256 bitmap) private pure returns (uint256 count) {
while (bitmap != 0) {
bitmap &= (bitmap - 1);
count++;
}
}
}
66 changes: 66 additions & 0 deletions contracts/sugar/interfaces/ISlipstreamSugar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;

import {INonfungiblePositionManager} from "../../periphery/interfaces/INonfungiblePositionManager.sol";

interface ISlipstreamSugar {
struct PopulatedTick {
int24 tick;
uint160 sqrtRatioX96;
int128 liquidityNet;
uint128 liquidityGross;
}

///
/// Wrappers for LiquidityAmounts
///

function getAmountsForLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) external pure returns (uint256 amount0, uint256 amount1);

function estimateAmount0(uint256 amount1, address pool, uint160 sqrtRatioX96, int24 tickLow, int24 tickHigh)
external
view
returns (uint256 amount0);

function estimateAmount1(uint256 amount0, address pool, uint160 sqrtRatioX96, int24 tickLow, int24 tickHigh)
external
view
returns (uint256 amount1);

///
/// Wrappers for PositionValue
///

function principal(INonfungiblePositionManager positionManager, uint256 tokenId, uint160 sqrtRatioX96)
external
view
returns (uint256 amount0, uint256 amount1);

function fees(INonfungiblePositionManager positionManager, uint256 tokenId)
external
view
returns (uint256 amount0, uint256 amount1);

///
/// Wrappers for TickMath
///

function getSqrtRatioAtTick(int24 tick) external pure returns (uint160 sqrtRatioX96);

function getTickAtSqrtRatio(uint160 sqrtRatioX96) external pure returns (int24 tick);

///
/// TickLens Helper
///

function getPopulatedTicks(address pool, int24 startTick)
external
view
returns (PopulatedTick[] memory populatedTicks);
}
7 changes: 7 additions & 0 deletions script/01_DeployLeafBaseFixture.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {LeafCLGaugeFactory} from "contracts/gauge/LeafCLGaugeFactory.sol";
import {CustomSwapFeeModule} from "contracts/core/fees/CustomSwapFeeModule.sol";
import {CustomUnstakedFeeModule} from "contracts/core/fees/CustomUnstakedFeeModule.sol";
import {MixedRouteQuoterV1} from "contracts/periphery/lens/MixedRouteQuoterV1.sol";
import {SlipstreamSugar} from "contracts/sugar/SlipstreamSugar.sol";
import {QuoterV2} from "contracts/periphery/lens/QuoterV2.sol";
import {SwapRouter} from "contracts/periphery/SwapRouter.sol";
import {Constants} from "script/constants/Constants.sol";
Expand Down Expand Up @@ -45,6 +46,7 @@ abstract contract DeployLeafBaseFixture is DeployFixture, Constants {

CustomSwapFeeModule public swapFeeModule;
CustomUnstakedFeeModule public unstakedFeeModule;
SlipstreamSugar public slipstreamSugar;
MixedRouteQuoterV1 public mixedQuoter;
QuoterV2 public quoter;
SwapRouter public swapRouter;
Expand Down Expand Up @@ -146,6 +148,9 @@ abstract contract DeployLeafBaseFixture is DeployFixture, Constants {
leafPoolFactory.setSwapFeeManager(_params.feeManager);
leafPoolFactory.setUnstakedFeeManager(_params.feeManager);

// deploy slipstream sugar
slipstreamSugar = new SlipstreamSugar();

//deploy quoter and router
mixedQuoter = MixedRouteQuoterV1(
cx.deployCreate3({
Expand Down Expand Up @@ -204,6 +209,7 @@ abstract contract DeployLeafBaseFixture is DeployFixture, Constants {
console2.log("leafGaugeFactory: ", address(leafGaugeFactory));
console2.log("swapFeeModule: ", address(swapFeeModule));
console2.log("unstakedFeeModule: ", address(unstakedFeeModule));
console2.log("slipstreamSugar: ", address(slipstreamSugar));
console2.log("mixedQuoter: ", address(mixedQuoter));
console2.log("quoter: ", address(quoter));
console2.log("swapRouter: ", address(swapRouter));
Expand All @@ -221,6 +227,7 @@ abstract contract DeployLeafBaseFixture is DeployFixture, Constants {
vm.writeJson(vm.serializeAddress("", "leafGaugeFactory: ", address(leafGaugeFactory)), path);
vm.writeJson(vm.serializeAddress("", "swapFeeModule: ", address(swapFeeModule)), path);
vm.writeJson(vm.serializeAddress("", "unstakedFeeModule: ", address(unstakedFeeModule)), path);
vm.writeJson(vm.serializeAddress("", "slipstreamSugar", address(slipstreamSugar)), path);
vm.writeJson(vm.serializeAddress("", "mixedQuoter: ", address(mixedQuoter)), path);
vm.writeJson(vm.serializeAddress("", "quoter: ", address(quoter)), path);
vm.writeJson(vm.serializeAddress("", "swapRouter: ", address(swapRouter)), path);
Expand Down
3 changes: 3 additions & 0 deletions script/deployParameters/mode/DeployLeafCL.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ contract DeployLeafCL is DeployLeafBaseFixture {
leafPoolFactory.setSwapFeeManager(_params.feeManager);
leafPoolFactory.setUnstakedFeeManager(_params.feeManager);

// deploy slipstream sugar
slipstreamSugar = new SlipstreamSugar();

//deploy quoter and router
mixedQuoter = MixedRouteQuoterV1(
cx.deployCreate3({
Expand Down
Loading

0 comments on commit 1bc6f6c

Please sign in to comment.