Skip to content

Commit

Permalink
Add unit tests for createPool.ts (#501)
Browse files Browse the repository at this point in the history
* Base createPool test

* Process feedback. Improve createPool.ts tests. Attempt modify sysvarFees.

* Fix calculation of initizialization costs. Add createPool tests.

* Clean up

* Clean up

* Clean up

* Resolve minor comments

* Restructure tests. Add getTokenSizeByProgram function, including tests.

* Resolve comments. Fix minor bug tih token size calculation based on extensions array.

* Format

* Update doc on core-sdks (unrelated)

* Update doc on core-sdks (unrelated)

* Format

* Resolve comments

* Revert changes

* Revert changes

* Revert changes

* Resolve comments

* Resolve comments

* Fromat

---------

Co-authored-by: calintje <csimon@orca.so>
  • Loading branch information
calintje and calintje authored Nov 15, 2024
1 parent 5e1f0f2 commit 3ba9c08
Show file tree
Hide file tree
Showing 16 changed files with 492 additions and 52 deletions.
2 changes: 1 addition & 1 deletion rust-sdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Expected output:
Position in range? true
```

### Quote Example
### Adjust Liquidity Quote Example

The following example demonstrates how to use the increase_liquidity_quote_a function to calculate a quote for increasing liquidity given a token A amount.

Expand Down
2 changes: 1 addition & 1 deletion ts-sdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Expected output:
Position in range? true
```

### Quote Example
### Adjust Liquidity Quote Example
The following example demonstrates how to use the `increaseLiquidityQuoteA` function to calculate a quote for increasing liquidity given a token A amount.

```tsx
Expand Down
48 changes: 30 additions & 18 deletions ts-sdk/whirlpool/src/createPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {
} from "@orca-so/whirlpools-client";
import type {
Address,
GetMinimumBalanceForRentExemptionApi,
GetAccountInfoApi,
GetMultipleAccountsApi,
IInstruction,
Lamports,
Rpc,
TransactionSigner,
} from "@solana/web3.js";
import { generateKeyPairSigner } from "@solana/web3.js";
import { generateKeyPairSigner, lamports } from "@solana/web3.js";
import { fetchSysvarRent } from "@solana/sysvars";
import {
DEFAULT_ADDRESS,
FUNDER,
Expand All @@ -30,9 +31,10 @@ import {
priceToSqrtPrice,
sqrtPriceToTickIndex,
} from "@orca-so/whirlpools-core";
import { fetchAllMint, getTokenSize } from "@solana-program/token-2022";
import { fetchAllMint } from "@solana-program/token-2022";
import assert from "assert";
import { getAccountExtensions, orderMints } from "./token";
import { getTokenSizeForMint, orderMints } from "./token";
import { calculateMinimumBalanceForRentExemption } from "./sysvar";

/**
* Represents the instructions and metadata for creating a pool.
Expand Down Expand Up @@ -81,7 +83,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 +112,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 +125,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 +135,7 @@ export function createSplashPoolInstructions(
* );
*/
export async function createConcentratedLiquidityPoolInstructions(
rpc: Rpc<GetMultipleAccountsApi & GetMinimumBalanceForRentExemptionApi>,
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi>,
tokenMintA: Address,
tokenMintB: Address,
tickSpacing: number,
Expand All @@ -149,7 +151,9 @@ 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;

const rent = await fetchSysvarRent(rpc);
let nonRefundableRent: bigint = 0n;

// 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 +208,18 @@ export async function createConcentratedLiquidityPoolInstructions(
}),
);

stateSpace += getTokenSize(getAccountExtensions(mintA.data));
stateSpace += getTokenSize(getAccountExtensions(mintB.data));
stateSpace += getWhirlpoolSize();
nonRefundableRent += calculateMinimumBalanceForRentExemption(
rent,
getTokenSizeForMint(mintA),
);
nonRefundableRent += calculateMinimumBalanceForRentExemption(
rent,
getTokenSizeForMint(mintB),
);
nonRefundableRent += calculateMinimumBalanceForRentExemption(
rent,
getWhirlpoolSize(),
);

const fullRange = getFullRangeTickIndexes(tickSpacing);
const lowerTickIndex = getTickArrayStartTickIndex(
Expand Down Expand Up @@ -242,16 +255,15 @@ export async function createConcentratedLiquidityPoolInstructions(
startTickIndex: tickArrayIndexes[i],
}),
);
stateSpace += getTickArraySize();
nonRefundableRent += calculateMinimumBalanceForRentExemption(
rent,
getTickArraySize(),
);
}

const nonRefundableRent = await rpc
.getMinimumBalanceForRentExemption(BigInt(stateSpace))
.send();

return {
instructions,
poolAddress,
estInitializationCost: nonRefundableRent,
estInitializationCost: lamports(nonRefundableRent),
};
}
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 type { SysvarRent } from "@solana/sysvars";

/**
* 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 {bigint} The minimum balance required for rent exemption in lamports.
*/
export function calculateMinimumBalanceForRentExemption(
rent: SysvarRent,
dataSize: number,
): bigint {
const dataSizeForRent = BigInt(dataSize + ACCOUNT_STORAGE_OVERHEAD);
const rentLamportsPerYear = rent.lamportsPerByteYear * dataSizeForRent;
const minimumBalance = rentLamportsPerYear * BigInt(rent.exemptionThreshold);

return minimumBalance;
}
16 changes: 15 additions & 1 deletion ts-sdk/whirlpool/src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getCreateAssociatedTokenInstruction,
getInitializeAccount3Instruction,
getSyncNativeInstruction,
getTokenSize,
TOKEN_PROGRAM_ADDRESS,
} from "@solana-program/token";
import type {
Expand All @@ -33,6 +32,8 @@ import {
getCreateAccountWithSeedInstruction,
getTransferSolInstruction,
} from "@solana-program/system";
import { getTokenSize } from "@solana-program/token";
import { getTokenSize as getTokenSizeWithExtensions } 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 @@ -364,3 +365,16 @@ export function orderMints(mint1: Address, mint2: Address): [Address, Address] {
? [mint1, mint2]
: [mint2, mint1];
}

/**
* 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()
: getTokenSizeWithExtensions(extensions);
}
Loading

0 comments on commit 3ba9c08

Please sign in to comment.