Allow developers to take additional fees from a swap (#46)

- Add a new swapWithDevFees function in the Whirlpool client class
- Add new swapQuoteByInputTokenWithDevFees quote function
This commit is contained in:
meep 2022-09-20 11:03:53 -07:00 committed by GitHub
parent 1acd1469bd
commit 0f592f267b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 944 additions and 129 deletions

View File

@ -7,14 +7,14 @@
"types": "dist/index.d.ts",
"dependencies": {
"@metaplex-foundation/mpl-token-metadata": "1.2.5",
"@orca-so/common-sdk": "~0.1.0",
"@orca-so/common-sdk": "~0.1.1",
"@project-serum/anchor": "~0.25.0",
"@solana/spl-token": "^0.1.8",
"decimal.js": "^10.3.1",
"tiny-invariant": "^1.2.0"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/bn.js": "~5.1.0",
"@types/decimal.js": "^7.4.0",
"@types/jest": "^26.0.24",
"@types/mocha": "^9.0.0",

View File

@ -11,6 +11,7 @@ export enum TokenErrorCode {
}
export enum SwapErrorCode {
InvalidDevFeePercentage = `InvalidDevFeePercentage`,
InvalidSqrtPriceLimitDirection = `InvalidSqrtPriceLimitDirection`,
SqrtPriceOutOfBounds = `SqrtPriceOutOfBounds`,
ZeroTradableAmount = `ZeroTradableAmount`,

View File

@ -3,29 +3,31 @@ import {
deriveATA,
Percentage,
resolveOrCreateATAs,
TokenUtil,
TransactionBuilder,
ZERO,
ZERO
} from "@orca-so/common-sdk";
import { Address, translateAddress, BN } from "@project-serum/anchor";
import { Address, BN, translateAddress } from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import invariant from "tiny-invariant";
import { WhirlpoolContext } from "../context";
import {
closePositionIx,
decreaseLiquidityIx,
DevFeeSwapInput,
IncreaseLiquidityInput,
increaseLiquidityIx,
initTickArrayIx,
openPositionIx,
openPositionWithMetadataIx,
initTickArrayIx,
increaseLiquidityIx,
decreaseLiquidityIx,
closePositionIx,
swapIx,
SwapInput,
swapIx
} from "../instructions";
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
import { Whirlpool } from "../whirlpool-client";
import { PublicKey, Keypair } from "@solana/web3.js";
import { AccountFetcher } from "../network/public";
import invariant from "tiny-invariant";
import { decreaseLiquidityQuoteByLiquidityWithParams } from "../quotes/public";
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
import { PDAUtil, TickArrayUtil, TickUtil } from "../utils/public";
import { decreaseLiquidityQuoteByLiquidityWithParams, SwapQuote } from "../quotes/public";
import { Whirlpool } from "../whirlpool-client";
import { getRewardInfos, getTokenVaultAccountInfos } from "./util";
export class WhirlpoolImpl implements Whirlpool {
@ -168,13 +170,47 @@ export class WhirlpoolImpl implements Whirlpool {
);
}
async swap(quote: SwapQuote, sourceWallet?: Address) {
async swap(quote: SwapInput, sourceWallet?: Address) {
const sourceWalletKey = sourceWallet
? AddressUtil.toPubKey(sourceWallet)
: this.ctx.wallet.publicKey;
return this.getSwapTx(quote, sourceWalletKey);
}
async swapWithDevFees(
quote: DevFeeSwapInput,
devFeeWallet: PublicKey,
wallet?: PublicKey | undefined,
payer?: PublicKey | undefined
): Promise<TransactionBuilder> {
const sourceWalletKey = wallet ? AddressUtil.toPubKey(wallet) : this.ctx.wallet.publicKey;
const payerKey = payer ? AddressUtil.toPubKey(payer) : this.ctx.wallet.publicKey;
const txBuilder = new TransactionBuilder(
this.ctx.provider.connection,
this.ctx.provider.wallet
);
if (!quote.devFeeAmount.eq(ZERO)) {
const inputToken =
quote.aToB === quote.amountSpecifiedIsInput ? this.getTokenAInfo() : this.getTokenBInfo();
txBuilder.addInstruction(
await TokenUtil.createSendTokensToWalletInstruction(
this.ctx.connection,
sourceWalletKey,
devFeeWallet,
inputToken.mint,
inputToken.decimals,
quote.devFeeAmount,
() => this.ctx.fetcher.getAccountRentExempt(),
payerKey
)
);
}
return this.getSwapTx(quote, sourceWalletKey, txBuilder);
}
/**
* Construct a transaction for opening an new position with optional metadata
*/
@ -393,13 +429,17 @@ export class WhirlpoolImpl implements Whirlpool {
return txBuilder;
}
private async getSwapTx(input: SwapInput, wallet: PublicKey): Promise<TransactionBuilder> {
private async getSwapTx(
input: SwapInput,
wallet: PublicKey,
initTxBuilder?: TransactionBuilder
): Promise<TransactionBuilder> {
invariant(input.amount.gt(ZERO), "swap amount must be more than zero.");
const { amount, aToB } = input;
const whirlpool = this.data;
const txBuilder = new TransactionBuilder(
this.ctx.provider.connection,
this.ctx.provider.wallet
);
const txBuilder =
initTxBuilder ??
new TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet);
const [ataA, ataB] = await resolveOrCreateATAs(
this.ctx.connection,

View File

@ -1,9 +1,8 @@
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
import { Program } from "@project-serum/anchor";
import { Whirlpool } from "../artifacts/whirlpool";
import { Instruction } from "@orca-so/common-sdk";
import { BN, Program } from "@project-serum/anchor";
import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { BN } from "@project-serum/anchor";
import { Whirlpool } from "../artifacts/whirlpool";
/**
* Parameters and accounts to swap on a Whirlpool
@ -61,6 +60,25 @@ export type SwapInput = {
tickArray2: PublicKey;
};
/**
* Parameters to swap on a Whirlpool with developer fees
*
* @category Instruction Types
* @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A.
* @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents
* the input token of the swap.
* @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput).
* @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput).
* @param sqrtPriceLimit - The maximum/minimum price the swap will swap to.
* @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in
* @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0.
* @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1.
* @param devFeeAmount - FeeAmount (developer fees) charged on this swap
*/
export type DevFeeSwapInput = SwapInput & {
devFeeAmount: u64;
};
/**
* Perform a swap in this Whirlpool
*

View File

@ -0,0 +1,85 @@
import { Percentage } from "@orca-so/common-sdk";
import { Address } from "@project-serum/anchor";
import { u64 } from "@solana/spl-token";
import { AccountFetcher } from "../..";
import { SwapErrorCode, WhirlpoolsError } from "../../errors/errors";
import { Whirlpool } from "../../whirlpool-client";
import { NormalSwapQuote, swapQuoteByInputToken } from "./swap-quote";
/**
* A collection of estimated values from quoting a swap that collects a developer-fee.
* @category Quotes
* @param estimatedAmountIn - Approximate number of input token swapped in the swap
* @param estimatedAmountOut - Approximate number of output token swapped in the swap
* @param estimatedEndTickIndex - Approximate tick-index the Whirlpool will land on after this swap
* @param estimatedEndSqrtPrice - Approximate sqrtPrice the Whirlpool will land on after this swap
* @param estimatedFeeAmount - Approximate feeAmount (all fees) charged on this swap
* @param estimatedSwapFeeAmount - Approximate feeAmount (LP + protocol fees) charged on this swap
* @param devFeeAmount - FeeAmount (developer fees) charged on this swap
*/
export type DevFeeSwapQuote = NormalSwapQuote & {
// NOTE: DevFeeSwaps supports input-token based swaps only as it is difficult
// to collect an exact % amount of dev-fees for output-token based swaps due to slippage.
// If there are third party requests in the future for this functionality, we can launch it
// but with the caveat that the % collected is only an estimate.
amountSpecifiedIsInput: true;
estimatedSwapFeeAmount: u64;
devFeeAmount: u64;
};
/**
* Get an estimated swap quote using input token amount while collecting dev fees.
*
* @category Quotes
* @param whirlpool - Whirlpool to perform the swap on
* @param inputTokenMint - PublicKey for the input token mint to swap with
* @param tokenAmount - The amount of input token to swap from
* @param slippageTolerance - The amount of slippage to account for in this quote
* @param programId - PublicKey for the Whirlpool ProgramId
* @param fetcher - AccountFetcher object to fetch solana accounts
* @param refresh - If true, fetcher would default to fetching the latest accounts
* @param devFeePercentage - The percentage amount to send to developer wallet prior to the swap. Percentage num/dem values has to match token decimal.
* @returns a SwapQuote object with slippage adjusted SwapInput parameters & estimates on token amounts, fee & end whirlpool states.
*/
export async function swapQuoteByInputTokenWithDevFees(
whirlpool: Whirlpool,
inputTokenMint: Address,
tokenAmount: u64,
slippageTolerance: Percentage,
programId: Address,
fetcher: AccountFetcher,
devFeePercentage: Percentage,
refresh: boolean
): Promise<DevFeeSwapQuote> {
if (devFeePercentage.toDecimal().greaterThanOrEqualTo(1)) {
throw new WhirlpoolsError(
"Provided devFeePercentage must be less than 100%",
SwapErrorCode.InvalidDevFeePercentage
);
}
const devFeeAmount = tokenAmount
.mul(devFeePercentage.numerator)
.div(devFeePercentage.denominator);
const slippageAdjustedQuote = await swapQuoteByInputToken(
whirlpool,
inputTokenMint,
tokenAmount.sub(devFeeAmount),
slippageTolerance,
programId,
fetcher,
refresh
);
const devFeeAdjustedQuote: DevFeeSwapQuote = {
...slippageAdjustedQuote,
amountSpecifiedIsInput: true,
estimatedAmountIn: slippageAdjustedQuote.estimatedAmountIn.add(devFeeAmount),
estimatedFeeAmount: slippageAdjustedQuote.estimatedFeeAmount.add(devFeeAmount),
estimatedSwapFeeAmount: slippageAdjustedQuote.estimatedFeeAmount,
devFeeAmount,
};
return devFeeAdjustedQuote;
}

View File

@ -1,15 +1,16 @@
import { AddressUtil, Percentage } from "@orca-so/common-sdk";
import { Address, BN } from "@project-serum/anchor";
import { u64 } from "@solana/spl-token";
import invariant from "tiny-invariant";
import { PoolUtil } from "../../utils/public/pool-utils";
import { SwapInput } from "../../instructions";
import { WhirlpoolData, TickArray } from "../../types/public";
import { AddressUtil, Percentage } from "@orca-so/common-sdk";
import { TickArrayUtil, TokenType } from "../../utils/public";
import { Whirlpool } from "../../whirlpool-client";
import { AccountFetcher } from "../../network/public";
import { simulateSwap } from "../swap/swap-quote-impl";
import { TickArray, WhirlpoolData } from "../../types/public";
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";
/**
* @category Quotes
@ -32,6 +33,14 @@ export type SwapQuoteParam = {
tickArrays: TickArray[];
};
/**
* A collection of estimated values from quoting a swap.
* @category Quotes
* @link {BaseSwapQuote}
* @link {DevFeeSwapQuote}
*/
export type SwapQuote = NormalSwapQuote | DevFeeSwapQuote;
/**
* A collection of estimated values from quoting a swap.
* @category Quotes
@ -41,7 +50,7 @@ export type SwapQuoteParam = {
* @param estimatedEndSqrtPrice - Approximate sqrtPrice the Whirlpool will land on after this swap
* @param estimatedFeeAmount - Approximate feeAmount (all fees) charged on this swap
*/
export type SwapQuote = {
export type NormalSwapQuote = {
estimatedAmountIn: u64;
estimatedAmountOut: u64;
estimatedEndTickIndex: number;
@ -71,17 +80,17 @@ export async function swapQuoteByInputToken(
fetcher: AccountFetcher,
refresh: boolean
): Promise<SwapQuote> {
return swapQuoteByToken(
const params = await swapQuoteByToken(
whirlpool,
inputTokenMint,
tokenAmount,
slippageTolerance,
TokenType.TokenA,
true,
programId,
fetcher,
refresh
);
return swapQuoteWithParams(params, slippageTolerance);
}
/**
@ -109,17 +118,17 @@ export async function swapQuoteByOutputToken(
fetcher: AccountFetcher,
refresh: boolean
): Promise<SwapQuote> {
return swapQuoteByToken(
const params = await swapQuoteByToken(
whirlpool,
outputTokenMint,
tokenAmount,
slippageTolerance,
TokenType.TokenB,
false,
programId,
fetcher,
refresh
);
return swapQuoteWithParams(params, slippageTolerance);
}
/**
@ -130,7 +139,10 @@ export async function swapQuoteByOutputToken(
* @param slippageTolerance - The amount of slippage to account for when generating the final quote.
* @returns a SwapQuote object with slippage adjusted SwapInput parameters & estimates on token amounts, fee & end whirlpool states.
*/
export function swapQuoteWithParams(params: SwapQuoteParam, slippageTolerance: Percentage) {
export function swapQuoteWithParams(
params: SwapQuoteParam,
slippageTolerance: Percentage
): SwapQuote {
checkIfAllTickArraysInitialized(params.tickArrays);
const quote = simulateSwap(params);
@ -138,11 +150,11 @@ export function swapQuoteWithParams(params: SwapQuoteParam, slippageTolerance: P
const slippageAdjustedQuote: SwapQuote = {
...quote,
...SwapUtils.calculateSwapAmountsFromQuote(
params.tokenAmount,
quote.amount,
quote.estimatedAmountIn,
quote.estimatedAmountOut,
slippageTolerance,
params.amountSpecifiedIsInput
quote.amountSpecifiedIsInput
),
};
@ -153,13 +165,12 @@ async function swapQuoteByToken(
whirlpool: Whirlpool,
inputTokenMint: Address,
tokenAmount: u64,
slippageTolerance: Percentage,
amountSpecifiedTokenType: TokenType,
amountSpecifiedIsInput: boolean,
programId: Address,
fetcher: AccountFetcher,
refresh: boolean
) {
): Promise<SwapQuoteParam> {
const whirlpoolData = whirlpool.getData();
const swapMintKey = AddressUtil.toPubKey(inputTokenMint);
const swapTokenType = PoolUtil.getTokenType(whirlpoolData, swapMintKey);
@ -177,29 +188,13 @@ async function swapQuoteByToken(
refresh
);
return swapQuoteWithParams(
{
whirlpoolData,
tokenAmount,
aToB,
amountSpecifiedIsInput,
sqrtPriceLimit: SwapUtils.getDefaultSqrtPriceLimit(aToB),
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(amountSpecifiedIsInput),
tickArrays,
},
slippageTolerance
);
}
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.`);
}
return {
whirlpoolData,
tokenAmount,
aToB,
amountSpecifiedIsInput,
sqrtPriceLimit: SwapUtils.getDefaultSqrtPriceLimit(aToB),
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(amountSpecifiedIsInput),
tickArrays,
};
}

View File

@ -0,0 +1,14 @@
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,8 +3,8 @@ import { Address } from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { WhirlpoolContext } from "./context";
import { WhirlpoolClientImpl } from "./impl/whirlpool-client-impl";
import { DevFeeSwapInput, SwapInput } from "./instructions";
import { AccountFetcher } from "./network/public";
import { SwapQuote } from "./quotes/public";
import {
DecreaseLiquidityInput,
IncreaseLiquidityInput,
@ -201,11 +201,27 @@ export interface Whirlpool {
/**
* Perform a swap between tokenA and tokenB on this pool.
*
* @param quote - A quote on the desired tokenIn and tokenOut for this swap. Use @link {swapQuote} to generate this object.
* @param input - A quote on the desired tokenIn and tokenOut for this swap. Use @link {swapQuote} to generate this object.
* @param wallet - The wallet that tokens will be withdrawn and deposit into. If null, the WhirlpoolContext wallet is used.
* @return a transaction that will perform the swap once executed.
*/
swap: (quote: SwapQuote, wallet?: PublicKey) => Promise<TransactionBuilder>;
swap: (input: SwapInput, wallet?: PublicKey) => Promise<TransactionBuilder>;
/**
* Collect a developer fee and perform a swap between tokenA and tokenB on this pool.
*
* @param input - A quote on the desired tokenIn and tokenOut for this swap. Use @link {swapQuote} to generate this object.
* @param devFeeWallet - The wallet that developer fees will be deposited into.
* @param wallet - The wallet that swap tokens will be withdrawn and deposit into. If null, the WhirlpoolContext wallet is used.
* @param payer - The wallet that will fund the cost needed to initialize the dev wallet token ATA accounts. If null, the WhirlpoolContext wallet is used.
* @return a transaction that will perform the swap once executed.
*/
swapWithDevFees: (
input: DevFeeSwapInput,
devFeeWallet: PublicKey,
wallet?: PublicKey,
payer?: PublicKey
) => Promise<TransactionBuilder>;
}
/**

View File

@ -0,0 +1,529 @@
import { Percentage, ZERO } from "@orca-so/common-sdk";
import * as anchor from "@project-serum/anchor";
import { Address } from "@project-serum/anchor";
import { u64 } from "@solana/spl-token";
import { Keypair, PublicKey } from "@solana/web3.js";
import * as assert from "assert";
import {
buildWhirlpoolClient, PriceMath,
swapQuoteByInputToken,
Whirlpool,
WhirlpoolContext
} from "../../../../src";
import { SwapErrorCode, WhirlpoolsError } from "../../../../src/errors/errors";
import { swapQuoteByInputTokenWithDevFees } from "../../../../src/quotes/public/dev-fee-swap-quote";
import {
assertDevFeeQuotes,
assertDevTokenAmount,
assertQuoteAndResults,
TickSpacing
} from "../../../utils";
import {
arrayTickIndexToTickIndex,
buildPosition,
setupSwapTest
} from "../../../utils/swap-test-utils";
import { getVaultAmounts } from "../../../utils/whirlpools-test-utils";
describe("whirlpool-dev-fee-swap", () => {
const provider = anchor.AnchorProvider.local();
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Whirlpool;
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
const client = buildWhirlpoolClient(ctx);
const tickSpacing = TickSpacing.SixtyFour;
const slippageTolerance = Percentage.fromFraction(0, 100);
it("swap with dev-fee 0% equals swap", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
const devWallet = Keypair.generate();
const aToB = false;
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(0, 1000); // 0%
const inputTokenAmount = new u64(119500000);
const postFeeTokenAmount = inputTokenAmount.sub(
inputTokenAmount.mul(devFeePercentage.numerator).div(devFeePercentage.denominator)
);
const whirlpoolData = await whirlpool.refreshData();
const beforeVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
const inputTokenQuote = await swapQuoteByInputToken(
whirlpool,
whirlpoolData.tokenMintB,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
true
);
const postFeeInputTokenQuote = await swapQuoteByInputToken(
whirlpool,
whirlpoolData.tokenMintB,
postFeeTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
true
);
const inputTokenQuoteWithDevFees = await swapQuoteByInputTokenWithDevFees(
whirlpool,
whirlpoolData.tokenMintB,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
devFeePercentage,
true
);
assertDevFeeQuotes(inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees);
await (
await whirlpool.swapWithDevFees(inputTokenQuoteWithDevFees, devWallet.publicKey)
).buildAndExecute();
const newData = await whirlpool.refreshData();
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
assertQuoteAndResults(aToB, inputTokenQuote, newData, beforeVaultAmounts, afterVaultAmounts);
});
it("swap with dev-fee 0.1%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
const devWallet = Keypair.generate();
const aToB = false;
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(1, 1000); // 0.1%
const inputTokenAmount = new u64(1195000);
const postFeeTokenAmount = inputTokenAmount.sub(
inputTokenAmount.mul(devFeePercentage.numerator).div(devFeePercentage.denominator)
);
const whirlpoolData = await whirlpool.refreshData();
const swapToken = aToB ? whirlpoolData.tokenMintA : whirlpoolData.tokenMintB;
const beforeVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
const { inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees } = await getQuotes(
ctx,
whirlpool,
swapToken,
inputTokenAmount,
postFeeTokenAmount,
slippageTolerance,
devFeePercentage
);
assertDevFeeQuotes(inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees);
await (
await whirlpool.swapWithDevFees(inputTokenQuoteWithDevFees, devWallet.publicKey)
).buildAndExecute();
const newData = await whirlpool.refreshData();
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
assertQuoteAndResults(
aToB,
postFeeInputTokenQuote,
newData,
beforeVaultAmounts,
afterVaultAmounts
);
await assertDevTokenAmount(ctx, inputTokenQuoteWithDevFees, swapToken, devWallet.publicKey);
});
it("swap with dev-fee 1%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 22 }, tickSpacing);
const devWallet = Keypair.generate();
const aToB = true;
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(1, 100); // 1%
const inputTokenAmount = new u64(119500000);
const postFeeTokenAmount = inputTokenAmount.sub(
inputTokenAmount.mul(devFeePercentage.numerator).div(devFeePercentage.denominator)
);
const whirlpoolData = await whirlpool.refreshData();
const swapToken = aToB ? whirlpoolData.tokenMintA : whirlpoolData.tokenMintB;
const beforeVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
const { inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees } = await getQuotes(
ctx,
whirlpool,
swapToken,
inputTokenAmount,
postFeeTokenAmount,
slippageTolerance,
devFeePercentage
);
assertDevFeeQuotes(inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees);
await (
await whirlpool.swapWithDevFees(inputTokenQuoteWithDevFees, devWallet.publicKey)
).buildAndExecute();
const newData = await whirlpool.refreshData();
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
assertQuoteAndResults(
aToB,
postFeeInputTokenQuote,
newData,
beforeVaultAmounts,
afterVaultAmounts
);
await assertDevTokenAmount(ctx, inputTokenQuoteWithDevFees, swapToken, devWallet.publicKey);
});
it("swap with input-token as NATIVE_MINT & dev-fee 1%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: 1, offsetIndex: 1 }, tickSpacing);
const aToB = true;
const tokenAIsNative = true;
const whirlpool = await setupSwapTest({
ctx,
client,
tickSpacing,
initSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currIndex),
initArrayStartTicks: [-16896, -11264, -5632, 0, 5632],
fundedPositions: [
buildPosition(
// a
{ arrayIndex: -1, offsetIndex: 10 },
{ arrayIndex: 1, offsetIndex: 23 },
tickSpacing,
new anchor.BN(990_000_000)
),
buildPosition(
// a
{ arrayIndex: -1, offsetIndex: 10 },
{ arrayIndex: 0, offsetIndex: 23 },
tickSpacing,
new anchor.BN(990_000_000)
),
buildPosition(
// a
{ arrayIndex: 0, offsetIndex: 22 },
{ arrayIndex: 1, offsetIndex: 23 },
tickSpacing,
new anchor.BN(1_990_000_000)
),
buildPosition(
// a
{ arrayIndex: 0, offsetIndex: 23 },
{ arrayIndex: 1, offsetIndex: 23 },
tickSpacing,
new anchor.BN(990_000_000)
),
],
}, tokenAIsNative);
const { devWallet, balance: preDevWalletBalance } = await setupDevWallet(ctx, 10_000_000)
const devFeePercentage = Percentage.fromFraction(1, 10000); // 0.01%
const inputTokenAmount = new u64(1_000_000_000); // Swap 1SOL
const postFeeTokenAmount = inputTokenAmount.sub(
inputTokenAmount.mul(devFeePercentage.numerator).div(devFeePercentage.denominator)
);
const whirlpoolData = await whirlpool.refreshData();
const swapToken = aToB ? whirlpoolData.tokenMintA : whirlpoolData.tokenMintB;
const beforeVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
const { inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees } = await getQuotes(
ctx,
whirlpool,
swapToken,
inputTokenAmount,
postFeeTokenAmount,
slippageTolerance,
devFeePercentage
);
assertDevFeeQuotes(inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees);
await (
await whirlpool.swapWithDevFees(inputTokenQuoteWithDevFees, devWallet.publicKey)
).buildAndExecute();
const newData = await whirlpool.refreshData();
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
assertQuoteAndResults(
aToB,
postFeeInputTokenQuote,
newData,
beforeVaultAmounts,
afterVaultAmounts
);
await assertDevTokenAmount(ctx, inputTokenQuoteWithDevFees, swapToken, devWallet.publicKey, preDevWalletBalance);
});
it("swap with dev-fee 50%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
const devWallet = Keypair.generate();
const aToB = false;
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(500000, 1000000); // 50%
const inputTokenAmount = new u64(119500000);
const postFeeTokenAmount = inputTokenAmount.sub(
inputTokenAmount.mul(devFeePercentage.numerator).div(devFeePercentage.denominator)
);
const whirlpoolData = await whirlpool.refreshData();
const swapToken = aToB ? whirlpoolData.tokenMintA : whirlpoolData.tokenMintB;
const beforeVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
const { inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees } = await getQuotes(
ctx,
whirlpool,
swapToken,
inputTokenAmount,
postFeeTokenAmount,
slippageTolerance,
devFeePercentage
);
assertDevFeeQuotes(inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees);
await (
await whirlpool.swapWithDevFees(inputTokenQuoteWithDevFees, devWallet.publicKey)
).buildAndExecute();
const newData = await whirlpool.refreshData();
const afterVaultAmounts = await getVaultAmounts(ctx, whirlpoolData);
assertQuoteAndResults(
aToB,
postFeeInputTokenQuote,
newData,
beforeVaultAmounts,
afterVaultAmounts
);
await assertDevTokenAmount(ctx, inputTokenQuoteWithDevFees, swapToken, devWallet.publicKey);
});
it("swap with dev-fee of 100%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
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(100, 100); // 100%
const inputTokenAmount = new u64(119500000);
const whirlpoolData = await whirlpool.refreshData();
const swapToken = whirlpoolData.tokenMintB;
assert.rejects(
() =>
swapQuoteByInputTokenWithDevFees(
whirlpool,
swapToken,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
devFeePercentage,
true
),
(err) => (err as WhirlpoolsError).errorCode === SwapErrorCode.InvalidDevFeePercentage
);
});
it("swap with dev-fee of 200%", async () => {
const currIndex = arrayTickIndexToTickIndex({ arrayIndex: -1, offsetIndex: 22 }, tickSpacing);
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(
() =>
swapQuoteByInputTokenWithDevFees(
whirlpool,
swapToken,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
devFeePercentage,
true
),
(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(
ctx: WhirlpoolContext,
whirlpool: Whirlpool,
swapToken: Address,
inputTokenAmount: u64,
postFeeTokenAmount: u64,
slippageTolerance: Percentage,
devFeePercentage: Percentage
) {
const inputTokenQuote = await swapQuoteByInputToken(
whirlpool,
swapToken,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
true
);
const postFeeInputTokenQuote = await swapQuoteByInputToken(
whirlpool,
swapToken,
postFeeTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
true
);
const inputTokenQuoteWithDevFees = await swapQuoteByInputTokenWithDevFees(
whirlpool,
swapToken,
inputTokenAmount,
slippageTolerance,
ctx.program.programId,
ctx.fetcher,
devFeePercentage,
true
);
return { inputTokenQuote, postFeeInputTokenQuote, inputTokenQuoteWithDevFees };
}
async function setupDevWallet(ctx: WhirlpoolContext, airdrop: number) {
// Setup dev-wallet. Airdrop some tokens in or it'll be difficult to account for
// rent-tokens when we do assertion
const devWallet = Keypair.generate();
const txn = await ctx.provider.connection.requestAirdrop(devWallet.publicKey, airdrop);
await ctx.provider.connection.confirmTransaction(txn);
const balance = await ctx.provider.connection.getBalance(devWallet.publicKey);
return { devWallet, balance }
}

View File

@ -1,12 +1,15 @@
import { deriveATA, ONE } from "@orca-so/common-sdk";
import { BN, Program, web3 } from "@project-serum/anchor";
import { AccountLayout, NATIVE_MINT } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import * as assert from "assert";
import { Program, web3, BN } from "@project-serum/anchor";
import { AccountLayout } from "@solana/spl-token";
import { TEST_TOKEN_PROGRAM_ID } from "./test-consts";
import { SwapQuote, WhirlpoolContext } from "../../src";
import { Whirlpool } from "../../src/artifacts/whirlpool";
import { DevFeeSwapQuote } from "../../src/quotes/public/dev-fee-swap-quote";
import { TickData, WhirlpoolData } from "../../src/types/public";
import { SwapQuote } from "../../src";
import { TEST_TOKEN_PROGRAM_ID } from "./test-consts";
import { getTokenBalance } from "./token";
import { VaultAmounts } from "./whirlpools-test-utils";
import { ONE } from "@orca-so/common-sdk";
export function assertInputOutputQuoteEqual(
inputTokenQuote: SwapQuote,
@ -39,6 +42,71 @@ export function assertInputOutputQuoteEqual(
);
}
export function assertDevFeeQuotes(
inputQuote: SwapQuote,
postFeeInputQuote: SwapQuote,
devFeeQuote: DevFeeSwapQuote
) {
assert.equal(inputQuote.aToB, devFeeQuote.aToB, "aToB not equal");
assert.ok(
devFeeQuote.estimatedAmountIn.eq(inputQuote.estimatedAmountIn),
`the devFeeQuote's estimatedAmountIn ${devFeeQuote.estimatedAmountIn} should equal the normal quote's estimatedAmountIn ${inputQuote.estimatedAmountIn}`
);
assert.ok(
devFeeQuote.estimatedAmountIn.eq(
postFeeInputQuote.estimatedAmountIn.add(devFeeQuote.devFeeAmount)
),
`the devFeeQuote's estimatedAmountIn ${devFeeQuote.estimatedAmountIn} should equal the post-fee quote's estimatedAmountIn ${inputQuote.estimatedAmountIn} plus devFeeAmount ${devFeeQuote.devFeeAmount}`
);
assert.ok(
postFeeInputQuote.estimatedAmountOut.sub(devFeeQuote.estimatedAmountOut).abs().lte(ONE),
`post-fee input estimatedAmountOut ${inputQuote.estimatedAmountOut} does not equal devFee quote estimatedAmountOut - ${devFeeQuote.estimatedAmountOut}`
);
assert.equal(
postFeeInputQuote.estimatedEndTickIndex,
devFeeQuote.estimatedEndTickIndex,
"estimatedEndTickIndex not equal"
);
assert.equal(
devFeeQuote.estimatedFeeAmount.toString(),
devFeeQuote.estimatedSwapFeeAmount.add(devFeeQuote.devFeeAmount).toString(),
"devFeeQuote estimatedFeeAmount is not the sum of estimatedSwapFeeAmount and devFeeAmount"
);
assert.equal(
devFeeQuote.estimatedSwapFeeAmount.toString(),
postFeeInputQuote.estimatedFeeAmount.toString(),
"devFeeQuote's estimatedSwapFeeAmount should equal the quote's total swap fee (without dev fee)"
);
assert.equal(
postFeeInputQuote.amountSpecifiedIsInput,
devFeeQuote.amountSpecifiedIsInput,
"amountSpecifiedIsInput not equal"
);
}
export async function assertDevTokenAmount(
ctx: WhirlpoolContext,
expectationQuote: DevFeeSwapQuote,
swapToken: PublicKey,
devWallet: PublicKey,
preDevWalletLamport = 0
) {
if (swapToken.equals(NATIVE_MINT)) {
const walletAmount = await ctx.provider.connection.getBalance(devWallet);
assert.equal(expectationQuote.devFeeAmount.toNumber() + preDevWalletLamport, walletAmount)
return;
}
const tokenDevWalletAta = await deriveATA(devWallet, swapToken);
const afterDevWalletAmount = await getTokenBalance(ctx.provider, tokenDevWalletAta);
assert.equal(
expectationQuote.devFeeAmount,
afterDevWalletAmount,
"incorrect devFee amount sent to dev wallet."
);
}
export function assertQuoteAndResults(
aToB: boolean,
quote: SwapQuote,

View File

@ -1,37 +1,26 @@
import { MathUtil, PDA } from "@orca-so/common-sdk";
import * as anchor from "@project-serum/anchor";
import {
InitTickArrayParams,
OpenPositionParams,
InitPoolParams,
InitializeRewardParams,
TICK_ARRAY_SIZE,
WhirlpoolContext,
AccountFetcher,
InitConfigParams,
TickUtil,
PriceMath,
WhirlpoolIx,
PDAUtil,
toTx,
} from "../../src";
import {
generateDefaultConfigParams,
generateDefaultInitFeeTierParams,
generateDefaultInitPoolParams,
generateDefaultInitTickArrayParams,
generateDefaultOpenPositionParams,
} from "./test-builders";
import { PublicKey, Keypair } from "@solana/web3.js";
import { u64 } from "@solana/spl-token";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
createAndMintToAssociatedTokenAccount,
createMint,
mintToByAuthority,
TickSpacing,
ZERO_BN,
ZERO_BN
} from ".";
import { u64 } from "@solana/spl-token";
import {
InitConfigParams, InitializeRewardParams, InitPoolParams, InitTickArrayParams,
OpenPositionParams, PDAUtil, PriceMath, TickUtil, TICK_ARRAY_SIZE, toTx, WhirlpoolClient, WhirlpoolContext, WhirlpoolIx
} from "../../src";
import { PoolUtil } from "../../src/utils/public/pool-utils";
import { MathUtil, PDA } from "@orca-so/common-sdk";
import {
generateDefaultConfigParams,
generateDefaultInitFeeTierParams,
generateDefaultInitPoolParams,
generateDefaultInitTickArrayParams,
generateDefaultOpenPositionParams
} from "./test-builders";
const defaultInitSqrtPrice = MathUtil.toX64_BN(new anchor.BN(5));
@ -47,7 +36,8 @@ export async function buildTestPoolParams(
tickSpacing: number,
defaultFeeRate = 3000,
initSqrtPrice = defaultInitSqrtPrice,
funder?: PublicKey
funder?: PublicKey,
tokenAIsNative = false
) {
const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx);
await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute();
@ -65,7 +55,8 @@ export async function buildTestPoolParams(
feeTierParams.feeTierPda.publicKey,
tickSpacing,
initSqrtPrice,
funder
funder,
tokenAIsNative
);
return {
configInitInfo,
@ -85,14 +76,16 @@ export async function initTestPool(
ctx: WhirlpoolContext,
tickSpacing: number,
initSqrtPrice = defaultInitSqrtPrice,
funder?: Keypair
funder?: Keypair,
tokenAIsNative = false
) {
const { configInitInfo, poolInitInfo, configKeypairs, feeTierParams } = await buildTestPoolParams(
ctx,
tickSpacing,
3000,
initSqrtPrice,
funder?.publicKey
funder?.publicKey,
tokenAIsNative
);
const tx = toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, poolInitInfo));
@ -289,27 +282,37 @@ export async function initTestPoolWithTokens(
ctx: WhirlpoolContext,
tickSpacing: number,
initSqrtPrice = defaultInitSqrtPrice,
mintAmount = new anchor.BN("15000000000")
mintAmount = new anchor.BN("15000000000"),
tokenAIsNative = false
) {
const provider = ctx.provider;
const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool(
ctx,
tickSpacing,
initSqrtPrice
initSqrtPrice,
undefined,
tokenAIsNative
);
const { tokenMintA, tokenMintB, whirlpoolPda } = poolInitInfo;
// Airdrop SOL into provider's wallet for SOL native token testing.
const connection = ctx.provider.connection;
await connection.requestAirdrop(ctx.provider.wallet.publicKey, 100_000_000_000_000)
const tokenAccountA = await createAndMintToAssociatedTokenAccount(
provider,
tokenMintA,
mintAmount
);
const tokenAccountB = await createAndMintToAssociatedTokenAccount(
provider,
tokenMintB,
mintAmount
);
return {
poolInitInfo,
configInitInfo,
@ -442,6 +445,31 @@ export interface FundedPositionInfo {
tickArrayUpper: PublicKey;
}
export async function fundPositionsWithClient(
client: WhirlpoolClient,
whirlpoolKey: PublicKey,
fundParams: FundedPositionParams[]
) {
const whirlpool = await client.getPool(whirlpoolKey, true);
const whirlpoolData = whirlpool.getData();
await Promise.all(fundParams.map(async (param, idx) => {
const { tokenA, tokenB } = PoolUtil.getTokenAmountsFromLiquidity(
param.liquidityAmount,
whirlpoolData.sqrtPrice,
PriceMath.tickIndexToSqrtPriceX64(param.tickLowerIndex),
PriceMath.tickIndexToSqrtPriceX64(param.tickUpperIndex),
true
);
const { tx } = await whirlpool.openPosition(param.tickLowerIndex, param.tickUpperIndex, {
liquidityAmount: param.liquidityAmount,
tokenMaxA: tokenA,
tokenMaxB: tokenB
});
await tx.buildAndExecute();
}));
}
export async function fundPositions(
ctx: WhirlpoolContext,
poolInitInfo: InitPoolParams,

View File

@ -1,10 +1,10 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { Percentage } from "@orca-so/common-sdk";
import * as anchor from "@project-serum/anchor";
import { u64 } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { TickSpacing } from ".";
import { WhirlpoolContext, WhirlpoolClient, Whirlpool, TICK_ARRAY_SIZE } from "../../src";
import { FundedPositionParams, initTestPoolWithTokens, fundPositions } from "./init-utils";
import { TICK_ARRAY_SIZE, Whirlpool, WhirlpoolClient, WhirlpoolContext } from "../../src";
import { FundedPositionParams, fundPositionsWithClient, initTestPoolWithTokens } from "./init-utils";
export interface SwapTestPoolParams {
ctx: WhirlpoolContext;
@ -29,19 +29,21 @@ export interface SwapTestSetup {
tickArrayAddresses: PublicKey[];
}
export async function setupSwapTest(setup: SwapTestPoolParams) {
export async function setupSwapTest(setup: SwapTestPoolParams, tokenAIsNative = false) {
const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = await initTestPoolWithTokens(
setup.ctx,
setup.tickSpacing,
setup.initSqrtPrice,
setup.tokenMintAmount
setup.tokenMintAmount,
tokenAIsNative
);
const whirlpool = await setup.client.getPool(whirlpoolPda.publicKey, true);
await (await whirlpool.initTickArrayForTicks(setup.initArrayStartTicks))?.buildAndExecute();
await fundPositions(setup.ctx, poolInitInfo, tokenAccountA, tokenAccountB, setup.fundedPositions);
await fundPositionsWithClient(setup.client, whirlpoolPda.publicKey, setup.fundedPositions)
return whirlpool;
}

View File

@ -1,6 +1,6 @@
import { MathUtil, PDA, Percentage } from "@orca-so/common-sdk";
import { AnchorProvider } from "@project-serum/anchor";
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { Keypair, PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { createAndMintToAssociatedTokenAccount, createMint } from ".";
@ -13,7 +13,7 @@ import {
OpenPositionParams,
PDAUtil,
PriceMath,
Whirlpool,
Whirlpool
} from "../../src";
import { WhirlpoolContext } from "../../src/context";
@ -46,9 +46,9 @@ export const generateDefaultConfigParams = (
return { configInitInfo, configKeypairs };
};
export const createInOrderMints = async (context: WhirlpoolContext) => {
export const createInOrderMints = async (context: WhirlpoolContext, tokenAIsNative = false) => {
const provider = context.provider;
const tokenXMintPubKey = await createMint(provider);
const tokenXMintPubKey = tokenAIsNative ? NATIVE_MINT : await createMint(provider);
const tokenYMintPubKey = await createMint(provider);
let tokenAMintPubKey, tokenBMintPubKey;
@ -69,9 +69,10 @@ export const generateDefaultInitPoolParams = async (
feeTierKey: PublicKey,
tickSpacing: number,
initSqrtPrice = MathUtil.toX64(new Decimal(5)),
funder?: PublicKey
funder?: PublicKey,
tokenAIsNative = false
): Promise<InitPoolParams> => {
const [tokenAMintPubKey, tokenBMintPubKey] = await createInOrderMints(context);
const [tokenAMintPubKey, tokenBMintPubKey] = await createInOrderMints(context, tokenAIsNative);
const whirlpoolPda = PDAUtil.getWhirlpool(
context.program.programId,

View File

@ -1,11 +1,14 @@
import { deriveATA } from "@orca-so/common-sdk";
import { BN, AnchorProvider, web3 } from "@project-serum/anchor";
import { deriveATA, TransactionBuilder, ZERO } from "@orca-so/common-sdk";
import { createWSOLAccountInstructions } from "@orca-so/common-sdk/dist/helpers/token-instructions";
import { AnchorProvider, BN, web3 } from "@project-serum/anchor";
import {
AccountLayout,
ASSOCIATED_TOKEN_PROGRAM_ID,
AuthorityType,
NATIVE_MINT,
Token,
TOKEN_PROGRAM_ID,
u64,
u64
} from "@solana/spl-token";
import { TEST_TOKEN_PROGRAM_ID } from "./test-consts";
@ -154,6 +157,20 @@ export async function createAndMintToAssociatedTokenAccount(
): Promise<web3.PublicKey> {
const destinationWalletKey = destinationWallet ? destinationWallet : provider.wallet.publicKey;
const payerKey = payer ? payer : provider.wallet.publicKey;
// Workaround For SOL - just create a wSOL account to satisfy the rest of the test building pipeline.
// Tests who want to test with SOL will have to request their own airdrop.
if (mint.equals(NATIVE_MINT)) {
const rentExemption = await provider.connection.getMinimumBalanceForRentExemption(
AccountLayout.span
);
const txBuilder = new TransactionBuilder(provider.connection, provider.wallet);
const { address: tokenAccount, ...ix } = createWSOLAccountInstructions(destinationWalletKey, ZERO, rentExemption);
txBuilder.addInstruction(ix);
await txBuilder.buildAndExecute();
return tokenAccount;
}
const tokenAccount = await createAssociatedTokenAccount(
provider,
mint,

View File

@ -565,14 +565,15 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@orca-so/common-sdk@~0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@orca-so/common-sdk/-/common-sdk-0.1.0.tgz#25050f18e95bab6a7ef9b10e2d3bd44699ce9836"
integrity sha512-WH6mjrrJyuUMq0DC41jWW6opEIq5Ip/UOf8h7z/gM9Zx+9156JwP/dm8N5GJTNnIG6GYFfhemyNNPj1y3U5LIw==
"@orca-so/common-sdk@~0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@orca-so/common-sdk/-/common-sdk-0.1.1.tgz#18a3a562f1b57f970f2115f453c7e00aa05034cf"
integrity sha512-OKFwr5VgzLKmM/C6xQom4y1jxrihuhKXhDwYMbgPLJW4SllePOfS6SG5fp9/17SvyuWL/zna8Z6ZW2QHpX1Yxw==
dependencies:
"@project-serum/anchor" "~0.25.0"
"@solana/spl-token" "0.1.8"
decimal.js "^10.3.1"
tiny-invariant "^1.2.0"
"@orca-so/whirlpool-client-sdk@0.0.7":
version "0.0.7"
@ -747,10 +748,10 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/bn.js@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
"@types/bn.js@~5.1.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682"
integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==
dependencies:
"@types/node" "*"