From bdbd6adb2cfe74574e3112904cf3510a2f9aedfb Mon Sep 17 00:00:00 2001 From: Will <82029448+wjthieme@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:49:16 -0500 Subject: [PATCH] Improve the ts-sdk testing setup (#448) * Improve the ts-sdk testing setup * Remove unused files * Lint --- ts-sdk/whirlpool/tests/e2e.test.ts | 123 ++---- ts-sdk/whirlpool/tests/pool.test.ts | 90 +++- ts-sdk/whirlpool/tests/position.test.ts | 50 ++- ts-sdk/whirlpool/tests/token.test.ts | 412 ++++++------------ .../tests/{ => utils}/assertInstruction.ts | 0 ts-sdk/whirlpool/tests/{ => utils}/mockRpc.ts | 231 ++-------- ts-sdk/whirlpool/tests/utils/program.ts | 143 ++++++ ts-sdk/whirlpool/tests/utils/token.ts | 80 ++++ .../whirlpool/tests/utils/tokenExtensions.ts | 94 ++++ 9 files changed, 659 insertions(+), 564 deletions(-) rename ts-sdk/whirlpool/tests/{ => utils}/assertInstruction.ts (100%) rename ts-sdk/whirlpool/tests/{ => utils}/mockRpc.ts (55%) create mode 100644 ts-sdk/whirlpool/tests/utils/program.ts create mode 100644 ts-sdk/whirlpool/tests/utils/token.ts create mode 100644 ts-sdk/whirlpool/tests/utils/tokenExtensions.ts diff --git a/ts-sdk/whirlpool/tests/e2e.test.ts b/ts-sdk/whirlpool/tests/e2e.test.ts index 8c692272a..7416604fe 100644 --- a/ts-sdk/whirlpool/tests/e2e.test.ts +++ b/ts-sdk/whirlpool/tests/e2e.test.ts @@ -7,87 +7,37 @@ import { openFullRangePositionInstructions, increaseLiquidityInstructions, } from "../src/increaseLiquidity"; -import { - TOKEN_MINT_1, - TOKEN_MINT_2, - sendTransaction, - rpc, - initPayer, - setAccount, -} from "./mockRpc"; -import { setDefaultFunder, SPLASH_POOL_TICK_SPACING } from "../src/config"; +import { sendTransaction, rpc } from "./utils/mockRpc"; +import { SPLASH_POOL_TICK_SPACING } from "../src/config"; import { swapInstructions } from "../src/swap"; -import type { Address, TransactionSigner } from "@solana/web3.js"; +import type { Address } from "@solana/web3.js"; import { harvestPositionInstructions } from "../src/harvest"; import { decreaseLiquidityInstructions, closePositionInstructions, } from "../src/decreaseLiquidity"; -import { - AccountState, - fetchToken, - findAssociatedTokenPda, - getTokenEncoder, - TOKEN_PROGRAM_ADDRESS, -} from "@solana-program/token"; +import { fetchToken } from "@solana-program/token"; import { fetchPosition, fetchWhirlpool, getPositionAddress, } from "@orca-so/whirlpools-client"; import assert from "assert"; +import { setupAta, setupMint } from "./utils/token"; +import { orderMints } from "../src/token"; describe("e2e", () => { + let mintA: Address; + let mintB: Address; let ataA: Address; let ataB: Address; - let payer: TransactionSigner; beforeAll(async () => { - payer = await initPayer(); - setDefaultFunder(payer); - - [ataA, ataB] = await Promise.all([ - findAssociatedTokenPda({ - mint: TOKEN_MINT_1, - owner: payer.address, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }).then((x) => x[0]), - findAssociatedTokenPda({ - mint: TOKEN_MINT_2, - owner: payer.address, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }).then((x) => x[0]), - ]); - - setAccount( - ataA, - getTokenEncoder().encode({ - mint: TOKEN_MINT_1, - owner: payer.address, - amount: 500e9, - delegate: null, - state: AccountState.Initialized, - isNative: null, - delegatedAmount: 0, - closeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ); - - setAccount( - ataB, - getTokenEncoder().encode({ - mint: TOKEN_MINT_2, - owner: payer.address, - amount: 500e9, - delegate: null, - state: AccountState.Initialized, - isNative: null, - delegatedAmount: 0, - closeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ); + const mint1 = await setupMint({ decimals: 9 }); + const mint2 = await setupMint({ decimals: 6 }); + [mintA, mintB] = orderMints(mint1, mint2); + ataA = await setupAta(mintA, { amount: 500e9 }); + ataB = await setupAta(mintB, { amount: 500e9 }); }); const fetchPositionByMint = async (positionMint: Address) => { @@ -97,12 +47,12 @@ describe("e2e", () => { const testInitSplashPool = async () => { const { instructions: createPoolInstructions, poolAddress } = - await createSplashPoolInstructions(rpc, TOKEN_MINT_1, TOKEN_MINT_2); - await sendTransaction(createPoolInstructions, payer); + await createSplashPoolInstructions(rpc, mintA, mintB); + await sendTransaction(createPoolInstructions); const pool = await fetchWhirlpool(rpc, poolAddress); - assert.strictEqual(pool.data.tokenMintA, TOKEN_MINT_1); - assert.strictEqual(pool.data.tokenMintB, TOKEN_MINT_2); + assert.strictEqual(pool.data.tokenMintA, mintA); + assert.strictEqual(pool.data.tokenMintB, mintB); assert.strictEqual(pool.data.tickSpacing, SPLASH_POOL_TICK_SPACING); return poolAddress; @@ -110,17 +60,12 @@ describe("e2e", () => { const testInitConcentratedLiquidityPool = async () => { const { instructions: createPoolInstructions, poolAddress } = - await createConcentratedLiquidityPoolInstructions( - rpc, - TOKEN_MINT_1, - TOKEN_MINT_2, - 128, - ); - await sendTransaction(createPoolInstructions, payer); + await createConcentratedLiquidityPoolInstructions(rpc, mintA, mintB, 128); + await sendTransaction(createPoolInstructions); const pool = await fetchWhirlpool(rpc, poolAddress); - assert.strictEqual(pool.data.tokenMintA, TOKEN_MINT_1); - assert.strictEqual(pool.data.tokenMintB, TOKEN_MINT_2); + assert.strictEqual(pool.data.tokenMintA, mintA); + assert.strictEqual(pool.data.tokenMintB, mintB); assert.strictEqual(pool.data.tickSpacing, 128); return poolAddress; @@ -134,7 +79,7 @@ describe("e2e", () => { await openFullRangePositionInstructions(rpc, poolAddress, { liquidity: 1000000000n, }); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const positionAfter = await fetchPositionByMint(positionMint); const tokenAAfter = await fetchToken(rpc, ataA); @@ -162,7 +107,7 @@ describe("e2e", () => { positionMint, { liquidity: 10000n }, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const positionAfter = await fetchPositionByMint(positionMint); const tokenAAfter = await fetchToken(rpc, ataA); @@ -191,7 +136,7 @@ describe("e2e", () => { positionMint, { liquidity: 10000n }, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const positionAfter = await fetchPositionByMint(positionMint); const tokenAAfter = await fetchToken(rpc, ataA); @@ -218,7 +163,7 @@ describe("e2e", () => { rpc, positionMint, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const tokenAAfter = await fetchToken(rpc, ataA); const tokenBAfter = await fetchToken(rpc, ataB); @@ -241,7 +186,7 @@ describe("e2e", () => { positionMint, { liquidity: 1000000000n }, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const positionAfter = await rpc.getMultipleAccounts([positionMint]).send(); const tokenAAfter = await fetchToken(rpc, ataA); @@ -263,10 +208,10 @@ describe("e2e", () => { const { instructions, quote } = await swapInstructions( rpc, - { inputAmount: 100n, mint: TOKEN_MINT_1 }, + { inputAmount: 100n, mint: mintA }, poolAddress, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const tokenAAfter = await fetchToken(rpc, ataA); const tokenBAfter = await fetchToken(rpc, ataB); @@ -287,10 +232,10 @@ describe("e2e", () => { const { instructions, quote } = await swapInstructions( rpc, - { outputAmount: 100n, mint: TOKEN_MINT_1 }, + { outputAmount: 100n, mint: mintA }, poolAddress, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const tokenAAfter = await fetchToken(rpc, ataA); const tokenBAfter = await fetchToken(rpc, ataB); @@ -311,10 +256,10 @@ describe("e2e", () => { const { instructions, quote } = await swapInstructions( rpc, - { inputAmount: 100000n, mint: TOKEN_MINT_2 }, + { inputAmount: 100000n, mint: mintB }, poolAddress, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const tokenAAfter = await fetchToken(rpc, ataA); const tokenBAfter = await fetchToken(rpc, ataB); @@ -335,10 +280,10 @@ describe("e2e", () => { const { instructions, quote } = await swapInstructions( rpc, - { outputAmount: 100000n, mint: TOKEN_MINT_2 }, + { outputAmount: 100000n, mint: mintB }, poolAddress, ); - await sendTransaction(instructions, payer); + await sendTransaction(instructions); const tokenAAfter = await fetchToken(rpc, ataA); const tokenBAfter = await fetchToken(rpc, ataB); diff --git a/ts-sdk/whirlpool/tests/pool.test.ts b/ts-sdk/whirlpool/tests/pool.test.ts index 3e304ce7c..e20e81209 100644 --- a/ts-sdk/whirlpool/tests/pool.test.ts +++ b/ts-sdk/whirlpool/tests/pool.test.ts @@ -1,5 +1,89 @@ -import { describe } from "vitest"; +import { describe, it, beforeAll } from "vitest"; +import { + fetchConcentratedLiquidityPool, + fetchSplashPool, + fetchWhirlpoolsByTokenPair, +} from "../src/pool"; +import { rpc } from "./utils/mockRpc"; +import assert from "assert"; +import { + SPLASH_POOL_TICK_SPACING, + WHIRLPOOLS_CONFIG_ADDRESS, +} from "../src/config"; +import type { Address } from "@solana/web3.js"; +import { setupMint } from "./utils/token"; +import { setupWhirlpool } from "./utils/program"; +import { getWhirlpoolAddress } from "@orca-so/whirlpools-client"; +import { orderMints } from "../src/token"; -describe.skip("Fetch Pool", () => { - // TODO: <- +describe("Fetch Pool", () => { + let mintA: Address; + let mintB: Address; + let defaultPool: Address; + let concentratedPool: Address; + let splashPool: Address; + + beforeAll(async () => { + const mint1 = await setupMint(); + const mint2 = await setupMint(); + [mintA, mintB] = orderMints(mint1, mint2); + concentratedPool = await setupWhirlpool(mintA, mintB, 64); + defaultPool = await getWhirlpoolAddress( + WHIRLPOOLS_CONFIG_ADDRESS, + mintA, + mintB, + 128, + ).then((x) => x[0]); + splashPool = await setupWhirlpool(mintA, mintB, SPLASH_POOL_TICK_SPACING); + }); + + it("Should be able to fetch a splash pool", async () => { + const pool = await fetchSplashPool(rpc, mintA, mintB); + assert.strictEqual(pool.initialized, true); + assert.strictEqual(pool.liquidity, 0n); + assert.strictEqual(pool.tickSpacing, SPLASH_POOL_TICK_SPACING); + assert.strictEqual(pool.address, splashPool); + assert.strictEqual(pool.tokenMintA, mintA); + assert.strictEqual(pool.tokenMintB, mintB); + assert.strictEqual(pool.feeRate, 1000); + assert.strictEqual(pool.protocolFeeRate, 100); + assert.strictEqual(pool.whirlpoolsConfig, WHIRLPOOLS_CONFIG_ADDRESS); + }); + + it("Should be able to fetch a concentrated liquidity pool", async () => { + const pool = await fetchConcentratedLiquidityPool(rpc, mintA, mintB, 64); + assert.strictEqual(pool.initialized, true); + assert.strictEqual(pool.liquidity, 0n); + assert.strictEqual(pool.tickSpacing, 64); + assert.strictEqual(pool.address, concentratedPool); + assert.strictEqual(pool.tokenMintA, mintA); + assert.strictEqual(pool.tokenMintB, mintB); + assert.strictEqual(pool.feeRate, 300); + assert.strictEqual(pool.protocolFeeRate, 100); + assert.strictEqual(pool.whirlpoolsConfig, WHIRLPOOLS_CONFIG_ADDRESS); + }); + + it("Should be able to try fetching a non-existent pool", async () => { + const pool = await fetchConcentratedLiquidityPool(rpc, mintA, mintB, 128); + assert.strictEqual(pool.initialized, false); + assert.strictEqual(pool.tickSpacing, 128); + assert.strictEqual(pool.address, defaultPool); + assert.strictEqual(pool.tokenMintA, mintA); + assert.strictEqual(pool.tokenMintB, mintB); + assert.strictEqual(pool.feeRate, 1000); + assert.strictEqual(pool.protocolFeeRate, 100); + assert.strictEqual(pool.whirlpoolsConfig, WHIRLPOOLS_CONFIG_ADDRESS); + }); + + // TODO: Enable this test once solana-bankrun exposes getProgramAccounts + it.skip("Should be able to fetch all pools for a pair", async () => { + const pools = await fetchWhirlpoolsByTokenPair(rpc, mintA, mintB); + assert.strictEqual(pools.length, 3); + assert.strictEqual(pools[0].initialized, true); + assert.strictEqual(pools[0].tickSpacing, 64); + assert.strictEqual(pools[1].initialized, true); + assert.strictEqual(pools[1].tickSpacing, SPLASH_POOL_TICK_SPACING); + assert.strictEqual(pools[2].initialized, false); + assert.strictEqual(pools[2].tickSpacing, 128); + }); }); diff --git a/ts-sdk/whirlpool/tests/position.test.ts b/ts-sdk/whirlpool/tests/position.test.ts index 4e030fc6f..1fc6af0a6 100644 --- a/ts-sdk/whirlpool/tests/position.test.ts +++ b/ts-sdk/whirlpool/tests/position.test.ts @@ -1,5 +1,49 @@ -import { describe } from "vitest"; +import type { Address} from "@solana/web3.js"; +import { generateKeyPairSigner } from "@solana/web3.js"; +import { assert, beforeAll, describe, it } from "vitest"; +import { setupAta, setupMint } from "./utils/token"; +import { + setupPosition, + setupPositionBundle, + setupTEPosition, + setupWhirlpool, +} from "./utils/program"; +import { SPLASH_POOL_TICK_SPACING } from "../src/config"; +import { fetchPositionsForOwner } from "../src/position"; +import { rpc, signer } from "./utils/mockRpc"; +import { orderMints } from "../src/token"; -describe.skip("Fetch Position", () => { - // TODO: <- +describe("Fetch Position", () => { + let mintA: Address; + let mintB: Address; + let pool: Address; + let splashPool: Address; + + beforeAll(async () => { + const mint1 = await setupMint(); + const mint2 = await setupMint(); + [mintA, mintB] = orderMints(mint1, mint2); + await setupAta(mintA, { amount: 500e9 }); + await setupAta(mintB, { amount: 500e9 }); + pool = await setupWhirlpool(mintA, mintB, 128); + splashPool = await setupWhirlpool(mintA, mintB, SPLASH_POOL_TICK_SPACING); + await setupPosition(pool); + await setupPosition(splashPool); + await setupTEPosition(pool); + await setupPositionBundle(pool); + await setupPositionBundle(splashPool, [{}, {}]); + }); + + // TODO: enable this when solana-bankrun supports gpa + it.skip("Should fetch all positions for an address", async () => { + const positions = await fetchPositionsForOwner(rpc, signer.address); + assert.strictEqual(positions.length, 5); + }); + + // TODO: enable this when solana-bankrun supports gpa + it.skip("Should fetch no positions for a different address", async () => { + const other = await generateKeyPairSigner(); + const positions = await fetchPositionsForOwner(rpc, other.address); + assert.strictEqual(positions.length, 0); + }); }); diff --git a/ts-sdk/whirlpool/tests/token.test.ts b/ts-sdk/whirlpool/tests/token.test.ts index 6790cea70..4392794c0 100644 --- a/ts-sdk/whirlpool/tests/token.test.ts +++ b/ts-sdk/whirlpool/tests/token.test.ts @@ -1,26 +1,12 @@ -import { describe, it, beforeAll, afterAll, afterEach, vi } from "vitest"; +import { describe, it, afterEach, vi, beforeAll, afterAll } from "vitest"; +import { deleteAccount, rpc, signer } from "./utils/mockRpc"; import { - setAccount, - rpc, - TOKEN_2022_MINT, - TOKEN_MINT_1, - TOKEN_MINT_2, -} from "./mockRpc"; -import { - AccountState, findAssociatedTokenPda, - getTokenEncoder, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token"; import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022"; -import { - DEFAULT_ADDRESS, - resetConfiguration, - setSolWrappingStrategy, -} from "../src/config"; -import type { Address, TransactionSigner } from "@solana/web3.js"; -import { address, createNoopSigner, generateKeyPairSigner } from "@solana/web3.js"; -import { NATIVE_MINT, orderMints, prepareTokenAccountsInstructions } from "../src/token"; +import { resetConfiguration, setSolWrappingStrategy } from "../src/config"; +import { NATIVE_MINT, prepareTokenAccountsInstructions, orderMints } from "../src/token"; import assert from "assert"; import { assertCloseAccountInstruction, @@ -30,138 +16,100 @@ import { assertInitializeAccountInstruction, assertSolTransferInstruction, assertSyncNativeInstruction, -} from "./assertInstruction"; +} from "./utils/assertInstruction"; +import { address, type Address } from "@solana/web3.js"; +import { setupAta, setupMint } from "./utils/token"; +import { setupMintTE } from "./utils/tokenExtensions"; describe("Token Account Creation", () => { - let signer: TransactionSigner = createNoopSigner(DEFAULT_ADDRESS); - let existingTokenAccount: Address = DEFAULT_ADDRESS; - let nonExistingTokenAccount: Address = DEFAULT_ADDRESS; - let nativeMintTokenAccount: Address = DEFAULT_ADDRESS; - - const createNativeMintTokenAccount = async () => { - setAccount( - nativeMintTokenAccount, - getTokenEncoder().encode({ - mint: TOKEN_MINT_1, - owner: signer.address, - amount: 500, - delegate: null, - state: AccountState.Initialized, - isNative: null, - delegatedAmount: 0, - closeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ); - }; + let mintA: Address; + let mintB: Address; + let mintTE: Address; + let ataA: Address; + let ataB: Address; + let ataTE: Address; + let ataNative: Address; beforeAll(async () => { vi.useFakeTimers(); - signer = await generateKeyPairSigner(); - [existingTokenAccount, nonExistingTokenAccount, nativeMintTokenAccount] = - await Promise.all( - [TOKEN_MINT_1, TOKEN_MINT_2, NATIVE_MINT].map((mint) => - findAssociatedTokenPda({ - owner: signer.address, - mint, - tokenProgram: TOKEN_PROGRAM_ADDRESS, - }).then((x) => x[0]), - ), - ); - await setAccount( - existingTokenAccount, - getTokenEncoder().encode({ - mint: TOKEN_MINT_1, - owner: signer.address, - amount: 500, - delegate: null, - state: AccountState.Initialized, - isNative: null, - delegatedAmount: 0, - closeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ); - }); - - afterAll(async () => { - vi.useRealTimers(); - await setAccount(existingTokenAccount, null); + mintA = await setupMint(); + mintB = await setupMint(); + mintTE = await setupMintTE(); + ataA = await setupAta(mintA); + ataB = await findAssociatedTokenPda({ + mint: mintB, + owner: signer.address, + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }).then((x) => x[0]); + ataTE = await findAssociatedTokenPda({ + mint: mintTE, + owner: signer.address, + tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, + }).then((x) => x[0]); + ataNative = await findAssociatedTokenPda({ + mint: NATIVE_MINT, + owner: signer.address, + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }).then((x) => x[0]); }); afterEach(async () => { + await deleteAccount(ataNative); resetConfiguration(); }); + afterAll(async () => { + vi.useRealTimers(); + }); + it("No native mint", async () => { const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 2); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); assert.strictEqual(result.createInstructions.length, 1); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assert.strictEqual(result.cleanupInstructions.length, 0); }); it("No native mint with balances", async () => { const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 2); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); assert.strictEqual(result.createInstructions.length, 1); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assert.strictEqual(result.cleanupInstructions.length, 0); }); it("Token 2022 token", async () => { const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_2022_MINT, + mintTE, ]); - const tokenAddress = await findAssociatedTokenPda({ - owner: signer.address, - mint: TOKEN_2022_MINT, - tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, - }).then((x) => x[0]); - assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 1); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_2022_MINT], - tokenAddress, - ); + assert.strictEqual(result.tokenAccountAddresses[mintTE], ataTE); assert.strictEqual(result.createInstructions.length, 1); assertCreateAtaInstruction(result.createInstructions[0], { - ata: tokenAddress, + ata: ataTE, owner: signer.address, - mint: TOKEN_2022_MINT, + mint: mintTE, tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, }); assert.strictEqual(result.cleanupInstructions.length, 0); @@ -171,32 +119,23 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("none"); const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, NATIVE_MINT, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 2); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAtaInstruction(result.createInstructions[1], { - ata: nativeMintTokenAccount, + ata: ataNative, owner: signer.address, mint: NATIVE_MINT, }); @@ -207,32 +146,23 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("none"); const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, [NATIVE_MINT]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 2); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAtaInstruction(result.createInstructions[1], { - ata: nativeMintTokenAccount, + ata: ataNative, owner: signer.address, mint: NATIVE_MINT, }); @@ -243,106 +173,77 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("ata"); const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, NATIVE_MINT, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 2); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAtaInstruction(result.createInstructions[1], { - ata: nativeMintTokenAccount, + ata: ataNative, owner: signer.address, mint: NATIVE_MINT, }); assert.strictEqual(result.cleanupInstructions.length, 1); assertCloseAccountInstruction(result.cleanupInstructions[0], { - account: nativeMintTokenAccount, + account: ataNative, owner: signer.address, }); }); it("Native mint and wrapping is ata but already exists", async () => { + await setupAta(NATIVE_MINT); setSolWrappingStrategy("ata"); - await createNativeMintTokenAccount(); const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, NATIVE_MINT, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 1); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assert.strictEqual(result.cleanupInstructions.length, 0); - - setAccount(nativeMintTokenAccount, null); }); it("Native mint and wrapping is ata with balances", async () => { setSolWrappingStrategy("ata"); const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, [NATIVE_MINT]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 4); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAtaInstruction(result.createInstructions[1], { - ata: nativeMintTokenAccount, + ata: ataNative, owner: signer.address, mint: NATIVE_MINT, }); @@ -356,39 +257,30 @@ describe("Token Account Creation", () => { }); assert.strictEqual(result.cleanupInstructions.length, 1); assertCloseAccountInstruction(result.cleanupInstructions[0], { - account: nativeMintTokenAccount, + account: ataNative, owner: signer.address, }); }); it("Native mint and wrapping is ata but already exists with balances", async () => { + await setupAta(NATIVE_MINT); setSolWrappingStrategy("ata"); - await createNativeMintTokenAccount(); const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, [NATIVE_MINT]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.strictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 3); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertSolTransferInstruction(result.createInstructions[1], { from: signer.address, @@ -399,37 +291,26 @@ describe("Token Account Creation", () => { account: result.tokenAccountAddresses[NATIVE_MINT], }); assert.strictEqual(result.cleanupInstructions.length, 0); - - setAccount(nativeMintTokenAccount, null); }); it("Native mint and wrapping is seed", async () => { setSolWrappingStrategy("seed"); const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, NATIVE_MINT, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.notStrictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.notStrictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 3); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAccountWithSeedInstruction(result.createInstructions[1], { account: result.tokenAccountAddresses[NATIVE_MINT], @@ -453,29 +334,20 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("seed"); const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, [NATIVE_MINT]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.notStrictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.notStrictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 5); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAccountWithSeedInstruction(result.createInstructions[1], { account: result.tokenAccountAddresses[NATIVE_MINT], @@ -507,29 +379,20 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("keypair"); const result = await prepareTokenAccountsInstructions(rpc, signer, [ - TOKEN_MINT_1, - TOKEN_MINT_2, + mintA, + mintB, NATIVE_MINT, ]); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.notStrictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.notStrictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 3); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAccountInstruction(result.createInstructions[1], { account: result.tokenAccountAddresses[NATIVE_MINT], @@ -552,29 +415,20 @@ describe("Token Account Creation", () => { setSolWrappingStrategy("keypair"); const result = await prepareTokenAccountsInstructions(rpc, signer, { - [TOKEN_MINT_1]: 100n, - [TOKEN_MINT_2]: 100n, + [mintA]: 100n, + [mintB]: 100n, [NATIVE_MINT]: 100n, }); assert.strictEqual(Object.keys(result.tokenAccountAddresses).length, 3); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_1], - existingTokenAccount, - ); - assert.strictEqual( - result.tokenAccountAddresses[TOKEN_MINT_2], - nonExistingTokenAccount, - ); - assert.notStrictEqual( - result.tokenAccountAddresses[NATIVE_MINT], - nativeMintTokenAccount, - ); + assert.strictEqual(result.tokenAccountAddresses[mintA], ataA); + assert.strictEqual(result.tokenAccountAddresses[mintB], ataB); + assert.notStrictEqual(result.tokenAccountAddresses[NATIVE_MINT], ataNative); assert.strictEqual(result.createInstructions.length, 5); assertCreateAtaInstruction(result.createInstructions[0], { - ata: nonExistingTokenAccount, + ata: ataB, owner: signer.address, - mint: TOKEN_MINT_2, + mint: mintB, }); assertCreateAccountInstruction(result.createInstructions[1], { account: result.tokenAccountAddresses[NATIVE_MINT], diff --git a/ts-sdk/whirlpool/tests/assertInstruction.ts b/ts-sdk/whirlpool/tests/utils/assertInstruction.ts similarity index 100% rename from ts-sdk/whirlpool/tests/assertInstruction.ts rename to ts-sdk/whirlpool/tests/utils/assertInstruction.ts diff --git a/ts-sdk/whirlpool/tests/mockRpc.ts b/ts-sdk/whirlpool/tests/utils/mockRpc.ts similarity index 55% rename from ts-sdk/whirlpool/tests/mockRpc.ts rename to ts-sdk/whirlpool/tests/utils/mockRpc.ts index 78b1840e4..ece39d38c 100644 --- a/ts-sdk/whirlpool/tests/mockRpc.ts +++ b/ts-sdk/whirlpool/tests/utils/mockRpc.ts @@ -1,14 +1,9 @@ -import { getMintEncoder, TOKEN_PROGRAM_ADDRESS } from "@solana-program/token"; -import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022"; import type { Address, IInstruction, - ReadonlyUint8Array, - TransactionSigner, VariableSizeDecoder, } from "@solana/web3.js"; import { - address, appendTransactionMessageInstructions, assertIsAddress, createSolanaRpcFromTransport, @@ -30,216 +25,64 @@ import { import assert from "assert"; import type { ProgramTestContext } from "solana-bankrun/dist/internal"; import { Account, startAnchor } from "solana-bankrun/dist/internal"; -import { - DEFAULT_ADDRESS, - SPLASH_POOL_TICK_SPACING, - WHIRLPOOLS_CONFIG_ADDRESS, -} from "../src/config"; -import { NATIVE_MINT } from "../src/token"; import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system"; -import { - getFeeTierEncoder, - getWhirlpoolsConfigEncoder, - WHIRLPOOL_PROGRAM_ADDRESS, -} from "@orca-so/whirlpools-client"; +import { WHIRLPOOL_PROGRAM_ADDRESS } from "@orca-so/whirlpools-client"; +import { setDefaultFunder, setWhirlpoolsConfig } from "../../src/config"; +import { setupConfigAndFeeTiers } from "./program"; -export const [ - TOKEN_MINT_1, - TOKEN_MINT_2, - TOKEN_2022_MINT, - TOKEN_2022_MINT_TRANSFER_FEE, - TOKEN_2022_MINT_TRANSFER_HOOK, -] = [...Array(25).keys()].map((i) => { - const bytes = Array.from({ length: 32 }, () => i + 1); - return getAddressDecoder().decode(new Uint8Array(bytes)); -}); - -export const CONCENTRATED_POOL_FEE_TIER = address( - "BGnhGXT9CCt5WYS23zg9sqsAT2MGXkq7VSwch9pML82W", -); -export const SPLASH_POOL_FEE_TIER = address( - "zVmMsL5qGh7txhTHFgGZcFQpSsxSx6DBLJ3u113PBer", -); +export const signer = await generateKeyPairSigner(); +setDefaultFunder(signer); function toBytes(address: Address): Uint8Array { return new Uint8Array(getAddressEncoder().encode(address)); } -function systemAccount(): Account { - return new Account( - BigInt(1e9), - new Uint8Array(), - toBytes(SYSTEM_PROGRAM_ADDRESS), - false, - 0n, - ); -} - -function toAccount(data: ReadonlyUint8Array | null, owner?: Address): Account { - const bytes = data ?? new Uint8Array(); - return new Account( - BigInt(bytes.length ?? 0) * 10n, - new Uint8Array(bytes), - toBytes(owner ?? SYSTEM_PROGRAM_ADDRESS), - false, - 0n, - ); -} - -const initialAccounts: [Uint8Array, Account][] = [ - [ - toBytes(TOKEN_MINT_1), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 6, - isInitialized: true, - freezeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(TOKEN_MINT_2), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 9, - isInitialized: true, - freezeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(NATIVE_MINT), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 9, - isInitialized: true, - freezeAuthority: null, - }), - TOKEN_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(TOKEN_2022_MINT), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 9, - isInitialized: true, - freezeAuthority: null, - }), - TOKEN_2022_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(TOKEN_2022_MINT_TRANSFER_FEE), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 9, - isInitialized: true, - freezeAuthority: null, - // TODO: <- transfer fee config - }), - TOKEN_2022_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(TOKEN_2022_MINT_TRANSFER_HOOK), - toAccount( - getMintEncoder().encode({ - mintAuthority: DEFAULT_ADDRESS, - supply: 1000000000, - decimals: 9, - isInitialized: true, - freezeAuthority: null, - // TODO: <- transfer hook config - }), - TOKEN_2022_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(WHIRLPOOLS_CONFIG_ADDRESS), - toAccount( - getWhirlpoolsConfigEncoder().encode({ - feeAuthority: DEFAULT_ADDRESS, - collectProtocolFeesAuthority: DEFAULT_ADDRESS, - rewardEmissionsSuperAuthority: DEFAULT_ADDRESS, - defaultProtocolFeeRate: 100, - }), - WHIRLPOOL_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(CONCENTRATED_POOL_FEE_TIER), - toAccount( - getFeeTierEncoder().encode({ - whirlpoolsConfig: WHIRLPOOLS_CONFIG_ADDRESS, - tickSpacing: 128, - defaultFeeRate: 10000, - }), - WHIRLPOOL_PROGRAM_ADDRESS, - ), - ], - [ - toBytes(SPLASH_POOL_FEE_TIER), - toAccount( - getFeeTierEncoder().encode({ - whirlpoolsConfig: WHIRLPOOLS_CONFIG_ADDRESS, - tickSpacing: SPLASH_POOL_TICK_SPACING, - defaultFeeRate: 10000, - }), - WHIRLPOOL_PROGRAM_ADDRESS, - ), - ], -]; - let _testContext: ProgramTestContext | null = null; export async function getTestContext(): Promise { if (_testContext == null) { _testContext = await startAnchor( "../../", [["whirlpool", toBytes(WHIRLPOOL_PROGRAM_ADDRESS)]], - initialAccounts, + [ + [ + toBytes(signer.address), + new Account( + BigInt(100e9), + new Uint8Array(), + toBytes(SYSTEM_PROGRAM_ADDRESS), + false, + 0n, + ), + ], + ], ); + + const configAddress = await setupConfigAndFeeTiers(); + setWhirlpoolsConfig(configAddress); } return _testContext; } -export async function setAccount( - address: Address, - data: ReadonlyUint8Array | null, - owner?: Address, -) { - const testContext = await getTestContext(); - testContext.setAccount(toBytes(address), toAccount(data, owner)); -} - -export async function initPayer(): Promise { - const payer = await generateKeyPairSigner(); +export async function deleteAccount(address: Address) { const testContext = await getTestContext(); - testContext.setAccount(toBytes(payer.address), systemAccount()); - return payer; + testContext.setAccount( + toBytes(address), + new Account( + BigInt(0), + new Uint8Array(), + toBytes(SYSTEM_PROGRAM_ADDRESS), + false, + 0n, + ), + ); } -export async function sendTransaction( - ixs: IInstruction[], - payer: TransactionSigner, -) { +export async function sendTransaction(ixs: IInstruction[]) { const blockhash = await rpc.getLatestBlockhash().send(); const transaction = await pipe( createTransactionMessage({ version: 0 }), (x) => appendTransactionMessageInstructions(ixs, x), - (x) => setTransactionMessageFeePayerSigner(payer, x), + (x) => setTransactionMessageFeePayerSigner(signer, x), (x) => setTransactionMessageLifetimeUsingBlockhash(blockhash.value, x), (x) => signTransactionMessageWithSigners(x), ); @@ -274,9 +117,11 @@ async function getAccountData(address: unknown, opts: unknown): Promise { assertIsAddress(address); const testContext = await getTestContext(); const account = await testContext.banksClient.getAccount(toBytes(address)); + if (account == null || account.lamports === 0n) { return null as T; } + return { data: [decoder.decode(account.data), encoding], executable: false, @@ -338,6 +183,12 @@ async function mockTransport( addresses.map((x) => getAccountData(x, opts)), ); return getResponseWithContext(accountsData); + case "getProgramAccounts": + throw new Error("gpa is not yet exposed through solana-bankrun"); + case "getTokenAccountsByOwner": + throw new Error( + "getTokenAccountsByOwner is not yet exposed through solana-bankrun", + ); case "getMinimumBalanceForRentExemption": const space = config.payload.params[0]; assert(typeof space === "number"); diff --git a/ts-sdk/whirlpool/tests/utils/program.ts b/ts-sdk/whirlpool/tests/utils/program.ts new file mode 100644 index 000000000..72e160490 --- /dev/null +++ b/ts-sdk/whirlpool/tests/utils/program.ts @@ -0,0 +1,143 @@ +import { + getFeeTierAddress, + getInitializeConfigInstruction, + getInitializeFeeTierInstruction, + getInitializePoolInstruction, + 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 { + SPLASH_POOL_TICK_SPACING, + WHIRLPOOLS_CONFIG_ADDRESS, +} from "../../src/config"; +import { tickIndexToSqrtPrice } from "@orca-so/whirlpools-core"; + +export async function setupConfigAndFeeTiers(): Promise
{ + const keypair = await generateKeyPairSigner(); + const instructions: IInstruction[] = []; + + instructions.push( + getInitializeConfigInstruction({ + config: keypair, + funder: signer, + feeAuthority: signer.address, + collectProtocolFeesAuthority: signer.address, + rewardEmissionsSuperAuthority: signer.address, + defaultProtocolFeeRate: 100, + }), + ); + + const defaultFeeTierPda = await getFeeTierAddress(keypair.address, 128); + instructions.push( + getInitializeFeeTierInstruction({ + config: keypair.address, + feeTier: defaultFeeTierPda[0], + funder: signer, + feeAuthority: signer, + tickSpacing: 128, + defaultFeeRate: 1000, + }), + ); + + const concentratedFeeTierPda = await getFeeTierAddress(keypair.address, 64); + instructions.push( + getInitializeFeeTierInstruction({ + config: keypair.address, + feeTier: concentratedFeeTierPda[0], + funder: signer, + feeAuthority: signer, + tickSpacing: 64, + defaultFeeRate: 300, + }), + ); + + const splashFeeTierPda = await getFeeTierAddress( + keypair.address, + SPLASH_POOL_TICK_SPACING, + ); + instructions.push( + getInitializeFeeTierInstruction({ + config: keypair.address, + feeTier: splashFeeTierPda[0], + funder: signer, + feeAuthority: signer, + tickSpacing: SPLASH_POOL_TICK_SPACING, + defaultFeeRate: 1000, + }), + ); + + await sendTransaction(instructions); + return keypair.address; +} + +export async function setupWhirlpool( + tokenA: Address, + tokenB: Address, + tickSpacing: number, + config: { initialSqrtPrice?: bigint } = {}, +): Promise
{ + const feeTierAddress = await getFeeTierAddress( + WHIRLPOOLS_CONFIG_ADDRESS, + tickSpacing, + ); + const whirlpoolAddress = await getWhirlpoolAddress( + WHIRLPOOLS_CONFIG_ADDRESS, + tokenA, + tokenB, + tickSpacing, + ); + const vaultA = await generateKeyPairSigner(); + const vaultB = await generateKeyPairSigner(); + + const sqrtPrice = config.initialSqrtPrice ?? tickIndexToSqrtPrice(0); + + const instructions: IInstruction[] = []; + + instructions.push( + getInitializePoolInstruction({ + whirlpool: whirlpoolAddress[0], + feeTier: feeTierAddress[0], + tokenMintA: tokenA, + tokenMintB: tokenB, + tickSpacing, + whirlpoolsConfig: WHIRLPOOLS_CONFIG_ADDRESS, + funder: signer, + tokenVaultA: vaultA, + tokenVaultB: vaultB, + whirlpoolBump: whirlpoolAddress[1], + initialSqrtPrice: sqrtPrice, + }), + ); + + await sendTransaction(instructions); + return whirlpoolAddress[0]; +} + +export async function setupPosition( + whirlpool: Address, + config: { tickLower?: number; tickUpper?: number; liquidity?: bigint } = {}, +): Promise
{ + // TODO: implement when solana-bankrun supports gpa + const _ = config; + return whirlpool; +} + +export async function setupTEPosition( + whirlpool: Address, + config: { tickLower?: number; tickUpper?: number; liquidity?: bigint } = {}, +): Promise
{ + // TODO: implement when solana-bankrun supports gpa + const _ = config; + return whirlpool; +} + +export async function setupPositionBundle( + whirlpool: Address, + config: { tickLower?: number; tickUpper?: number; liquidity?: bigint }[] = [], +): Promise
{ + // TODO: implement when solana-bankrun supports gpa + const _ = config; + return whirlpool; +} diff --git a/ts-sdk/whirlpool/tests/utils/token.ts b/ts-sdk/whirlpool/tests/utils/token.ts new file mode 100644 index 000000000..e479ac9d4 --- /dev/null +++ b/ts-sdk/whirlpool/tests/utils/token.ts @@ -0,0 +1,80 @@ +import { getCreateAccountInstruction } from "@solana-program/system"; +import { + getMintSize, + getInitializeMint2Instruction, + TOKEN_PROGRAM_ADDRESS, + getCreateAssociatedTokenIdempotentInstruction, + findAssociatedTokenPda, + getMintToInstruction, +} from "@solana-program/token"; +import type { Address, IInstruction } from "@solana/web3.js"; +import { generateKeyPairSigner } from "@solana/web3.js"; +import { signer, sendTransaction } from "./mockRpc"; + +export async function setupAta( + mint: Address, + config: { amount?: number | bigint } = {}, +): Promise
{ + const ata = await findAssociatedTokenPda({ + mint, + owner: signer.address, + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }); + + const instructions: IInstruction[] = []; + + instructions.push( + getCreateAssociatedTokenIdempotentInstruction({ + mint, + owner: signer.address, + ata: ata[0], + payer: signer, + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }), + ); + + if (config.amount) { + instructions.push( + getMintToInstruction({ + mint, + token: ata[0], + mintAuthority: signer, + amount: config.amount, + }), + ); + } + + await sendTransaction(instructions); + + return ata[0]; +} + +export async function setupMint( + config: { decimals?: number } = {}, +): Promise
{ + const keypair = await generateKeyPairSigner(); + const instructions: IInstruction[] = []; + + instructions.push( + getCreateAccountInstruction({ + payer: signer, + newAccount: keypair, + lamports: 1e8, + space: getMintSize(), + programAddress: TOKEN_PROGRAM_ADDRESS, + }), + ); + + instructions.push( + getInitializeMint2Instruction({ + mint: keypair.address, + mintAuthority: signer.address, + freezeAuthority: null, + decimals: config.decimals ?? 6, + }), + ); + + await sendTransaction(instructions); + + return keypair.address; +} diff --git a/ts-sdk/whirlpool/tests/utils/tokenExtensions.ts b/ts-sdk/whirlpool/tests/utils/tokenExtensions.ts new file mode 100644 index 000000000..26f57f8df --- /dev/null +++ b/ts-sdk/whirlpool/tests/utils/tokenExtensions.ts @@ -0,0 +1,94 @@ +import { + findAssociatedTokenPda, + TOKEN_2022_PROGRAM_ADDRESS, + getCreateAssociatedTokenIdempotentInstruction, + getMintToInstruction, + getMintSize, + getInitializeMint2Instruction, +} from "@solana-program/token-2022"; +import type { Address, IInstruction } from "@solana/web3.js"; +import { generateKeyPairSigner } from "@solana/web3.js"; +import { sendTransaction, signer } from "./mockRpc"; +import { getCreateAccountInstruction } from "@solana-program/system"; + +export async function setupAtaTE( + mint: Address, + config: { amount?: number | bigint } = {}, +): Promise
{ + const ata = await findAssociatedTokenPda({ + mint, + owner: signer.address, + tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, + }); + + const instructions: IInstruction[] = []; + + instructions.push( + getCreateAssociatedTokenIdempotentInstruction({ + mint, + owner: signer.address, + ata: ata[0], + payer: signer, + tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, + }), + ); + + if (config.amount) { + instructions.push( + getMintToInstruction({ + mint, + token: ata[0], + mintAuthority: signer, + amount: config.amount, + }), + ); + } + + await sendTransaction(instructions); + + return ata[0]; +} + +export async function setupMintTE( + config: { decimals?: number } = {}, +): Promise
{ + const keypair = await generateKeyPairSigner(); + const instructions: IInstruction[] = []; + + instructions.push( + getCreateAccountInstruction({ + payer: signer, + newAccount: keypair, + lamports: 1e8, + space: getMintSize(), + programAddress: TOKEN_2022_PROGRAM_ADDRESS, + }), + ); + + instructions.push( + getInitializeMint2Instruction({ + mint: keypair.address, + mintAuthority: signer.address, + freezeAuthority: null, + decimals: config.decimals ?? 6, + }), + ); + + await sendTransaction(instructions); + + return keypair.address; +} + +export async function setupMintTEFee( + config: { decimals?: number } = {}, +): Promise
{ + // TODO: Implement fee + return setupMintTE(config); +} + +export async function setupMintTEHook( + config: { decimals?: number } = {}, +): Promise
{ + // TODO: Implement hook + return setupMintTE(config); +}