fix: strict initialized checking at swapQuoteWithParams (#55)
* fix: strict initialized checking at swapQuoteWithParams - removed checkIfAllTickArraysInitialized from swapQuoteWithParams - added check to ensure that all TickArrays are initialized at getSwapTx - In a TickArraySequence, uninitialized TickArrays are truncated. - fix sqrtPriceLimit range check - fix commitment issue in test code - remove test "swap with a manual quote with dev-fee of 200%" (the code does not throw the exception) * refactor: TickArraySequence - remove Non-null assertion operator(!) - use simple foreach loop and early break - avoid name ambiguity (tickArrays to sequence) - set constructor argument (tickArrays) to readonly passed all tests (integration & sdk)
This commit is contained in:
parent
61874e5c56
commit
8a7ed57bc5
|
@ -435,6 +435,18 @@ export class WhirlpoolImpl implements Whirlpool {
|
|||
initTxBuilder?: TransactionBuilder
|
||||
): Promise<TransactionBuilder> {
|
||||
invariant(input.amount.gt(ZERO), "swap amount must be more than zero.");
|
||||
|
||||
// Check if all the tick arrays have been initialized.
|
||||
const tickArrayAddresses = [input.tickArray0, input.tickArray1, input.tickArray2];
|
||||
const tickArrays = await this.fetcher.listTickArrays(tickArrayAddresses, true);
|
||||
const uninitializedIndices = TickArrayUtil.getUninitializedArrays(tickArrays);
|
||||
if (uninitializedIndices.length > 0) {
|
||||
const uninitializedArrays = uninitializedIndices
|
||||
.map((index) => tickArrayAddresses[index].toBase58())
|
||||
.join(", ");
|
||||
throw new Error(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`);
|
||||
}
|
||||
|
||||
const { amount, aToB } = input;
|
||||
const whirlpool = this.data;
|
||||
const txBuilder =
|
||||
|
|
|
@ -9,7 +9,6 @@ import { PoolUtil, TokenType } from "../../utils/public";
|
|||
import { SwapUtils } from "../../utils/public/swap-utils";
|
||||
import { Whirlpool } from "../../whirlpool-client";
|
||||
import { simulateSwap } from "../swap/swap-quote-impl";
|
||||
import { checkIfAllTickArraysInitialized } from "../swap/swap-quote-utils";
|
||||
import { DevFeeSwapQuote } from "./dev-fee-swap-quote";
|
||||
|
||||
/**
|
||||
|
@ -143,8 +142,6 @@ export function swapQuoteWithParams(
|
|||
params: SwapQuoteParam,
|
||||
slippageTolerance: Percentage
|
||||
): SwapQuote {
|
||||
checkIfAllTickArraysInitialized(params.tickArrays);
|
||||
|
||||
const quote = simulateSwap(params);
|
||||
|
||||
const slippageAdjustedQuote: SwapQuote = {
|
||||
|
|
|
@ -23,7 +23,7 @@ export function simulateSwap(params: SwapQuoteParam): SwapQuote {
|
|||
amountSpecifiedIsInput,
|
||||
} = params;
|
||||
|
||||
if (sqrtPriceLimit.gt(new BN(MAX_SQRT_PRICE) || sqrtPriceLimit.lt(new BN(MIN_SQRT_PRICE)))) {
|
||||
if (sqrtPriceLimit.gt(new BN(MAX_SQRT_PRICE)) || sqrtPriceLimit.lt(new BN(MIN_SQRT_PRICE))) {
|
||||
throw new WhirlpoolsError(
|
||||
"Provided SqrtPriceLimit is out of bounds.",
|
||||
SwapErrorCode.SqrtPriceOutOfBounds
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { TickArray, TickArrayUtil } from "../..";
|
||||
|
||||
export function checkIfAllTickArraysInitialized(tickArrays: TickArray[]) {
|
||||
// Check if all the tick arrays have been initialized.
|
||||
const uninitializedIndices = TickArrayUtil.getUninitializedArrays(
|
||||
tickArrays.map((array) => array.data)
|
||||
);
|
||||
if (uninitializedIndices.length > 0) {
|
||||
const uninitializedArrays = uninitializedIndices
|
||||
.map((index) => tickArrays[index].address.toBase58())
|
||||
.join(", ");
|
||||
throw new Error(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`);
|
||||
}
|
||||
}
|
|
@ -3,22 +3,29 @@ import {
|
|||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
TickArray,
|
||||
TickArrayData,
|
||||
TickData,
|
||||
TICK_ARRAY_SIZE,
|
||||
} from "../../types/public";
|
||||
import { TickArrayIndex } from "./tick-array-index";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
type InitializedTickArray = TickArray & {
|
||||
// override
|
||||
data: TickArrayData;
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTE: differs from contract method of having the swap manager keep track of array index.
|
||||
* This is due to the initial requirement to lazy load tick-arrays. This requirement is no longer necessary.
|
||||
*/
|
||||
export class TickArraySequence {
|
||||
private sequence: InitializedTickArray[];
|
||||
private touchedArrays: boolean[];
|
||||
private startArrayIndex: number;
|
||||
|
||||
constructor(
|
||||
readonly tickArrays: TickArray[],
|
||||
tickArrays: Readonly<TickArray[]>,
|
||||
readonly tickSpacing: number,
|
||||
readonly aToB: boolean
|
||||
) {
|
||||
|
@ -26,15 +33,27 @@ export class TickArraySequence {
|
|||
throw new Error("TickArray index 0 must be initialized");
|
||||
}
|
||||
|
||||
this.touchedArrays = [...Array<boolean>(tickArrays.length).fill(false)];
|
||||
// If an uninitialized TickArray appears, truncate all TickArrays after it (inclusive).
|
||||
this.sequence = [];
|
||||
for (const tickArray of tickArrays) {
|
||||
if ( !tickArray || !tickArray.data ) {
|
||||
break;
|
||||
}
|
||||
this.sequence.push({
|
||||
address: tickArray.address,
|
||||
data: tickArray.data
|
||||
});
|
||||
}
|
||||
|
||||
this.touchedArrays = [...Array<boolean>(this.sequence.length).fill(false)];
|
||||
this.startArrayIndex = TickArrayIndex.fromTickIndex(
|
||||
tickArrays[0].data.startTickIndex,
|
||||
this.sequence[0].data.startTickIndex,
|
||||
this.tickSpacing
|
||||
).arrayIndex;
|
||||
}
|
||||
|
||||
checkArrayContainsTickIndex(sequenceIndex: number, tickIndex: number) {
|
||||
const tickArray = this.tickArrays[sequenceIndex]?.data;
|
||||
const tickArray = this.sequence[sequenceIndex]?.data;
|
||||
if (!tickArray) {
|
||||
return false;
|
||||
}
|
||||
|
@ -48,7 +67,7 @@ export class TickArraySequence {
|
|||
getTouchedArrays(minArraySize: number): PublicKey[] {
|
||||
let result = this.touchedArrays.reduce<PublicKey[]>((prev, curr, index) => {
|
||||
if (curr) {
|
||||
prev.push(this.tickArrays[index].address);
|
||||
prev.push(this.sequence[index].address);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
|
@ -77,7 +96,7 @@ export class TickArraySequence {
|
|||
}
|
||||
|
||||
const localArrayIndex = this.getLocalArrayIndex(targetTaIndex.arrayIndex, this.aToB);
|
||||
const tickArray = this.tickArrays[localArrayIndex].data;
|
||||
const tickArray = this.sequence[localArrayIndex].data;
|
||||
|
||||
this.touchedArrays[localArrayIndex] = true;
|
||||
|
||||
|
@ -148,7 +167,7 @@ export class TickArraySequence {
|
|||
private isArrayIndexInBounds(index: TickArrayIndex, aToB: boolean) {
|
||||
// a+0...a+n-1 array index is ok
|
||||
const localArrayIndex = this.getLocalArrayIndex(index.arrayIndex, aToB);
|
||||
const seqLength = this.tickArrays.length;
|
||||
const seqLength = this.sequence.length;
|
||||
return localArrayIndex >= 0 && localArrayIndex < seqLength;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,57 @@ describe("swap arrays test", () => {
|
|||
const slippageTolerance = Percentage.fromFraction(0, 100);
|
||||
|
||||
/**
|
||||
* |-------------c2-----|xxxxxxxxxxxxxxxxx|------c1-----------|
|
||||
* |--------------------|xxxxxxxxxxxxxxxxx|-c2---c1-----------|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array not initialized, use tickArray0 only, a->b", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const quote = await swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintA,
|
||||
tradeAmount,
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
|
||||
// Verify with an actual swap.
|
||||
// estimatedEndTickIndex is 8446 (arrayIndex: 1)
|
||||
assert.equal(quote.aToB, true);
|
||||
assert.equal(quote.amountSpecifiedIsInput, true);
|
||||
assert.equal(
|
||||
quote.sqrtPriceLimit.toString(),
|
||||
SwapUtils.getDefaultSqrtPriceLimit(true).toString()
|
||||
);
|
||||
assert.equal(
|
||||
quote.otherAmountThreshold.toString(),
|
||||
adjustForSlippage(quote.estimatedAmountOut, slippageTolerance, false).toString()
|
||||
);
|
||||
assert.equal(quote.estimatedAmountIn.toString(), tradeAmount);
|
||||
assert.doesNotThrow(async () => await (await whirlpool.swap(quote)).buildAndExecute());
|
||||
});
|
||||
|
||||
/**
|
||||
* |--------------------|xxxxxxxxxxxxxc2xx|------c1-----------|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array not initialized, a->b", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 44 }, tickSpacing);
|
||||
|
@ -55,14 +105,14 @@ describe("swap arrays test", () => {
|
|||
],
|
||||
});
|
||||
|
||||
// estimatedEndTickIndex is 4091 (arrayIndex: 0 (not initialized))
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const missingTickArray = PDAUtil.getTickArray(ctx.program.programId, whirlpool.getAddress(), 0);
|
||||
const expectedError = `[${missingTickArray.publicKey.toBase58()}] need to be initialized`;
|
||||
const expectedError = "Swap input value traversed too many arrays.";
|
||||
await assert.rejects(
|
||||
swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintA,
|
||||
new u64(10000),
|
||||
new u64(40_000_000),
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
|
@ -73,7 +123,57 @@ describe("swap arrays test", () => {
|
|||
});
|
||||
|
||||
/**
|
||||
* |-------------c1-----|xxxxxxxxxxxxxxxxx|------c2-----------|
|
||||
* |-------------c1--c2-|xxxxxxxxxxxxxxxxx|-------------------|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array not initialized, use tickArray0 only, b->a", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const quote = await swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintB,
|
||||
tradeAmount,
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
|
||||
// Verify with an actual swap.
|
||||
// estimatedEndTickIndex is -2816 (arrayIndex: -1)
|
||||
assert.equal(quote.aToB, false);
|
||||
assert.equal(quote.amountSpecifiedIsInput, true);
|
||||
assert.equal(
|
||||
quote.sqrtPriceLimit.toString(),
|
||||
SwapUtils.getDefaultSqrtPriceLimit(false).toString()
|
||||
);
|
||||
assert.equal(
|
||||
quote.otherAmountThreshold.toString(),
|
||||
adjustForSlippage(quote.estimatedAmountOut, slippageTolerance, false).toString()
|
||||
);
|
||||
assert.equal(quote.estimatedAmountIn.toString(), tradeAmount);
|
||||
assert.doesNotThrow(async () => await (await whirlpool.swap(quote)).buildAndExecute());
|
||||
});
|
||||
|
||||
/**
|
||||
* |-------------c1-----|xxc2xxxxxxxxxxxxx|-------------------|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array not initialized, b->a", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 44 }, tickSpacing);
|
||||
|
@ -94,14 +194,14 @@ describe("swap arrays test", () => {
|
|||
],
|
||||
});
|
||||
|
||||
// estimatedEndTickIndex is 556 (arrayIndex: 0 (not initialized))
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const missingTickArray = PDAUtil.getTickArray(ctx.program.programId, whirlpool.getAddress(), 0);
|
||||
const expectedError = `[${missingTickArray.publicKey.toBase58()}] need to be initialized`;
|
||||
const expectedError = "Swap input value traversed too many arrays.";
|
||||
await assert.rejects(
|
||||
swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintB,
|
||||
new u64(10000),
|
||||
new u64(40_000_000),
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
|
@ -111,6 +211,106 @@ describe("swap arrays test", () => {
|
|||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* |xxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxx|-c2---c1-----------|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array and 3rd array not initialized, use tickArray0 only, a->b", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const quote = await swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintA,
|
||||
tradeAmount,
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
|
||||
// Verify with an actual swap.
|
||||
// estimatedEndTickIndex is 8446 (arrayIndex: 1)
|
||||
assert.equal(quote.aToB, true);
|
||||
assert.equal(quote.amountSpecifiedIsInput, true);
|
||||
assert.equal(
|
||||
quote.sqrtPriceLimit.toString(),
|
||||
SwapUtils.getDefaultSqrtPriceLimit(true).toString()
|
||||
);
|
||||
assert.equal(
|
||||
quote.otherAmountThreshold.toString(),
|
||||
adjustForSlippage(quote.estimatedAmountOut, slippageTolerance, false).toString()
|
||||
);
|
||||
assert.equal(quote.estimatedAmountIn.toString(), tradeAmount);
|
||||
assert.doesNotThrow(async () => await (await whirlpool.swap(quote)).buildAndExecute());
|
||||
});
|
||||
|
||||
/**
|
||||
* |-------------c1--c2-|xxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxx|
|
||||
*/
|
||||
it("3 sequential arrays, 2nd array and 3rd array not initialized, use tickArray0 only, b->a", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const quote = await swapQuoteByInputToken(
|
||||
whirlpool,
|
||||
whirlpoolData.tokenMintB,
|
||||
tradeAmount,
|
||||
slippageTolerance,
|
||||
ctx.program.programId,
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
|
||||
// Verify with an actual swap.
|
||||
// estimatedEndTickIndex is -2816 (arrayIndex: -1)
|
||||
assert.equal(quote.aToB, false);
|
||||
assert.equal(quote.amountSpecifiedIsInput, true);
|
||||
assert.equal(
|
||||
quote.sqrtPriceLimit.toString(),
|
||||
SwapUtils.getDefaultSqrtPriceLimit(false).toString()
|
||||
);
|
||||
assert.equal(
|
||||
quote.otherAmountThreshold.toString(),
|
||||
adjustForSlippage(quote.estimatedAmountOut, slippageTolerance, false).toString()
|
||||
);
|
||||
assert.equal(quote.estimatedAmountIn.toString(), tradeAmount);
|
||||
assert.doesNotThrow(async () => await (await whirlpool.swap(quote)).buildAndExecute());
|
||||
});
|
||||
|
||||
/**
|
||||
* c1|------------------|-----------------|-------------------|
|
||||
*/
|
||||
|
@ -536,4 +736,106 @@ describe("swap arrays test", () => {
|
|||
assert.equal(quote.estimatedAmountIn.toString(), tradeAmount);
|
||||
assert.doesNotThrow(async () => await (await whirlpool.swap(quote)).buildAndExecute());
|
||||
});
|
||||
|
||||
/**
|
||||
* |xxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxx|-c2---c1-----------|
|
||||
*/
|
||||
it("Whirlpool.swap with uninitialized TickArrays, a->b", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const aToB = true;
|
||||
const tickArrays = SwapUtils.getTickArrayPublicKeys(
|
||||
whirlpoolData.tickCurrentIndex,
|
||||
whirlpoolData.tickSpacing,
|
||||
aToB,
|
||||
ctx.program.programId,
|
||||
whirlpool.getAddress()
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
whirlpool.swap({
|
||||
amount: tradeAmount,
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB,
|
||||
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(true),
|
||||
sqrtPriceLimit: SwapUtils.getDefaultSqrtPriceLimit(aToB),
|
||||
tickArray0: tickArrays[0],
|
||||
tickArray1: tickArrays[1],
|
||||
tickArray2: tickArrays[2],
|
||||
}),
|
||||
(err: Error) => {
|
||||
const uninitializedArrays = [tickArrays[1].toBase58(), tickArrays[2].toBase58()].join(", ");
|
||||
return err.message.indexOf(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`) >= 0;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* |-------------c1--c2-|xxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxx|
|
||||
*/
|
||||
it("Whirlpool.swap with uninitialized TickArrays, b->a", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 44 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 44 },
|
||||
{ arrayIndex: 2, offsetIndex: 44 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const tradeAmount = new u64(10000);
|
||||
const aToB = false;
|
||||
const tickArrays = SwapUtils.getTickArrayPublicKeys(
|
||||
whirlpoolData.tickCurrentIndex,
|
||||
whirlpoolData.tickSpacing,
|
||||
aToB,
|
||||
ctx.program.programId,
|
||||
whirlpool.getAddress()
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
whirlpool.swap({
|
||||
amount: tradeAmount,
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB,
|
||||
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(true),
|
||||
sqrtPriceLimit: SwapUtils.getDefaultSqrtPriceLimit(aToB),
|
||||
tickArray0: tickArrays[0],
|
||||
tickArray1: tickArrays[1],
|
||||
tickArray2: tickArrays[2],
|
||||
}),
|
||||
(err: Error) => {
|
||||
const uninitializedArrays = [tickArrays[1].toBase58(), tickArrays[2].toBase58()].join(", ");
|
||||
return err.message.indexOf(`TickArray addresses - [${uninitializedArrays}] need to be initialized.`) >= 0;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -428,53 +428,6 @@ describe("whirlpool-dev-fee-swap", () => {
|
|||
(err) => (err as WhirlpoolsError).errorCode === SwapErrorCode.InvalidDevFeePercentage
|
||||
);
|
||||
});
|
||||
|
||||
it("swap with a manual quote with dev-fee of 200%", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
|
||||
const devWallet = Keypair.generate();
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-5632, 0, 5632],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -1, offsetIndex: 10 },
|
||||
{ arrayIndex: 1, offsetIndex: 23 },
|
||||
tickSpacing,
|
||||
new anchor.BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const devFeePercentage = Percentage.fromFraction(200, 100); // 200%
|
||||
const inputTokenAmount = new u64(119500000);
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const swapToken = whirlpoolData.tokenMintB;
|
||||
|
||||
assert.rejects(
|
||||
async () =>
|
||||
(
|
||||
await whirlpool.swapWithDevFees(
|
||||
{
|
||||
amount: new u64(10000),
|
||||
devFeeAmount: new u64(30000),
|
||||
amountSpecifiedIsInput: true,
|
||||
aToB: true,
|
||||
otherAmountThreshold: ZERO,
|
||||
sqrtPriceLimit: ZERO,
|
||||
tickArray0: PublicKey.default,
|
||||
tickArray1: PublicKey.default,
|
||||
tickArray2: PublicKey.default,
|
||||
},
|
||||
devWallet.publicKey
|
||||
)
|
||||
).buildAndExecute(),
|
||||
(err) => (err as WhirlpoolsError).errorCode === SwapErrorCode.InvalidDevFeePercentage
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function getQuotes(
|
||||
|
|
|
@ -7,11 +7,15 @@ import {
|
|||
buildWhirlpoolClient,
|
||||
MAX_TICK_INDEX,
|
||||
MIN_TICK_INDEX,
|
||||
MAX_SQRT_PRICE,
|
||||
MIN_SQRT_PRICE,
|
||||
PriceMath,
|
||||
swapQuoteByInputToken,
|
||||
swapQuoteByOutputToken,
|
||||
swapQuoteWithParams,
|
||||
TICK_ARRAY_SIZE,
|
||||
WhirlpoolContext,
|
||||
SwapUtils,
|
||||
} from "../../../../src";
|
||||
import { SwapErrorCode, WhirlpoolsError } from "../../../../src/errors/errors";
|
||||
import { assertInputOutputQuoteEqual, assertQuoteAndResults, TickSpacing } from "../../../utils";
|
||||
|
@ -1165,4 +1169,108 @@ describe("swap traversal tests", () => {
|
|||
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
|
||||
assertQuoteAndResults(aToB, quote, newData, beforeVaultAmounts, afterVaultAmounts);
|
||||
});
|
||||
|
||||
/**
|
||||
* sqrtPriceLimit < MIN_SQRT_PRICE
|
||||
* |--------------------|-----------------|---------x1----------|
|
||||
*/
|
||||
it("3 arrays, sqrtPriceLimit is out of bounds (< MIN_SQRT_PRICE), a->b", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 22 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 0, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 10 },
|
||||
{ arrayIndex: 2, offsetIndex: 23 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const aToB = true;
|
||||
const tickArrays = await SwapUtils.getTickArrays(
|
||||
currIndex,
|
||||
tickSpacing,
|
||||
aToB,
|
||||
ctx.program.programId,
|
||||
whirlpool.getAddress(),
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
swapQuoteWithParams(
|
||||
{
|
||||
aToB,
|
||||
amountSpecifiedIsInput: true,
|
||||
tokenAmount: new u64("10000"),
|
||||
whirlpoolData,
|
||||
tickArrays,
|
||||
sqrtPriceLimit: new BN(MIN_SQRT_PRICE).subn(1),
|
||||
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(true),
|
||||
},
|
||||
slippageTolerance
|
||||
),
|
||||
(err) => (err as WhirlpoolsError).errorCode === SwapErrorCode.SqrtPriceOutOfBounds
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* sqrtPriceLimit > MAX_SQRT_PRICE
|
||||
* |-----x1-------------|-----------------|---------------------|
|
||||
*/
|
||||
it("3 arrays, sqrtPriceLimit is out of bounds (> MAX_SQRT_PRICE), b->a", async () => {
|
||||
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
|
||||
const whirlpool = await setupSwapTest({
|
||||
ctx,
|
||||
client,
|
||||
tickSpacing,
|
||||
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
|
||||
initArrayStartTicks: [-11264, -5632, 0, 5632, 11264],
|
||||
fundedPositions: [
|
||||
buildPosition(
|
||||
// a
|
||||
{ arrayIndex: -2, offsetIndex: 10 },
|
||||
{ arrayIndex: 2, offsetIndex: 23 },
|
||||
tickSpacing,
|
||||
new BN(250_000_000)
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const whirlpoolData = await whirlpool.refreshData();
|
||||
const aToB = false;
|
||||
const tickArrays = await SwapUtils.getTickArrays(
|
||||
currIndex,
|
||||
tickSpacing,
|
||||
aToB,
|
||||
ctx.program.programId,
|
||||
whirlpool.getAddress(),
|
||||
fetcher,
|
||||
true
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
swapQuoteWithParams(
|
||||
{
|
||||
aToB,
|
||||
amountSpecifiedIsInput: true,
|
||||
tokenAmount: new u64("10000"),
|
||||
whirlpoolData,
|
||||
tickArrays,
|
||||
sqrtPriceLimit: new BN(MAX_SQRT_PRICE).addn(1),
|
||||
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(true),
|
||||
},
|
||||
slippageTolerance
|
||||
),
|
||||
(err) => (err as WhirlpoolsError).errorCode === SwapErrorCode.SqrtPriceOutOfBounds
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,10 @@ import { initTestPool } from "../../utils/init-utils";
|
|||
import { mintTokensToTestAccount } from "../../utils/test-builders";
|
||||
|
||||
describe("whirlpool-impl", () => {
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
// The default commitment of AnchorProvider is "processed".
|
||||
// But commitment of some Token operations is based on “confirmed”, and preflight simulation sometimes fail.
|
||||
// So use "confirmed" consistently.
|
||||
const provider = anchor.AnchorProvider.local(undefined, {commitment: "confirmed", preflightCommitment: "confirmed"});
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
const program = anchor.workspace.Whirlpool;
|
||||
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
|
||||
|
|
Loading…
Reference in New Issue