Skip to content

Commit

Permalink
Resolve comments. Fix minor bug tih token size calculation based on e…
Browse files Browse the repository at this point in the history
…xtensions array.
  • Loading branch information
calintje committed Nov 14, 2024
1 parent 71a7d7b commit 51bc3a0
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 98 deletions.
19 changes: 11 additions & 8 deletions ts-sdk/whirlpool/src/createPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@orca-so/whirlpools-client";
import type {
Address,
GetAccountInfoApi,
GetMinimumBalanceForRentExemptionApi,
GetMultipleAccountsApi,
IInstruction,
Expand All @@ -18,6 +19,7 @@ import type {
TransactionSigner,
} from "@solana/web3.js";
import { generateKeyPairSigner, lamports } from "@solana/web3.js";
import { fetchSysvarRent } from "@solana/sysvars"
import {
DEFAULT_ADDRESS,
FUNDER,
Expand All @@ -32,7 +34,8 @@ import {
} from "@orca-so/whirlpools-core";
import { fetchAllMint } from "@solana-program/token-2022";
import assert from "assert";
import { getTokenSizeByProgram, orderMints } from "./token";
import { getTokenSizeForMint, orderMints } from "./token";
import { calculateMinimumBalance } from "./sysvar";

/**
* Represents the instructions and metadata for creating a pool.
Expand Down Expand Up @@ -81,7 +84,7 @@ export type CreatePoolInstructions = {
* );
*/
export function createSplashPoolInstructions(
rpc: Rpc<GetMultipleAccountsApi & GetMinimumBalanceForRentExemptionApi>,
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi>,
tokenMintA: Address,
tokenMintB: Address,
initialPrice: number = 1,
Expand Down Expand Up @@ -110,7 +113,7 @@ export function createSplashPoolInstructions(
* @returns {Promise<CreatePoolInstructions>} A promise that resolves to an object containing the pool creation instructions, the estimated initialization cost, and the pool address.
*
* @example
* import { createConcentratedLiquidityPool } from '@orca-so/whirlpools';
* import { createConcentratedLiquidityPoolInstructions } from '@orca-so/whirlpools';
* import { generateKeyPairSigner, createSolanaRpc, devnet, lamports } from '@solana/web3.js';
*
* const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));
Expand All @@ -123,7 +126,7 @@ export function createSplashPoolInstructions(
* const tickSpacing = 64;
* const initialPrice = 0.01;
*
* const { poolAddress, instructions, estInitializationCost } = await createConcentratedLiquidityPool(
* const { poolAddress, instructions, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
* devnetRpc,
* tokenMintOne,
* tokenMintTwo,
Expand All @@ -133,7 +136,7 @@ export function createSplashPoolInstructions(
* );
*/
export async function createConcentratedLiquidityPoolInstructions(
rpc: Rpc<GetMultipleAccountsApi & GetMinimumBalanceForRentExemptionApi>,
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi>,
tokenMintA: Address,
tokenMintB: Address,
tickSpacing: number,
Expand Down Expand Up @@ -204,8 +207,8 @@ export async function createConcentratedLiquidityPoolInstructions(
}),
);

stateSpaces.push(getTokenSizeByProgram(mintA));
stateSpaces.push(getTokenSizeByProgram(mintB));
stateSpaces.push(getTokenSizeForMint(mintA));
stateSpaces.push(getTokenSizeForMint(mintB));
stateSpaces.push(getWhirlpoolSize());

const fullRange = getFullRangeTickIndexes(tickSpacing);
Expand Down Expand Up @@ -247,7 +250,7 @@ export async function createConcentratedLiquidityPoolInstructions(

const nonRefundableRents: Lamports[] = await Promise.all(
stateSpaces.map(async (space) => {
const rentExemption = await rpc.getMinimumBalanceForRentExemption(BigInt(space)).send();
const rentExemption = await calculateMinimumBalance(rpc, space);
return rentExemption;
})
);
Expand Down
24 changes: 24 additions & 0 deletions ts-sdk/whirlpool/src/sysvar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { fetchSysvarRent } from "@solana/sysvars"
import { GetAccountInfoApi, lamports, Rpc } from "@solana/web3.js";
import type { Lamports } from "@solana/web3.js";

/**
* The overhead storage size for accounts.
*/
const ACCOUNT_STORAGE_OVERHEAD = 128;

/**
* Calculates the minimum balance required for rent exemption for a given account size.
*
* @param {Rpc} rpc - The Solana RPC client to fetch sysvar rent data.
* @param {number} dataSize - The size of the account data in bytes.
* @returns {Promise<BigInt>} The minimum balance required for rent exemption in lamports.
*/
export async function calculateMinimumBalance(rpc: Rpc<GetAccountInfoApi>, dataSize: number): Promise<Lamports> {
const rent = await fetchSysvarRent(rpc);
const actualDataLen = BigInt(dataSize + ACCOUNT_STORAGE_OVERHEAD);
const rentLamportsPerYear = rent.lamportsPerByteYear * actualDataLen;
const minimumBalance = rentLamportsPerYear * BigInt(Math.floor(rent.exemptionThreshold));

return lamports(minimumBalance)
}
17 changes: 11 additions & 6 deletions ts-sdk/whirlpool/src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
getCreateAccountWithSeedInstruction,
getTransferSolInstruction,
} from "@solana-program/system";
import { TOKEN_2022_PROGRAM_ADDRESS, getTokenSize } from "@solana-program/token-2022"
import { getTokenSize } from "@solana-program/token";
import { TOKEN_2022_PROGRAM_ADDRESS, getTokenSize as getToken22Size } from "@solana-program/token-2022"
import type { ExtensionArgs, Mint } from "@solana-program/token-2022";
import type { TransferFee } from "@orca-so/whirlpools-core";
import assert from "assert";
Expand Down Expand Up @@ -365,9 +366,13 @@ export function orderMints(mint1: Address, mint2: Address): [Address, Address] {
: [mint2, mint1];
}

export function getTokenSizeByProgram(mint: Account<Mint>): number {
const tokenProgram = mint.programAddress;
return tokenProgram === TOKEN_2022_PROGRAM_ADDRESS
? getTokenSize(getAccountExtensions(mint.data))
: getTokenSize()
/**
* Returns the token size for a given mint account.
*
* @param {Account<Mint>} mint - The mint account to get the token size for.
* @returns {number} The token size for the given mint account.
*/
export function getTokenSizeForMint(mint: Account<Mint>): number {
const extensions = getAccountExtensions(mint.data);
return extensions.length === 0 ? getTokenSize() : getToken22Size(extensions);
}
199 changes: 121 additions & 78 deletions ts-sdk/whirlpool/tests/createPool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ describe("Create Pool", () => {
assert.strictEqual(SPLASH_POOL_TICK_SPACING, pool.data.tickSpacing);
});

it("Should create splash pool with 1 TE", async () => {
it("Should create splash pool with 1 TE token", async () => {
const mint1 = await setupMint();
const mint2 = await setupMintTE();
const mint2 = await setupMintTEFee();
const [mintC, mintD] = orderMints(mint1, mint2);
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6);
Expand Down Expand Up @@ -120,9 +120,9 @@ describe("Create Pool", () => {
assert.strictEqual(SPLASH_POOL_TICK_SPACING, pool.data.tickSpacing);
});

it("Should create splash pool with 2 TE", async () => {
const mint1 = await setupMintTE();
const mint2 = await setupMintTE();
it("Should create splash pool with 2 TE tokens", async () => {
const mint1 = await setupMintTEFee();
const mint2 = await setupMintTEFee();
const [mintC, mintD] = orderMints(mint1, mint2);
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6);
Expand Down Expand Up @@ -158,77 +158,120 @@ describe("Create Pool", () => {
assert.strictEqual(SPLASH_POOL_TICK_SPACING, pool.data.tickSpacing);
});

// it("Should create concentrated liquidity pool", async () => {
// const tickspacing = 64;
// const price = 10;
// const sqrtPrice = priceToSqrtPrice(price, 6, 6);

// let signerAccount = await rpc.getAccountInfo(signer.address).send();
// const balanceBefore = signerAccount.value?.lamports ?? lamports(0n);

// const { instructions, poolAddress, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
// rpc,
// mintA,
// mintB,
// tickspacing,
// price,
// );

// const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress);
// assert.strictEqual(maybePool.exists, false)

// await sendTransaction(instructions);

// const pool = await fetchMaybeWhirlpool(rpc, poolAddress);
// assertAccountExists(pool);

// signerAccount = await rpc.getAccountInfo(signer.address).send();
// const balanceAfter = signerAccount.value?.lamports ?? lamports(0n);
// const balanceChange = balanceBefore - balanceAfter;
// const txFee = 15000n; // 3 signing accounts * 5000 lamports
// const minRentExempt = balanceChange - txFee;
// assert.strictEqual(estInitializationCost, minRentExempt);

// assert.strictEqual(sqrtPrice, pool.data.sqrtPrice);
// assert.strictEqual(mintA, pool.data.tokenMintA);
// assert.strictEqual(mintB, pool.data.tokenMintB);
// assert.strictEqual(tickspacing, pool.data.tickSpacing);
// });

// it("Should create concentrate liquidity pool", async () => {
// const tickspacing = 64;
// const price = 10;
// const sqrtPrice = priceToSqrtPrice(price, 6, 6);

// let signerAccount = await rpc.getAccountInfo(signer.address).send();
// const balanceBefore = signerAccount.value?.lamports ?? lamports(0n);

// const { instructions, poolAddress, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
// rpc,
// mintA,
// mintB,
// tickspacing,
// price,
// );

// const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress);
// assert.strictEqual(maybePool.exists, false)

// await sendTransaction(instructions);

// const pool = await fetchMaybeWhirlpool(rpc, poolAddress);
// assertAccountExists(pool);

// signerAccount = await rpc.getAccountInfo(signer.address).send();
// const balanceAfter = signerAccount.value?.lamports ?? lamports(0n);
// const balanceChange = balanceBefore - balanceAfter;
// const txFee = 15000n; // 3 signing accounts * 5000 lamports
// const minRentExempt = balanceChange - txFee;
// assert.strictEqual(estInitializationCost, minRentExempt);

// assert.strictEqual(sqrtPrice, pool.data.sqrtPrice);
// assert.strictEqual(mintA, pool.data.tokenMintA);
// assert.strictEqual(mintB, pool.data.tokenMintB);
// assert.strictEqual(tickspacing, pool.data.tickSpacing);
// });
it("Should create concentrated liquidity pool", async () => {
const tickSpacing = 64
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6);

let signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceBefore = signerAccount.value?.lamports ?? lamports(0n);

const { instructions, poolAddress, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
rpc,
mintA,
mintB,
tickSpacing,
price,
);

const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress);
assert.strictEqual(maybePool.exists, false)

await sendTransaction(instructions);

const pool = await fetchMaybeWhirlpool(rpc, poolAddress);
assertAccountExists(pool);

signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceAfter = signerAccount.value?.lamports ?? lamports(0n);
const balanceChange = balanceBefore - balanceAfter;
const txFee = 15000n; // 3 signing accounts * 5000 lamports
const minRentExempt = balanceChange - txFee;
assert.strictEqual(estInitializationCost, minRentExempt);

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

it("Should create concentrated liquidity pool with 1 TE token", async () => {
const mint1 = await setupMint();
const mint2 = await setupMintTEFee();
const [mintC, mintD] = orderMints(mint1, mint2);
const tickSpacing = 64;
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6);

let signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceBefore = signerAccount.value?.lamports ?? lamports(0n);

const { instructions, poolAddress, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
rpc,
mintC,
mintD,
tickSpacing,
price,
);

const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress);
assert.strictEqual(maybePool.exists, false)

await sendTransaction(instructions);

const pool = await fetchMaybeWhirlpool(rpc, poolAddress);
assertAccountExists(pool);

signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceAfter = signerAccount.value?.lamports ?? lamports(0n);
const balanceChange = balanceBefore - balanceAfter;
const txFee = 15000n; // 3 signing accounts * 5000 lamports
const minRentExempt = balanceChange - txFee;
assert.strictEqual(estInitializationCost, minRentExempt);

assert.strictEqual(sqrtPrice, pool.data.sqrtPrice);
assert.strictEqual(mintC, pool.data.tokenMintA);
assert.strictEqual(mintD, pool.data.tokenMintB);
assert.strictEqual(tickSpacing, pool.data.tickSpacing);
});

it("Should create splash concentrated liquidity with 2 TE tokens", async () => {
const mint1 = await setupMintTEFee();
const mint2 = await setupMintTEFee();
const [mintC, mintD] = orderMints(mint1, mint2);
const tickSpacing = 64;
const price = 10;
const sqrtPrice = priceToSqrtPrice(price, 6, 6);

let signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceBefore = signerAccount.value?.lamports ?? lamports(0n);

const { instructions, poolAddress, estInitializationCost } = await createConcentratedLiquidityPoolInstructions(
rpc,
mintC,
mintD,
tickSpacing,
price,
);

const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress);
assert.strictEqual(maybePool.exists, false)

await sendTransaction(instructions);

const pool = await fetchMaybeWhirlpool(rpc, poolAddress);
assertAccountExists(pool);

signerAccount = await rpc.getAccountInfo(signer.address).send();
const balanceAfter = signerAccount.value?.lamports ?? lamports(0n);
const balanceChange = balanceBefore - balanceAfter;
const txFee = 15000n; // 3 signing accounts * 5000 lamports
const minRentExempt = balanceChange - txFee;
assert.strictEqual(estInitializationCost, minRentExempt);

assert.strictEqual(sqrtPrice, pool.data.sqrtPrice);
assert.strictEqual(mintC, pool.data.tokenMintA);
assert.strictEqual(mintD, pool.data.tokenMintB);
assert.strictEqual(tickSpacing, pool.data.tickSpacing);
});
});
Loading

0 comments on commit 51bc3a0

Please sign in to comment.