Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for createPool.ts #501

Merged
merged 22 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions ts-sdk/whirlpool/src/createPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
Rpc,
TransactionSigner,
} from "@solana/web3.js";
import { generateKeyPairSigner } from "@solana/web3.js";
import { generateKeyPairSigner, lamports } from "@solana/web3.js";
import {
DEFAULT_ADDRESS,
FUNDER,
Expand All @@ -30,7 +30,8 @@ import {
priceToSqrtPrice,
sqrtPriceToTickIndex,
} from "@orca-so/whirlpools-core";
import { fetchAllMint, getTokenSize } from "@solana-program/token-2022";
import { fetchAllMint, getTokenSize, TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
import assert from "assert";
import { getAccountExtensions, orderMints } from "./token";

Expand Down Expand Up @@ -149,7 +150,7 @@ export async function createConcentratedLiquidityPoolInstructions(
"Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)",
);
const instructions: IInstruction[] = [];
let stateSpace = 0;
let stateSpaces = [];

// Since TE mint data is an extension of T mint data, we can use the same fetch function
const [mintA, mintB] = await fetchAllMint(rpc, [tokenMintA, tokenMintB]);
Expand Down Expand Up @@ -204,9 +205,17 @@ export async function createConcentratedLiquidityPoolInstructions(
}),
);

stateSpace += getTokenSize(getAccountExtensions(mintA.data));
stateSpace += getTokenSize(getAccountExtensions(mintB.data));
stateSpace += getWhirlpoolSize();
stateSpaces.push(
tokenProgramA === TOKEN_2022_PROGRAM_ADDRESS
? getTokenSize(getAccountExtensions(mintA.data))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a separate function (should be able to check mint.owner so mint would be the only parameter)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suspect we will need the same thing in increase_liquidity too. We can put it in token.ts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then we can write a test for it 😄

: getTokenSize()
);
stateSpaces.push(
tokenProgramB === TOKEN_2022_PROGRAM_ADDRESS
? getTokenSize(getAccountExtensions(mintB.data))
: getTokenSize()
);
stateSpaces.push(getWhirlpoolSize());

const fullRange = getFullRangeTickIndexes(tickSpacing);
const lowerTickIndex = getTickArrayStartTickIndex(
Expand Down Expand Up @@ -242,12 +251,17 @@ export async function createConcentratedLiquidityPoolInstructions(
startTickIndex: tickArrayIndexes[i],
}),
);
stateSpace += getTickArraySize();
stateSpaces.push(getTickArraySize());
}

const nonRefundableRent = await rpc
.getMinimumBalanceForRentExemption(BigInt(stateSpace))
.send();
const nonRefundableRents: Lamports[] = await Promise.all(
stateSpaces.map(async (space) => {
const rentExemption = await rpc.getMinimumBalanceForRentExemption(BigInt(space)).send();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't do this since it would change the rpc calls it needs to make from 1 to 5 (or 6)

return rentExemption;
})
);

const nonRefundableRent = lamports(nonRefundableRents.reduce((a, b) => a + b, 0n));

return {
instructions,
Expand Down
144 changes: 141 additions & 3 deletions ts-sdk/whirlpool/tests/createPool.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,143 @@
import { describe } from "vitest";
import { describe, it, beforeAll } from "vitest";
import {
createSplashPoolInstructions,
createConcentratedLiquidityPoolInstructions,
} from "../src/createPool";
import {
DEFAULT_FUNDER,
resetConfiguration,
setDefaultFunder,
SPLASH_POOL_TICK_SPACING,
WHIRLPOOLS_CONFIG_ADDRESS,
} from "../src/config";
import { setupMint } from "./utils/token";
import { orderMints } from "../src/token";
import { rpc, sendTransaction, signer } from "./utils/mockRpc";
import { fetchWhirlpool, getTickArrayAddress, getWhirlpoolAddress } from "@orca-so/whirlpools-client";
import assert from "assert";
import type { Address, Lamports } from "@solana/web3.js";
import { lamports } from "@solana/web3.js";
import { _TICK_ARRAY_SIZE, getFullRangeTickIndexes, getTickArrayStartTickIndex, priceToSqrtPrice, sqrtPriceToTickIndex } from "@orca-so/whirlpools-core";
import { setupMintTE, setupMintTEFee } from "./utils/tokenExtensions";

describe.skip("Create Pool", () => {
// TODO:


describe("Create Pool", () => {
let mintA: Address;
let mintB: Address;

beforeAll(async () => {
const mint1 = await setupMint();
const mint2 = await setupMint();
[mintA, mintB] = orderMints(mint1, mint2);
})

it("Should throw an error if funder is not set", async () => {
setDefaultFunder(DEFAULT_FUNDER);
await assert.rejects(
createConcentratedLiquidityPoolInstructions(rpc, mintA, mintB, 64, 1),
{
name: 'AssertionError',
message: 'Either supply a funder or set the default funder'
}
);
setDefaultFunder(signer);
});

it("Should throw an error if token mints are not ordered", async () => {
await assert.rejects(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: they are ordered, just not ordered correctly

createConcentratedLiquidityPoolInstructions(rpc, mintB, mintA, 64, 1),
{
name: 'AssertionError',
message: 'Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it exists but is there a new AssertionError("Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)")

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we use double quotes for strings in ts (but you can run yarn format and that should format your files correctly

);
})

it("Should create valid splash pool instructions", async () => {
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6)

const { instructions, poolAddress } =
await createSplashPoolInstructions(rpc, mintA, mintB, price);
await sendTransaction(instructions);

const pool = await fetchWhirlpool(rpc, poolAddress);
assert.strictEqual(sqrtPrice, pool.data.sqrtPrice);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would structure the test like this:

  • create splash pool
  • fetch maybe pool before
  • fetch signer balance before
  • assert pool does not exist
  • send tx
  • fetch maybe pool
  • fetch signer blance after
  • assert pool exists
  • assert sqrt, mintA, mintB, and tick spacing
  • assert that estInitialization cost equals difference in signer balance

That way you don't need the Should estimate initialization costs correctly test case entirely

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then create more tests with similar structure for:

  • Splash pool with TE token
  • CL pool
  • CL pool with TE token
  • etc.

assert.strictEqual(mintA, pool.data.tokenMintA);
assert.strictEqual(mintB, pool.data.tokenMintB);
assert.strictEqual(SPLASH_POOL_TICK_SPACING, pool.data.tickSpacing);
})

it("Should estimate initialization costs correctly", async () => {
const assertEstInitializationCost = async (estInitializationCost, poolAddress, tickSpacing) => {
const pool = await fetchWhirlpool(rpc, poolAddress);
const tokenVaultA = await rpc.getAccountInfo(pool.data.tokenVaultA).send();
const tokenVaultB = await rpc.getAccountInfo(pool.data.tokenVaultB).send();
const fullRange = getFullRangeTickIndexes(tickSpacing);
const lowerTickIndex = getTickArrayStartTickIndex(
fullRange.tickLowerIndex,
tickSpacing,
);
const upperTickIndex = getTickArrayStartTickIndex(
fullRange.tickUpperIndex,
tickSpacing,
);
const initialTickIndex = sqrtPriceToTickIndex(pool.data.sqrtPrice);
const currentTickIndex = getTickArrayStartTickIndex(
initialTickIndex,
tickSpacing,
);
const tickArrayIndexes = Array.from(
new Set([lowerTickIndex, upperTickIndex, currentTickIndex]),
);
const tickArrayAddresses = await Promise.all(
tickArrayIndexes.map((x) =>
getTickArrayAddress(poolAddress, x).then((x) => x[0]),
),
);
const tickArrayAccounts = await Promise.all(
tickArrayAddresses.map(async (address) => {
const accountInfo = await rpc.getAccountInfo(address).send();
return accountInfo;
})
);

const poolLamports = pool.lamports;
const vaultALamports = tokenVaultA.value?.lamports ?? lamports(0n);
const vaultBLamports = tokenVaultB.value?.lamports ?? lamports(0n);

const tickArrayLamports = tickArrayAccounts.reduce<Lamports>((acc, account) => {
return lamports(acc + (account.value?.lamports ?? 0n));
}, lamports(0n));

const minRentExempt = poolLamports + vaultALamports + vaultBLamports + tickArrayLamports;

assert.strictEqual(estInitializationCost, minRentExempt)
}

let tickSpacing = 64
const testPoolInstructionsCLMM =
await createConcentratedLiquidityPoolInstructions(rpc, mintA, mintB, tickSpacing);
await sendTransaction(testPoolInstructionsCLMM.instructions);
assertEstInitializationCost(testPoolInstructionsCLMM.estInitializationCost, testPoolInstructionsCLMM.poolAddress, tickSpacing);

const mint3 = await setupMint()
const mint4 = await setupMint()
const [mintC, mintD] = orderMints(mint3, mint4);
const testPoolInstructionsSplash =
await createSplashPoolInstructions(rpc, mintC, mintD);
await sendTransaction(testPoolInstructionsSplash.instructions);
assertEstInitializationCost(testPoolInstructionsSplash.estInitializationCost, testPoolInstructionsSplash.poolAddress, SPLASH_POOL_TICK_SPACING);

const mint5 = await setupMintTE()
const mint6 = await setupMintTEFee()
const [mintE, mintF] = orderMints(mint5, mint6);
const testPoolTE =
await createConcentratedLiquidityPoolInstructions(rpc, mintE, mintF, tickSpacing);
await sendTransaction(testPoolTE.instructions);
assertEstInitializationCost(testPoolTE.estInitializationCost, testPoolTE.poolAddress, tickSpacing);
})
});


19 changes: 16 additions & 3 deletions ts-sdk/whirlpool/tests/utils/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import {
getInitializeConfigInstruction,
getInitializeFeeTierInstruction,
getInitializePoolInstruction,
getInitializePoolV2Instruction,
getTokenBadgeAddress,
getWhirlpoolAddress,
} from "@orca-so/whirlpools-client";
import type { Address, IInstruction } from "@solana/web3.js";
import { generateKeyPairSigner } from "@solana/web3.js";
import { sendTransaction, signer } from "./mockRpc";
import { rpc, sendTransaction, signer } from "./mockRpc";
import {
SPLASH_POOL_TICK_SPACING,
WHIRLPOOLS_CONFIG_ADDRESS,
} from "../../src/config";
import { tickIndexToSqrtPrice } from "@orca-so/whirlpools-core";
import { fetchMint } from "@solana-program/token";
import { a } from "vitest/dist/chunks/suite.B2jumIFP.js";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?


export async function setupConfigAndFeeTiers(): Promise<Address> {
const keypair = await generateKeyPairSigner();
Expand Down Expand Up @@ -90,13 +94,19 @@ export async function setupWhirlpool(
);
const vaultA = await generateKeyPairSigner();
const vaultB = await generateKeyPairSigner();
const badgeA = await getTokenBadgeAddress(WHIRLPOOLS_CONFIG_ADDRESS, tokenA);
const badgeB = await getTokenBadgeAddress(WHIRLPOOLS_CONFIG_ADDRESS, tokenB);
const mintA = await fetchMint(rpc, tokenA)
const mintB = await fetchMint(rpc, tokenB)
const programA = mintA.programAddress
const programB = mintB.programAddress

const sqrtPrice = config.initialSqrtPrice ?? tickIndexToSqrtPrice(0);

const instructions: IInstruction[] = [];

instructions.push(
getInitializePoolInstruction({
getInitializePoolV2Instruction({
whirlpool: whirlpoolAddress[0],
feeTier: feeTierAddress[0],
tokenMintA: tokenA,
Expand All @@ -106,7 +116,10 @@ export async function setupWhirlpool(
funder: signer,
tokenVaultA: vaultA,
tokenVaultB: vaultB,
whirlpoolBump: whirlpoolAddress[1],
tokenBadgeA: badgeA[0],
tokenBadgeB: badgeB[0],
tokenProgramA: programA,
tokenProgramB: programB,
initialSqrtPrice: sqrtPrice,
}),
);
Expand Down
Loading