Price-based slippage calculation for increase liquidity (#135)
- Deprecated the old increaseLiquidityQuoteByInputToken quote function & added a new increaseLiquidityQuoteByInputTokenUsingPriceSlippage to calculate tokenMax based on price movement - Adding increaseLiquidityQuoteByLiquidity quote function to generate a quote by a set liquidity value
This commit is contained in:
parent
6f7277c2f7
commit
d327356343
|
@ -17,6 +17,9 @@ import {
|
|||
import { PriceMath, TickUtil } from "../../utils/public";
|
||||
import { Whirlpool } from "../../whirlpool-client";
|
||||
|
||||
|
||||
/*** --------- Quote by Input Token --------- ***/
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
* @param inputTokenAmount - The amount of input tokens to deposit.
|
||||
|
@ -45,7 +48,208 @@ export type IncreaseLiquidityQuoteParam = {
|
|||
* Return object from increase liquidity quote functions.
|
||||
* @category Quotes
|
||||
*/
|
||||
export type IncreaseLiquidityQuote = IncreaseLiquidityInput & { tokenEstA: BN; tokenEstB: BN };
|
||||
export type IncreaseLiquidityQuote = IncreaseLiquidityInput & IncreaseLiquidityEstimate;
|
||||
type IncreaseLiquidityEstimate = { liquidityAmount: BN; tokenEstA: BN; tokenEstB: BN };
|
||||
|
||||
/**
|
||||
* Get an estimated quote on the maximum tokens required to deposit based on a specified input token amount.
|
||||
* This new version calculates slippage based on price percentage movement, rather than setting the percentage threshold based on token estimates.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param inputTokenAmount - The amount of input tokens to deposit.
|
||||
* @param inputTokenMint - The mint of the input token the user would like to deposit.
|
||||
* @param tickLower - The lower index of the position that we are depositing into.
|
||||
* @param tickUpper - The upper index of the position that we are depositing into.
|
||||
* @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received.
|
||||
* @param whirlpool - A Whirlpool helper class to help interact with the Whirlpool account.
|
||||
* @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix.
|
||||
*/
|
||||
export function increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
inputTokenMint: Address,
|
||||
inputTokenAmount: Decimal,
|
||||
tickLower: number,
|
||||
tickUpper: number,
|
||||
slippageTolerance: Percentage,
|
||||
whirlpool: Whirlpool
|
||||
) {
|
||||
const data = whirlpool.getData();
|
||||
const tokenAInfo = whirlpool.getTokenAInfo();
|
||||
const tokenBInfo = whirlpool.getTokenBInfo();
|
||||
|
||||
const inputMint = AddressUtil.toPubKey(inputTokenMint);
|
||||
const inputTokenInfo = inputMint.equals(tokenAInfo.mint) ? tokenAInfo : tokenBInfo;
|
||||
|
||||
return increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage({
|
||||
inputTokenMint: inputMint,
|
||||
inputTokenAmount: DecimalUtil.toBN(inputTokenAmount, inputTokenInfo.decimals),
|
||||
tickLowerIndex: TickUtil.getInitializableTickIndex(tickLower, data.tickSpacing),
|
||||
tickUpperIndex: TickUtil.getInitializableTickIndex(tickUpper, data.tickSpacing),
|
||||
slippageTolerance,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an estimated quote on the maximum tokens required to deposit based on a specified input token amount.
|
||||
* This new version calculates slippage based on price percentage movement, rather than setting the percentage threshold based on token estimates.
|
||||
*
|
||||
* @category Quotes
|
||||
* @param param IncreaseLiquidityQuoteParam
|
||||
* @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix.
|
||||
*/
|
||||
export function increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage(
|
||||
param: IncreaseLiquidityQuoteParam
|
||||
): IncreaseLiquidityQuote {
|
||||
invariant(TickUtil.checkTickInBounds(param.tickLowerIndex), "tickLowerIndex is out of bounds.");
|
||||
invariant(TickUtil.checkTickInBounds(param.tickUpperIndex), "tickUpperIndex is out of bounds.");
|
||||
invariant(
|
||||
param.inputTokenMint.equals(param.tokenMintA) || param.inputTokenMint.equals(param.tokenMintB),
|
||||
`input token mint ${param.inputTokenMint.toBase58()} does not match any tokens in the provided pool.`
|
||||
);
|
||||
|
||||
const liquidity = getLiquidityFromInputToken(param);
|
||||
|
||||
if (liquidity.eq(ZERO)) {
|
||||
return {
|
||||
tokenMaxA: ZERO,
|
||||
tokenMaxB: ZERO,
|
||||
liquidityAmount: ZERO,
|
||||
tokenEstA: ZERO,
|
||||
tokenEstB: ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
return increaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity,
|
||||
tickCurrentIndex: param.tickCurrentIndex,
|
||||
sqrtPrice: param.sqrtPrice,
|
||||
tickLowerIndex: param.tickLowerIndex,
|
||||
tickUpperIndex: param.tickUpperIndex,
|
||||
slippageTolerance: param.slippageTolerance,
|
||||
});
|
||||
}
|
||||
|
||||
function getLiquidityFromInputToken(params: IncreaseLiquidityQuoteParam) {
|
||||
const { inputTokenMint, inputTokenAmount, tickLowerIndex, tickUpperIndex, tickCurrentIndex, sqrtPrice } = params;
|
||||
invariant(tickLowerIndex < tickUpperIndex, `tickLowerIndex(${tickLowerIndex}) must be less than tickUpperIndex(${tickUpperIndex})`);
|
||||
|
||||
if (inputTokenAmount.eq(ZERO)) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
const isTokenA = params.tokenMintA.equals(inputTokenMint);
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const positionStatus = PositionUtil.getStrictPositionStatus(sqrtPrice, tickLowerIndex, tickUpperIndex);
|
||||
|
||||
if (positionStatus === PositionStatus.BelowRange) {
|
||||
return isTokenA ? getLiquidityFromTokenA(inputTokenAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, false) : ZERO;
|
||||
}
|
||||
|
||||
if (positionStatus === PositionStatus.AboveRange) {
|
||||
return isTokenA ? ZERO : getLiquidityFromTokenB(inputTokenAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, false);
|
||||
}
|
||||
|
||||
return isTokenA
|
||||
? getLiquidityFromTokenA(inputTokenAmount, sqrtPrice, sqrtPriceUpperX64, false)
|
||||
: getLiquidityFromTokenB(inputTokenAmount, sqrtPriceLowerX64, sqrtPrice, false);
|
||||
}
|
||||
|
||||
/*** --------- Quote by Liquidity --------- ***/
|
||||
|
||||
/**
|
||||
* @category Quotes
|
||||
* @param liquidity - The amount of liquidity value to deposit into the Whirlpool.
|
||||
* @param tokenMintA - The mint of tokenA in the Whirlpool the user is depositing into.
|
||||
* @param tokenMintB -The mint of tokenB in the Whirlpool the user is depositing into.
|
||||
* @param tickCurrentIndex - The Whirlpool's current tickIndex
|
||||
* @param sqrtPrice - The Whirlpool's current sqrtPrice
|
||||
* @param tickLowerIndex - The lower index of the position that we are withdrawing from.
|
||||
* @param tickUpperIndex - The upper index of the position that we are withdrawing from.
|
||||
* @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received.
|
||||
*/
|
||||
export type IncreaseLiquidityQuoteByLiquidityParam = {
|
||||
liquidity: BN;
|
||||
tickCurrentIndex: number;
|
||||
sqrtPrice: BN;
|
||||
tickLowerIndex: number;
|
||||
tickUpperIndex: number;
|
||||
slippageTolerance: Percentage;
|
||||
};
|
||||
|
||||
export function increaseLiquidityQuoteByLiquidityWithParams(params: IncreaseLiquidityQuoteByLiquidityParam): IncreaseLiquidityQuote {
|
||||
if (params.liquidity.eq(ZERO)) {
|
||||
return {
|
||||
tokenMaxA: ZERO,
|
||||
tokenMaxB: ZERO,
|
||||
liquidityAmount: ZERO,
|
||||
tokenEstA: ZERO,
|
||||
tokenEstB: ZERO,
|
||||
};
|
||||
}
|
||||
const { tokenEstA, tokenEstB } = getTokenEstimatesFromLiquidity(params);
|
||||
|
||||
const {
|
||||
lowerBound: [sLowerSqrtPrice, sLowerIndex],
|
||||
upperBound: [sUpperSqrtPrice, sUpperIndex],
|
||||
} = PriceMath.getSlippageBoundForSqrtPrice(params.sqrtPrice, params.slippageTolerance);
|
||||
|
||||
const { tokenEstA: tokenEstALower, tokenEstB: tokenEstBLower } = getTokenEstimatesFromLiquidity({
|
||||
...params,
|
||||
sqrtPrice: sLowerSqrtPrice,
|
||||
tickCurrentIndex: sLowerIndex,
|
||||
});
|
||||
|
||||
const { tokenEstA: tokenEstAUpper, tokenEstB: tokenEstBUpper } = getTokenEstimatesFromLiquidity({
|
||||
...params,
|
||||
sqrtPrice: sUpperSqrtPrice,
|
||||
tickCurrentIndex: sUpperIndex,
|
||||
});
|
||||
|
||||
const tokenMaxA = BN.max(BN.max(tokenEstA, tokenEstALower), tokenEstAUpper);
|
||||
const tokenMaxB = BN.max(BN.max(tokenEstB, tokenEstBLower), tokenEstBUpper);
|
||||
|
||||
return {
|
||||
tokenMaxA,
|
||||
tokenMaxB,
|
||||
tokenEstA,
|
||||
tokenEstB,
|
||||
liquidityAmount: params.liquidity,
|
||||
};
|
||||
}
|
||||
|
||||
function getTokenEstimatesFromLiquidity(params: IncreaseLiquidityQuoteByLiquidityParam) {
|
||||
const {
|
||||
liquidity,
|
||||
sqrtPrice,
|
||||
tickLowerIndex,
|
||||
tickUpperIndex
|
||||
} = params;
|
||||
if (liquidity.eq(ZERO)) {
|
||||
throw new Error("liquidity must be greater than 0");
|
||||
}
|
||||
let tokenEstA = ZERO;
|
||||
let tokenEstB = ZERO;
|
||||
|
||||
const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
const positionStatus = PositionUtil.getStrictPositionStatus(sqrtPrice, tickLowerIndex, tickUpperIndex);
|
||||
|
||||
if (positionStatus === PositionStatus.BelowRange) {
|
||||
tokenEstA = getTokenAFromLiquidity(liquidity, lowerSqrtPrice, upperSqrtPrice, true);
|
||||
} else if (positionStatus === PositionStatus.InRange) {
|
||||
tokenEstA = getTokenAFromLiquidity(liquidity, sqrtPrice, upperSqrtPrice, true);
|
||||
tokenEstB = getTokenBFromLiquidity(liquidity, lowerSqrtPrice, sqrtPrice, true);
|
||||
} else {
|
||||
tokenEstB = getTokenBFromLiquidity(liquidity, lowerSqrtPrice, upperSqrtPrice, true);
|
||||
}
|
||||
|
||||
return { tokenEstA, tokenEstB };
|
||||
}
|
||||
|
||||
/*** --------- Deprecated --------- ***/
|
||||
|
||||
/**
|
||||
* Get an estimated quote on the maximum tokens required to deposit based on a specified input token amount.
|
||||
|
@ -58,6 +262,7 @@ export type IncreaseLiquidityQuote = IncreaseLiquidityInput & { tokenEstA: BN; t
|
|||
* @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received.
|
||||
* @param whirlpool - A Whirlpool helper class to help interact with the Whirlpool account.
|
||||
* @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix.
|
||||
* @deprecated Use increaseLiquidityQuoteByInputTokenUsingPriceSlippage instead.
|
||||
*/
|
||||
export function increaseLiquidityQuoteByInputToken(
|
||||
inputTokenMint: Address,
|
||||
|
@ -79,7 +284,7 @@ export function increaseLiquidityQuoteByInputToken(
|
|||
inputTokenAmount: DecimalUtil.toBN(inputTokenAmount, inputTokenInfo.decimals),
|
||||
tickLowerIndex: TickUtil.getInitializableTickIndex(tickLower, data.tickSpacing),
|
||||
tickUpperIndex: TickUtil.getInitializableTickIndex(tickUpper, data.tickSpacing),
|
||||
slippageTolerance,
|
||||
slippageTolerance: slippageTolerance,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
@ -90,6 +295,7 @@ export function increaseLiquidityQuoteByInputToken(
|
|||
* @category Quotes
|
||||
* @param param IncreaseLiquidityQuoteParam
|
||||
* @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix.
|
||||
* @deprecated Use increaseLiquidityQuoteByInputTokenWithParams_PriceSlippage instead.
|
||||
*/
|
||||
export function increaseLiquidityQuoteByInputTokenWithParams(
|
||||
param: IncreaseLiquidityQuoteParam
|
||||
|
@ -119,8 +325,9 @@ export function increaseLiquidityQuoteByInputTokenWithParams(
|
|||
}
|
||||
}
|
||||
|
||||
/*** Private ***/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityQuote {
|
||||
const {
|
||||
tokenMintA,
|
||||
|
@ -168,6 +375,9 @@ function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
function quotePositionInRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityQuote {
|
||||
const {
|
||||
tokenMintA,
|
||||
|
@ -213,6 +423,9 @@ function quotePositionInRange(param: IncreaseLiquidityQuoteParam): IncreaseLiqui
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityQuote {
|
||||
const {
|
||||
tokenMintB,
|
||||
|
@ -257,4 +470,4 @@ function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLi
|
|||
tokenEstB,
|
||||
liquidityAmount,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -51,6 +51,18 @@ export const MAX_SQRT_PRICE = "79226673515401279992447579055";
|
|||
*/
|
||||
export const MIN_SQRT_PRICE = "4295048016";
|
||||
|
||||
/**
|
||||
* The minimum sqrt-price supported by the Whirlpool program.
|
||||
* @category Constants
|
||||
*/
|
||||
export const MIN_SQRT_PRICE_BN = new BN(MIN_SQRT_PRICE);
|
||||
|
||||
/**
|
||||
* The maximum sqrt-price supported by the Whirlpool program.
|
||||
* @category Constants
|
||||
*/
|
||||
export const MAX_SQRT_PRICE_BN = new BN(MAX_SQRT_PRICE);
|
||||
|
||||
/**
|
||||
* The number of initialized ticks that a tick-array account can hold.
|
||||
* @category Constants
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { BN } from "@coral-xyz/anchor";
|
||||
import { MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import { PriceMath } from "./public";
|
||||
import {
|
||||
getLowerSqrtPriceFromTokenA,
|
||||
getLowerSqrtPriceFromTokenB,
|
||||
getUpperSqrtPriceFromTokenA,
|
||||
getUpperSqrtPriceFromTokenB,
|
||||
} from "./swap-utils";
|
||||
import { PriceMath } from "./public";
|
||||
|
||||
export enum SwapDirection {
|
||||
AtoB = "Swap A to B",
|
||||
|
@ -25,7 +25,7 @@ export enum PositionStatus {
|
|||
}
|
||||
|
||||
export class PositionUtil {
|
||||
private constructor() {}
|
||||
private constructor() { }
|
||||
|
||||
/**
|
||||
* Returns the position status of a given tickCurrentIndex in relation to the tickLowerIndex and tickUpperIndex.
|
||||
|
@ -72,7 +72,7 @@ export class PositionUtil {
|
|||
): PositionStatus {
|
||||
const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex);
|
||||
const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex);
|
||||
|
||||
|
||||
if (sqrtPriceX64.lte(sqrtPriceLowerX64)) {
|
||||
return PositionStatus.BelowRange;
|
||||
} else if (sqrtPriceX64.gte(sqrtPriceUpperX64)) {
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { BN } from "@coral-xyz/anchor";
|
||||
import { MathUtil } from "@orca-so/common-sdk";
|
||||
import { DecimalUtil, MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import Decimal from "decimal.js";
|
||||
import { MAX_SQRT_PRICE, MIN_SQRT_PRICE } from "../../types/public";
|
||||
import {
|
||||
MAX_SQRT_PRICE,
|
||||
MAX_SQRT_PRICE_BN,
|
||||
MIN_SQRT_PRICE,
|
||||
MIN_SQRT_PRICE_BN,
|
||||
} from "../../types/public";
|
||||
import { TickUtil } from "./tick-utils";
|
||||
|
||||
const BIT_PRECISION = 14;
|
||||
|
@ -22,7 +27,7 @@ export class PriceMath {
|
|||
public static sqrtPriceX64ToPrice(
|
||||
sqrtPriceX64: BN,
|
||||
decimalsA: number,
|
||||
decimalsB: number
|
||||
decimalsB: number,
|
||||
): Decimal {
|
||||
return MathUtil.fromX64(sqrtPriceX64)
|
||||
.pow(2)
|
||||
|
@ -78,12 +83,12 @@ export class PriceMath {
|
|||
const tickLow = signedShiftRight(
|
||||
logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)),
|
||||
64,
|
||||
128
|
||||
128,
|
||||
).toNumber();
|
||||
const tickHigh = signedShiftRight(
|
||||
logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)),
|
||||
64,
|
||||
128
|
||||
128,
|
||||
).toNumber();
|
||||
|
||||
if (tickLow == tickHigh) {
|
||||
|
@ -102,13 +107,13 @@ export class PriceMath {
|
|||
return PriceMath.sqrtPriceX64ToPrice(
|
||||
PriceMath.tickIndexToSqrtPriceX64(tickIndex),
|
||||
decimalsA,
|
||||
decimalsB
|
||||
decimalsB,
|
||||
);
|
||||
}
|
||||
|
||||
public static priceToTickIndex(price: Decimal, decimalsA: number, decimalsB: number): number {
|
||||
return PriceMath.sqrtPriceX64ToTickIndex(
|
||||
PriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB)
|
||||
PriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -116,11 +121,11 @@ export class PriceMath {
|
|||
price: Decimal,
|
||||
decimalsA: number,
|
||||
decimalsB: number,
|
||||
tickSpacing: number
|
||||
tickSpacing: number,
|
||||
): number {
|
||||
return TickUtil.getInitializableTickIndex(
|
||||
PriceMath.priceToTickIndex(price, decimalsA, decimalsB),
|
||||
tickSpacing
|
||||
tickSpacing,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -151,6 +156,48 @@ export class PriceMath {
|
|||
const invTick = TickUtil.invertTick(tick);
|
||||
return PriceMath.tickIndexToSqrtPriceX64(invTick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sqrtPriceX64 & tick index slippage price boundary for a given price and slippage.
|
||||
* Note: This function loses precision
|
||||
*
|
||||
* @param sqrtPriceX64 the sqrtPriceX64 to apply the slippage on
|
||||
* @param slippage the slippage to apply onto the sqrtPriceX64
|
||||
* @returns the sqrtPriceX64 & tick index slippage price boundary
|
||||
*/
|
||||
public static getSlippageBoundForSqrtPrice(
|
||||
sqrtPriceX64: BN,
|
||||
slippage: Percentage,
|
||||
): { lowerBound: [BN, number]; upperBound: [BN, number] } {
|
||||
const sqrtPriceX64Decimal = DecimalUtil.fromBN(sqrtPriceX64);
|
||||
const slippageNumerator = new Decimal(slippage.numerator.toString());
|
||||
const slippageDenominator = new Decimal(slippage.denominator.toString());
|
||||
const lowerBoundSqrtPriceDecimal = sqrtPriceX64Decimal
|
||||
.mul(slippageDenominator.sub(slippageNumerator).sqrt())
|
||||
.div(slippageDenominator.sqrt())
|
||||
.toDecimalPlaces(0);
|
||||
const upperBoundSqrtPriceDecimal = sqrtPriceX64Decimal
|
||||
.mul(slippageDenominator.add(slippageNumerator).sqrt())
|
||||
.div(slippageDenominator.sqrt())
|
||||
.toDecimalPlaces(0);
|
||||
|
||||
const lowerBoundSqrtPrice = BN.min(
|
||||
BN.max(new BN(lowerBoundSqrtPriceDecimal.toString()), MIN_SQRT_PRICE_BN),
|
||||
MAX_SQRT_PRICE_BN,
|
||||
);
|
||||
const upperBoundSqrtPrice = BN.min(
|
||||
BN.max(new BN(upperBoundSqrtPriceDecimal.toString()), MIN_SQRT_PRICE_BN),
|
||||
MAX_SQRT_PRICE_BN,
|
||||
);
|
||||
|
||||
const lowerTickCurrentIndex = PriceMath.sqrtPriceX64ToTickIndex(lowerBoundSqrtPrice);
|
||||
const upperTickCurrentIndex = PriceMath.sqrtPriceX64ToTickIndex(upperBoundSqrtPrice);
|
||||
|
||||
return {
|
||||
lowerBound: [lowerBoundSqrtPrice, lowerTickCurrentIndex],
|
||||
upperBound: [upperBoundSqrtPrice, upperTickCurrentIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Private Functions
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
WhirlpoolContext,
|
||||
WhirlpoolIx,
|
||||
buildWhirlpoolClient,
|
||||
increaseLiquidityQuoteByInputTokenWithParams,
|
||||
increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage,
|
||||
toTx
|
||||
} from "../../src";
|
||||
import { IGNORE_CACHE } from "../../src/network/public/fetcher";
|
||||
|
@ -196,7 +196,7 @@ describe("close_bundled_position", () => {
|
|||
|
||||
// deposit
|
||||
const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey, IGNORE_CACHE);
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage({
|
||||
tokenMintA: poolInitInfo.tokenMintA,
|
||||
tokenMintB: poolInitInfo.tokenMintB,
|
||||
sqrtPrice: pool.getData().sqrtPrice,
|
||||
|
|
|
@ -6,7 +6,7 @@ import Decimal from "decimal.js";
|
|||
import {
|
||||
buildWhirlpoolClient,
|
||||
decreaseLiquidityQuoteByLiquidity,
|
||||
increaseLiquidityQuoteByInputToken,
|
||||
increaseLiquidityQuoteByInputTokenUsingPriceSlippage,
|
||||
PriceMath
|
||||
} from "../../../src";
|
||||
import { WhirlpoolContext } from "../../../src/context";
|
||||
|
@ -71,7 +71,7 @@ describe("position-impl", () => {
|
|||
// [Action] Increase liquidity by 70 tokens of tokenB
|
||||
const position = await client.getPosition(positionAddress.publicKey, IGNORE_CACHE);
|
||||
const preIncreaseData = position.getData();
|
||||
const increase_quote = increaseLiquidityQuoteByInputToken(
|
||||
const increase_quote = increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
poolInitInfo.tokenMintB,
|
||||
new Decimal(70),
|
||||
lowerTick,
|
||||
|
@ -155,7 +155,7 @@ describe("position-impl", () => {
|
|||
// [Action] Increase liquidity by 70 tokens of tokenB & create the ATA in the new source Wallet
|
||||
const position = await client.getPosition(positionAddress.publicKey, IGNORE_CACHE);
|
||||
const preIncreaseData = position.getData();
|
||||
const increase_quote = increaseLiquidityQuoteByInputToken(
|
||||
const increase_quote = increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
poolInitInfo.tokenMintB,
|
||||
new Decimal(70),
|
||||
lowerTick,
|
||||
|
@ -201,7 +201,7 @@ describe("position-impl", () => {
|
|||
10_500_000_000,
|
||||
otherWallet.publicKey
|
||||
);
|
||||
const increaseQuoteFromOtherWallet = increaseLiquidityQuoteByInputToken(
|
||||
const increaseQuoteFromOtherWallet = increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
poolInitInfo.tokenMintB,
|
||||
new Decimal(80),
|
||||
lowerTick,
|
||||
|
|
|
@ -0,0 +1,450 @@
|
|||
import { Percentage, ZERO } from "@orca-so/common-sdk";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import * as assert from "assert";
|
||||
import BN from "bn.js";
|
||||
import {
|
||||
PriceMath,
|
||||
increaseLiquidityQuoteByInputTokenWithParams,
|
||||
increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage,
|
||||
increaseLiquidityQuoteByLiquidityWithParams,
|
||||
} from "../../../../src";
|
||||
import {
|
||||
getLiquidityFromTokenA,
|
||||
getLiquidityFromTokenB,
|
||||
} from "../../../../src/utils/position-util";
|
||||
|
||||
function getTestSlippageRange(currIndex: number, slippage: Percentage) {
|
||||
const sqrtPrice = PriceMath.tickIndexToSqrtPriceX64(currIndex);
|
||||
const {
|
||||
lowerBound: [_sLowerSqrtPrice, sLowerIndex],
|
||||
upperBound: [_sUpperSqrtPrice, sUpperIndex],
|
||||
} = PriceMath.getSlippageBoundForSqrtPrice(sqrtPrice, slippage);
|
||||
|
||||
return {
|
||||
tickLowerIndex: sLowerIndex === sUpperIndex ? sLowerIndex - 1 : sLowerIndex,
|
||||
tickUpperIndex: sUpperIndex,
|
||||
tickCurrentIndex: currIndex,
|
||||
};
|
||||
}
|
||||
|
||||
const variations = [
|
||||
[0, true, Percentage.fromFraction(1, 1000)] as const,
|
||||
[0, false, Percentage.fromFraction(1, 1000)] as const,
|
||||
[0, true, Percentage.fromFraction(1, 100)] as const,
|
||||
[0, false, Percentage.fromFraction(1, 100)] as const,
|
||||
[234653, true, Percentage.fromFraction(1, 1000)] as const,
|
||||
[234653, false, Percentage.fromFraction(1, 1000)] as const,
|
||||
[234653, true, Percentage.fromFraction(1, 100)] as const,
|
||||
[234653, false, Percentage.fromFraction(1, 100)] as const,
|
||||
[-234653, true, Percentage.fromFraction(1, 1000)] as const,
|
||||
[-234653, false, Percentage.fromFraction(1, 1000)] as const,
|
||||
[-234653, true, Percentage.fromFraction(1, 100)] as const,
|
||||
[-234653, false, Percentage.fromFraction(1, 100)] as const,
|
||||
];
|
||||
|
||||
// NOTE: Slippage range for current price (tick = 0) is [-101, 99]
|
||||
// [---P---] = P is the current price & [] is the slippage boundary
|
||||
// |-------| = Position Boundary
|
||||
variations.forEach(([currentTickIndex, isTokenA, slippage]) => {
|
||||
describe("increaseLiquidityQuoteByInputTokenUsingPriceSlippage", () => {
|
||||
const tokenMintA = new PublicKey("orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE");
|
||||
const tokenMintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
||||
|
||||
it(`|[--------P--------]| @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|----------------| [---P---] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickLowerIndex - 100,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|--------------[--|--P----] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickLowerIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[|---|---P------] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex - 1,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[--|---|--P-------] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex - 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|-----[---P---]-----| @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 200,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[--|----P----]-----| @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|--[---P---|-----] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 125,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---|---P---|----] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---P---] |---------| @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickUpperIndex + 100,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 200,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---P--|---]------| @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 100,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[-----P--|---|] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[-------P--|---|--] @ isTokenA - ${isTokenA} tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlippageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 2,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex - 2,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
inputTokenAmount: new BN(100000),
|
||||
isTokenA,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
function testVariation(params: {
|
||||
pTickLowerIndex: number;
|
||||
pTickUpperIndex: number;
|
||||
tickCurrentIndex: number;
|
||||
inputTokenAmount: BN;
|
||||
isTokenA: boolean;
|
||||
slippageTolerance: Percentage;
|
||||
}) {
|
||||
const { pTickLowerIndex, pTickUpperIndex, tickCurrentIndex, inputTokenAmount, isTokenA } =
|
||||
params;
|
||||
|
||||
const sqrtPrice = PriceMath.tickIndexToSqrtPriceX64(tickCurrentIndex);
|
||||
|
||||
const inputTokenMint = isTokenA ? tokenMintA : tokenMintB;
|
||||
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParamsUsingPriceSlippage({
|
||||
inputTokenAmount,
|
||||
inputTokenMint,
|
||||
sqrtPrice,
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: pTickLowerIndex,
|
||||
tickUpperIndex: pTickUpperIndex,
|
||||
tickCurrentIndex,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
|
||||
// Expectations
|
||||
const liquidity = getLiquidityFromInputToken({
|
||||
inputTokenAmount,
|
||||
isInputTokenA: isTokenA,
|
||||
sqrtPrice,
|
||||
currentTickIndex: tickCurrentIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
|
||||
const expectedQuote = increaseLiquidityQuoteByLiquidityWithParams({
|
||||
tickLowerIndex: pTickLowerIndex,
|
||||
tickUpperIndex: pTickUpperIndex,
|
||||
tickCurrentIndex,
|
||||
liquidity,
|
||||
sqrtPrice: sqrtPrice,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
|
||||
const {
|
||||
tokenEstA: expectedTokenEstA,
|
||||
tokenEstB: expectedTokenEstB,
|
||||
tokenMaxA: expectedTokenMaxA,
|
||||
tokenMaxB: expectedTokenMaxB,
|
||||
} = expectedQuote;
|
||||
|
||||
assert.ok(
|
||||
quote.tokenEstA.eq(expectedTokenEstA),
|
||||
`tokenEstA: ${quote.tokenEstA.toString()} !== ${expectedTokenEstA.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenEstB.eq(expectedTokenEstB),
|
||||
`tokenEstB: ${quote.tokenEstB.toString()} !== ${expectedTokenEstB.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenMaxA.eq(expectedTokenMaxA),
|
||||
`tokenMaxA: ${quote.tokenMaxA.toString()} !== ${expectedTokenMaxA.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenMaxB.eq(expectedTokenMaxB),
|
||||
`tokenMaxB: ${quote.tokenMaxB.toString()} !== ${expectedTokenMaxB.toString()}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases for old slippage", () => {
|
||||
const tokenMintA = new PublicKey("orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE");
|
||||
const tokenMintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
||||
|
||||
it("sqrtPrice on lower bound, tokenB input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(0),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.isZero());
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("sqrtPrice on lower bound, tokenA input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(0),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("tickCurrentIndex on lower bound but sqrtPrice not on lower bound, tokenA input", async () => {
|
||||
assert.ok(
|
||||
PriceMath.tickIndexToSqrtPriceX64(1).subn(1).gt(PriceMath.tickIndexToSqrtPriceX64(0)),
|
||||
);
|
||||
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(1).subn(1),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
|
||||
it("tickCurrentIndex on lower bound but sqrtPrice not on lower bound, tokenB input", async () => {
|
||||
assert.ok(
|
||||
PriceMath.tickIndexToSqrtPriceX64(1).subn(1).gt(PriceMath.tickIndexToSqrtPriceX64(0)),
|
||||
);
|
||||
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(1).subn(1),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
|
||||
it("sqrtPrice on upper bound, tokenA input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(64),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 64,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.isZero());
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("sqrtPrice on upper bound, tokenB input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(64),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 64,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
});
|
||||
|
||||
function getLiquidityFromInputToken(params: {
|
||||
inputTokenAmount: BN;
|
||||
isInputTokenA: boolean;
|
||||
currentTickIndex: number;
|
||||
sqrtPrice: BN;
|
||||
lowerTickIndex: number;
|
||||
upperTickIndex: number;
|
||||
}) {
|
||||
const {
|
||||
inputTokenAmount,
|
||||
isInputTokenA,
|
||||
sqrtPrice,
|
||||
currentTickIndex,
|
||||
lowerTickIndex,
|
||||
upperTickIndex,
|
||||
} = params;
|
||||
|
||||
const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(lowerTickIndex);
|
||||
const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(upperTickIndex);
|
||||
|
||||
if (currentTickIndex >= upperTickIndex) {
|
||||
return isInputTokenA
|
||||
? ZERO
|
||||
: getLiquidityFromTokenB(inputTokenAmount, lowerSqrtPrice, upperSqrtPrice, false);
|
||||
}
|
||||
|
||||
if (currentTickIndex < lowerTickIndex) {
|
||||
return isInputTokenA
|
||||
? getLiquidityFromTokenA(inputTokenAmount, lowerSqrtPrice, upperSqrtPrice, false)
|
||||
: ZERO;
|
||||
}
|
||||
|
||||
return isInputTokenA
|
||||
? getLiquidityFromTokenA(inputTokenAmount, sqrtPrice, upperSqrtPrice, false)
|
||||
: getLiquidityFromTokenB(inputTokenAmount, lowerSqrtPrice, sqrtPrice, false);
|
||||
}
|
|
@ -0,0 +1,327 @@
|
|||
import { Percentage, ZERO } from "@orca-so/common-sdk";
|
||||
import assert from "assert";
|
||||
import BN from "bn.js";
|
||||
import { PriceMath, increaseLiquidityQuoteByLiquidityWithParams } from "../../../../src";
|
||||
import {
|
||||
getTokenAFromLiquidity,
|
||||
getTokenBFromLiquidity,
|
||||
} from "../../../../src/utils/position-util";
|
||||
|
||||
const variations = [
|
||||
[0, Percentage.fromFraction(1, 1000), new BN(17733543)] as const,
|
||||
[0, Percentage.fromFraction(1, 100), new BN(17733543)] as const,
|
||||
[0, Percentage.fromFraction(5, 100), new BN(17733543)] as const,
|
||||
[234653, Percentage.fromFraction(1, 1000), new BN(17733543)] as const,
|
||||
[234653, Percentage.fromFraction(1, 100), new BN(17733543)] as const,
|
||||
[234653, Percentage.fromFraction(5, 100), new BN(17733543)] as const,
|
||||
[-234653, Percentage.fromFraction(1, 1000), new BN(17733543)] as const,
|
||||
[-234653, Percentage.fromFraction(1, 100), new BN(17733543)] as const,
|
||||
[-234653, Percentage.fromFraction(5, 100), new BN(17733543)] as const,
|
||||
];
|
||||
|
||||
function getTestSlipageRange(currIndex: number, slippage: Percentage) {
|
||||
const sqrtPrice = PriceMath.tickIndexToSqrtPriceX64(currIndex);
|
||||
const {
|
||||
lowerBound: [_sLowerSqrtPrice, sLowerIndex],
|
||||
upperBound: [_sUpperSqrtPrice, sUpperIndex],
|
||||
} = PriceMath.getSlippageBoundForSqrtPrice(sqrtPrice, slippage);
|
||||
|
||||
return {
|
||||
tickLowerIndex: sLowerIndex === sUpperIndex ? sLowerIndex - 1 : sLowerIndex,
|
||||
tickUpperIndex: sUpperIndex,
|
||||
tickCurrentIndex: currIndex,
|
||||
};
|
||||
}
|
||||
|
||||
// [---P---] = P is the current price & [] is the slippage boundary
|
||||
// |-------| = Position Boundary
|
||||
variations.forEach(([currentTickIndex, slippage, liquidity]) => {
|
||||
describe("increaseLiquidityQuoteByLiquidity", () => {
|
||||
it(`|[--------P--------]| @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|----------------| [---P---] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickLowerIndex - 100,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|--------------[--|--P----] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickLowerIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[|---|---P------] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex - 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[--|---|--P-------] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex - 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|-----[---P---]-----| @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 200,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 200,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[--|----P----]-----| @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`|--[---P---|-----] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex - 125,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---|---P---|----] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickLowerIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickCurrentIndex + 5,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---P---] |---------| @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickUpperIndex + 100,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 200,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[---P--|---]------| @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex + 100,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[-----P--|---|] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 5,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
it(`[-------P--|---|--] @ tickCurrentIndex - ${currentTickIndex}, slippage - ${slippage.toDecimal()}%`, async () => {
|
||||
const slippageRange = getTestSlipageRange(currentTickIndex, slippage);
|
||||
testVariation({
|
||||
pTickLowerIndex: slippageRange.tickCurrentIndex + 2,
|
||||
pTickUpperIndex: slippageRange.tickUpperIndex - 2,
|
||||
tickCurrentIndex: slippageRange.tickCurrentIndex,
|
||||
liquidity,
|
||||
slippageTolerance: slippage,
|
||||
});
|
||||
});
|
||||
|
||||
async function testVariation(params: {
|
||||
pTickLowerIndex: number;
|
||||
pTickUpperIndex: number;
|
||||
tickCurrentIndex: number;
|
||||
liquidity: BN;
|
||||
slippageTolerance: Percentage;
|
||||
}) {
|
||||
const { pTickLowerIndex, pTickUpperIndex, tickCurrentIndex, liquidity } = params;
|
||||
|
||||
const sqrtPrice = PriceMath.tickIndexToSqrtPriceX64(tickCurrentIndex);
|
||||
|
||||
const quote = increaseLiquidityQuoteByLiquidityWithParams({
|
||||
liquidity,
|
||||
sqrtPrice,
|
||||
tickLowerIndex: pTickLowerIndex,
|
||||
tickUpperIndex: pTickUpperIndex,
|
||||
tickCurrentIndex,
|
||||
slippageTolerance: params.slippageTolerance,
|
||||
});
|
||||
|
||||
const {
|
||||
lowerBound: [sLowerSqrtPrice, sLowerIndex],
|
||||
upperBound: [sUpperSqrtPrice, sUpperIndex],
|
||||
} = PriceMath.getSlippageBoundForSqrtPrice(sqrtPrice, slippage);
|
||||
|
||||
const upperTokenEstA = getTokenEstA({
|
||||
liquidity,
|
||||
sqrtPrice: sUpperSqrtPrice,
|
||||
currentTickIndex: sUpperIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
const upperTokenEstB = getTokenEstB({
|
||||
liquidity,
|
||||
sqrtPrice: sUpperSqrtPrice,
|
||||
currentTickIndex: sUpperIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
|
||||
const lowerTokenEstA = getTokenEstA({
|
||||
liquidity,
|
||||
sqrtPrice: sLowerSqrtPrice,
|
||||
currentTickIndex: sLowerIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
const lowerTokenEstB = getTokenEstB({
|
||||
liquidity,
|
||||
sqrtPrice: sLowerSqrtPrice,
|
||||
currentTickIndex: sLowerIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
|
||||
const expectedTokenMaxA = BN.max(
|
||||
BN.max(quote.tokenEstA, upperTokenEstA),
|
||||
lowerTokenEstA,
|
||||
);
|
||||
const expectedTokenMaxB = BN.max(
|
||||
BN.max(quote.tokenEstB, upperTokenEstB),
|
||||
lowerTokenEstB,
|
||||
);
|
||||
|
||||
// Generate expectations for TokenEstA and TokenEstB
|
||||
const expectedTokenEstA = getTokenEstA({
|
||||
liquidity,
|
||||
sqrtPrice,
|
||||
currentTickIndex: tickCurrentIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
const expectedTokenEstB = getTokenEstB({
|
||||
liquidity,
|
||||
sqrtPrice,
|
||||
currentTickIndex: tickCurrentIndex,
|
||||
lowerTickIndex: pTickLowerIndex,
|
||||
upperTickIndex: pTickUpperIndex,
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
quote.tokenEstA.eq(expectedTokenEstA),
|
||||
`tokenEstA: ${quote.tokenEstA.toString()} !== ${expectedTokenEstA.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenEstB.eq(expectedTokenEstB),
|
||||
`tokenEstB: ${quote.tokenEstB.toString()} !== ${expectedTokenEstB.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenMaxA.eq(expectedTokenMaxA),
|
||||
`tokenMaxA: ${quote.tokenMaxA.toString()} !== ${expectedTokenMaxA.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
quote.tokenMaxB.eq(expectedTokenMaxB),
|
||||
`tokenMaxB: ${quote.tokenMaxB.toString()} !== ${expectedTokenMaxB.toString()}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getTokenEstA(params: {
|
||||
liquidity: BN;
|
||||
sqrtPrice: BN;
|
||||
currentTickIndex: number;
|
||||
lowerTickIndex: number;
|
||||
upperTickIndex: number;
|
||||
}) {
|
||||
const { liquidity, sqrtPrice, currentTickIndex, lowerTickIndex, upperTickIndex } = params;
|
||||
|
||||
const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(lowerTickIndex);
|
||||
const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(upperTickIndex);
|
||||
|
||||
if (currentTickIndex >= upperTickIndex) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
if (currentTickIndex < lowerTickIndex) {
|
||||
return getTokenAFromLiquidity(liquidity, lowerSqrtPrice, upperSqrtPrice, true);
|
||||
}
|
||||
|
||||
return getTokenAFromLiquidity(liquidity, sqrtPrice, upperSqrtPrice, true);
|
||||
}
|
||||
|
||||
function getTokenEstB(params: {
|
||||
liquidity: BN;
|
||||
sqrtPrice: BN;
|
||||
currentTickIndex: number;
|
||||
lowerTickIndex: number;
|
||||
upperTickIndex: number;
|
||||
}) {
|
||||
const { liquidity, sqrtPrice, currentTickIndex, lowerTickIndex, upperTickIndex } = params;
|
||||
|
||||
const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(lowerTickIndex);
|
||||
const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(upperTickIndex);
|
||||
|
||||
if (currentTickIndex < lowerTickIndex) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
if (currentTickIndex >= upperTickIndex) {
|
||||
return getTokenBFromLiquidity(liquidity, lowerSqrtPrice, upperSqrtPrice, true);
|
||||
}
|
||||
|
||||
return getTokenBFromLiquidity(liquidity, lowerSqrtPrice, sqrtPrice, true);
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
import * as assert from "assert";
|
||||
import { PriceMath, increaseLiquidityQuoteByInputTokenWithParams } from "../../../../src";
|
||||
import { BN } from "bn.js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
|
||||
describe("edge cases", () => {
|
||||
const tokenMintA = new PublicKey("orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE");
|
||||
const tokenMintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
||||
|
||||
it("sqrtPrice on lower bound, tokenB input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(0),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.isZero());
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("sqrtPrice on lower bound, tokenA input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(0),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("tickCurrentIndex on lower bound but sqrtPrice not on lower bound, tokenA input", async () => {
|
||||
assert.ok(PriceMath.tickIndexToSqrtPriceX64(1).subn(1).gt(PriceMath.tickIndexToSqrtPriceX64(0)));
|
||||
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(1).subn(1),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
|
||||
it("tickCurrentIndex on lower bound but sqrtPrice not on lower bound, tokenB input", async () => {
|
||||
assert.ok(PriceMath.tickIndexToSqrtPriceX64(1).subn(1).gt(PriceMath.tickIndexToSqrtPriceX64(0)));
|
||||
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(1).subn(1),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 0,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.gtn(0));
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.gtn(0));
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
|
||||
it("sqrtPrice on upper bound, tokenA input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintA,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(64),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 64,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.isZero());
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.isZero());
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.isZero());
|
||||
});
|
||||
|
||||
it("sqrtPrice on upper bound, tokenB input", async () => {
|
||||
const quote = increaseLiquidityQuoteByInputTokenWithParams({
|
||||
inputTokenAmount: new BN(1000),
|
||||
inputTokenMint: tokenMintB,
|
||||
sqrtPrice: PriceMath.tickIndexToSqrtPriceX64(64),
|
||||
tokenMintA,
|
||||
tokenMintB,
|
||||
tickLowerIndex: 0,
|
||||
tickUpperIndex: 64,
|
||||
tickCurrentIndex: 64,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
});
|
||||
|
||||
assert.ok(quote.liquidityAmount.gtn(0));
|
||||
assert.ok(quote.tokenEstA.isZero());
|
||||
assert.ok(quote.tokenEstB.gtn(0));
|
||||
assert.ok(quote.tokenMaxA.isZero());
|
||||
assert.ok(quote.tokenMaxB.gtn(0));
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
import { MathUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import assert from "assert";
|
||||
import BN from "bn.js";
|
||||
import Decimal from "decimal.js";
|
||||
import {
|
||||
MAX_SQRT_PRICE_BN,
|
||||
MAX_TICK_INDEX,
|
||||
MIN_SQRT_PRICE_BN,
|
||||
MIN_TICK_INDEX,
|
||||
PriceMath,
|
||||
} from "../../../../src";
|
||||
|
||||
function toSqrtPrice(n: number) {
|
||||
return PriceMath.priceToSqrtPriceX64(new Decimal(n), 6, 6);
|
||||
}
|
||||
|
||||
const EVAL_PRECISION = 16;
|
||||
const variations = [
|
||||
[MAX_SQRT_PRICE_BN, Percentage.fromFraction(0, 100), true] as const,
|
||||
[MAX_SQRT_PRICE_BN, Percentage.fromFraction(1, 1000), true] as const,
|
||||
[MAX_SQRT_PRICE_BN, Percentage.fromFraction(1, 100), true] as const,
|
||||
[MIN_SQRT_PRICE_BN, Percentage.fromFraction(0, 1000), true] as const,
|
||||
[MIN_SQRT_PRICE_BN, Percentage.fromFraction(1, 1000), true] as const,
|
||||
[MIN_SQRT_PRICE_BN, Percentage.fromFraction(1, 100), true] as const,
|
||||
[MIN_SQRT_PRICE_BN, Percentage.fromFraction(1, 100), true] as const,
|
||||
[toSqrtPrice(5), Percentage.fromFraction(0, 1000), false] as const,
|
||||
[toSqrtPrice(5), Percentage.fromFraction(1, 1000), false] as const,
|
||||
[toSqrtPrice(5), Percentage.fromFraction(10, 1000), false] as const,
|
||||
[toSqrtPrice(1000000), Percentage.fromFraction(0, 1000), false] as const,
|
||||
[toSqrtPrice(1000000), Percentage.fromFraction(5, 1000), false] as const,
|
||||
[toSqrtPrice(1000000), Percentage.fromFraction(20, 1000), false] as const,
|
||||
[toSqrtPrice(61235.33), Percentage.fromFraction(0, 1000), false] as const,
|
||||
[toSqrtPrice(61235.33), Percentage.fromFraction(5, 1000), false] as const,
|
||||
[toSqrtPrice(61235.33), Percentage.fromFraction(20, 1000), false] as const,
|
||||
];
|
||||
|
||||
function toPrecisionLevel(decimal: Decimal) {
|
||||
return decimal.toSignificantDigits(EVAL_PRECISION);
|
||||
}
|
||||
|
||||
variations.forEach(([sqrtPrice, slippage, ignorePrecisionVerification]) => {
|
||||
describe("PriceMath - getSlippageBoundForSqrtPrice tests", () => {
|
||||
it(`slippage boundary for sqrt price - ${sqrtPrice.toString()}, slippage - ${slippage
|
||||
.toDecimal()
|
||||
.mul(100)}%`, () => {
|
||||
const { lowerBound, upperBound } = PriceMath.getSlippageBoundForSqrtPrice(
|
||||
sqrtPrice,
|
||||
slippage,
|
||||
);
|
||||
|
||||
const price = PriceMath.sqrtPriceX64ToPrice(sqrtPrice, 6, 6);
|
||||
const slippageDecimal = slippage.toDecimal();
|
||||
|
||||
const expectedUpperSlippagePrice = toPrecisionLevel(price.mul(slippageDecimal.add(1)));
|
||||
const expectedLowerSlippagePrice = toPrecisionLevel(
|
||||
price.mul(new Decimal(1).sub(slippageDecimal)),
|
||||
);
|
||||
|
||||
const expectedUpperSqrtPrice = BN.min(
|
||||
BN.max(MathUtil.toX64(expectedUpperSlippagePrice.sqrt()), MIN_SQRT_PRICE_BN),
|
||||
MAX_SQRT_PRICE_BN,
|
||||
);
|
||||
const expectedLowerSqrtPrice = BN.min(
|
||||
BN.max(MathUtil.toX64(expectedLowerSlippagePrice.sqrt()), MIN_SQRT_PRICE_BN),
|
||||
MAX_SQRT_PRICE_BN,
|
||||
);
|
||||
|
||||
const expectedUpperTickIndex = PriceMath.sqrtPriceX64ToTickIndex(expectedUpperSqrtPrice);
|
||||
const expectedLowerTickIndex = PriceMath.sqrtPriceX64ToTickIndex(expectedLowerSqrtPrice);
|
||||
|
||||
const lowerBoundSqrtPrice = lowerBound[0];
|
||||
const lowerBoundTickIndex = lowerBound[1];
|
||||
const lowerBoundPrice = toPrecisionLevel(
|
||||
PriceMath.sqrtPriceX64ToPrice(lowerBoundSqrtPrice, 6, 6),
|
||||
);
|
||||
|
||||
const upperBoundSqrtPrice = upperBound[0];
|
||||
const upperBoundTickIndex = upperBound[1];
|
||||
const upperBoundPrice = toPrecisionLevel(
|
||||
PriceMath.sqrtPriceX64ToPrice(upperBoundSqrtPrice, 6, 6),
|
||||
);
|
||||
|
||||
// For larger sqrt-price boundary values, it's difficult to verify exactly due to the precision loss.
|
||||
// We will only verify that it won't crash and the upper and lower bounds are within the expected range by
|
||||
// testing that the function won't crash.
|
||||
if (!ignorePrecisionVerification) {
|
||||
assert.ok(
|
||||
lowerBoundPrice.eq(expectedLowerSlippagePrice),
|
||||
`lower slippage price ${lowerBoundPrice.toString()} should equal ${expectedLowerSlippagePrice.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
upperBoundPrice.eq(expectedUpperSlippagePrice),
|
||||
`upper slippage price ${upperBoundPrice.toString()} should equal ${expectedUpperSlippagePrice.toString()}`,
|
||||
);
|
||||
assert.ok(
|
||||
expectedUpperTickIndex === upperBoundTickIndex,
|
||||
`upper tick index ${upperBoundTickIndex} should equal ${expectedUpperTickIndex}`,
|
||||
);
|
||||
assert.ok(
|
||||
expectedLowerTickIndex === lowerBoundTickIndex,
|
||||
`lower tick index ${lowerBoundTickIndex} should equal ${expectedLowerTickIndex}`,
|
||||
);
|
||||
} else {
|
||||
// Verify generally the conditions hold between sqrtPrices and tick indicies
|
||||
assert.ok(
|
||||
lowerBoundSqrtPrice.gte(MIN_SQRT_PRICE_BN) && lowerBoundSqrtPrice.lte(MAX_SQRT_PRICE_BN),
|
||||
`lower bound sqrt price ${lowerBoundSqrtPrice.toString()} should be within bounds of MIN_SQRT_PRICE_BN & MAX_SQRT_PRICE_BN`,
|
||||
);
|
||||
assert.ok(
|
||||
upperBoundSqrtPrice.gte(MIN_SQRT_PRICE_BN) && upperBoundSqrtPrice.lte(MAX_SQRT_PRICE_BN),
|
||||
`lower bound sqrt price ${upperBoundSqrtPrice.toString()} should be within bounds of MIN_SQRT_PRICE_BN & MAX_SQRT_PRICE_BN`,
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
lowerBoundTickIndex >= MIN_TICK_INDEX && lowerBoundTickIndex <= MAX_TICK_INDEX,
|
||||
`lower bound tick index ${lowerBoundTickIndex} should be within bounds of MIN_TICK_INDEX & MAX_TICK_INDEX`,
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
upperBoundTickIndex >= MIN_TICK_INDEX && upperBoundTickIndex <= MAX_TICK_INDEX,
|
||||
`upper bound tick index ${upperBoundTickIndex} should be within bounds of MIN_TICK_INDEX & MAX_TICK_INDEX`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,7 +12,7 @@ import {
|
|||
collectFeesQuote,
|
||||
collectRewardsQuote,
|
||||
decreaseLiquidityQuoteByLiquidity,
|
||||
increaseLiquidityQuoteByInputToken,
|
||||
increaseLiquidityQuoteByInputTokenUsingPriceSlippage,
|
||||
toTx
|
||||
} from "../../../src";
|
||||
import { WhirlpoolContext } from "../../../src/context";
|
||||
|
@ -84,7 +84,7 @@ describe("whirlpool-impl", () => {
|
|||
);
|
||||
|
||||
const inputTokenMint = poolData.tokenMintA;
|
||||
const quote = increaseLiquidityQuoteByInputToken(
|
||||
const quote = increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
inputTokenMint,
|
||||
new Decimal(50),
|
||||
tickLower,
|
||||
|
@ -193,7 +193,7 @@ describe("whirlpool-impl", () => {
|
|||
|
||||
const inputTokenMint = poolData.tokenMintA;
|
||||
const depositAmount = new Decimal(50);
|
||||
const quote = increaseLiquidityQuoteByInputToken(
|
||||
const quote = increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
inputTokenMint,
|
||||
depositAmount,
|
||||
tickLower,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
PoolUtil,
|
||||
PriceMath,
|
||||
Whirlpool,
|
||||
increaseLiquidityQuoteByInputToken
|
||||
increaseLiquidityQuoteByInputTokenUsingPriceSlippage,
|
||||
} from "../../src";
|
||||
import { WhirlpoolContext } from "../../src/context";
|
||||
|
||||
|
@ -215,7 +215,7 @@ export async function initPosition(
|
|||
tokenBDecimal,
|
||||
tickSpacing
|
||||
);
|
||||
const quote = await increaseLiquidityQuoteByInputToken(
|
||||
const quote = await increaseLiquidityQuoteByInputTokenUsingPriceSlippage(
|
||||
inputTokenMint,
|
||||
new Decimal(inputTokenAmount),
|
||||
lowerTick,
|
||||
|
|
Loading…
Reference in New Issue