-
Notifications
You must be signed in to change notification settings - Fork 150
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
Changes from 7 commits
c202a75
f777c27
a3eab20
502aae4
08186d7
47eba59
413c5b0
de84176
71a7d7b
51bc3a0
e8902bc
3b4d976
7e18813
9a32a10
d54d855
4fa6b40
5c1bfc0
ccffacd
55b81c6
c6a8823
7c359f9
3c72de4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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"; | ||
|
||
|
@@ -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]); | ||
|
@@ -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)) | ||
: getTokenSize() | ||
); | ||
stateSpaces.push( | ||
tokenProgramB === TOKEN_2022_PROGRAM_ADDRESS | ||
? getTokenSize(getAccountExtensions(mintB.data)) | ||
: getTokenSize() | ||
); | ||
stateSpaces.push(getWhirlpoolSize()); | ||
|
||
const fullRange = getFullRangeTickIndexes(tickSpacing); | ||
const lowerTickIndex = getTickArrayStartTickIndex( | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)") |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would structure the test like this:
That way you don't need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And then create more tests with similar structure for:
|
||
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); | ||
}) | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
|
||
export async function setupConfigAndFeeTiers(): Promise<Address> { | ||
const keypair = await generateKeyPairSigner(); | ||
|
@@ -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, | ||
|
@@ -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, | ||
}), | ||
); | ||
|
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😄