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:
yugure-orca 2022-11-24 15:13:59 +09:00 committed by GitHub
parent 61874e5c56
commit 8a7ed57bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 461 additions and 81 deletions

View File

@ -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 =

View File

@ -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 = {

View File

@ -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

View File

@ -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.`);
}
}

View File

@ -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;
}

View File

@ -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,
@ -71,9 +121,59 @@ describe("swap arrays test", () => {
(err: Error) => err.message.indexOf(expectedError) != -1
);
});
/**
* |-------------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-----|xxxxxxxxxxxxxxxxx|------c2-----------|
* |-------------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;
}
);
});
});

View File

@ -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(

View File

@ -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
);
});
});

View File

@ -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);