-
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
Conversation
ts-sdk/whirlpool/src/createPool.ts
Outdated
.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 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)
ts-sdk/whirlpool/src/createPool.ts
Outdated
stateSpace += getWhirlpoolSize(); | ||
stateSpaces.push( | ||
tokenProgramA === TOKEN_2022_PROGRAM_ADDRESS | ||
? getTokenSize(getAccountExtensions(mintA.data)) |
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 😄
setDefaultFunder(signer); | ||
}); | ||
|
||
it("Should throw an error if token mints are not ordered", async () => { |
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.
nit: they are ordered, just not ordered correctly
await assert.rejects( | ||
createConcentratedLiquidityPoolInstructions(rpc, mintB, mintA, 64, 1), | ||
{ | ||
name: 'AssertionError', |
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.
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)")
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 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
await createSplashPoolInstructions(rpc, mintA, mintB, price); | ||
await sendTransaction(instructions); | ||
|
||
const pool = await fetchWhirlpool(rpc, poolAddress); |
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.
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
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 create more tests with similar structure for:
- Splash pool with TE token
- CL pool
- CL pool with TE token
- etc.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
?
ts-sdk/whirlpool/src/createPool.ts
Outdated
.send(); | ||
const nonRefundableRents: Lamports[] = await Promise.all( | ||
stateSpaces.map(async (space) => { | ||
const rentExemption = await calculateMinimumBalance(rpc, space); |
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.
This is still doing the same thing where it fetches the rent 5 (or 6 times).
The way I would do it. Change the function to:
export function calculateMinimumBalance(
rent: Rent,
dataSize: number,
): Lamports {
Fetcht he rent sysvar at the beginning of this function and then instead of keeping track of stateSpace: Array<bigint>
you can just straight up keep track of estInitializationCost: Lamports
.
And then instead of doing stateSpace.push(...)
you can just do estInitializationCost += calculateMinimumBalance(rent, ...)
ts-sdk/whirlpool/src/sysvar.ts
Outdated
* @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( |
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 you write a test for this?
const price = 10; | ||
const sqrtPrice = priceToSqrtPrice(price, 6, 6); | ||
|
||
let signerAccount = await rpc.getAccountInfo(signer.address).send(); |
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.
Think nicer if we implement getBalance on the mockRpc so we don't have to duplicate this every time.
assertAccountExists(pool); | ||
|
||
signerAccount = await rpc.getAccountInfo(signer.address).send(); | ||
const balanceAfter = signerAccount.value?.lamports ?? lamports(0n); |
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.
nit: I would group all the fetch together before starting on any assertions. poolBefore together with balanceBefore, poolAfter together with balanceAfter.
const balanceChange = balanceBefore - balanceAfter; | ||
const txFee = 15000n; // 3 signing accounts * 5000 lamports | ||
const minRentExempt = balanceChange - txFee; | ||
assert.strictEqual(estInitializationCost, minRentExempt); |
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.
Let me see if I can make the tx fee go to 0 in bankrun so that this assertion can just become:
assert.strictEqual(estInitializationCost, balanceBefore - balanceAfter);
}); | ||
|
||
it("Should create splash pool with 1 TE token", async () => { | ||
const mint1 = await setupMint(); |
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.
I would set up these mints in the beforeAll as well since that will get faster execution (init the TE mint once opposed to doing it for each TE test)
}); | ||
|
||
it("Should create splash pool with 2 TE tokens", async () => { | ||
const mint1 = await setupMintTEFee(); |
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.
For the tests I would do (instead of trying to combine Transfer fee and 1/2 TE token tests):
- 1 TE token (without extensions)
- 2 TE tokens (without extensions)
- 1 TE token (with transfer fee)
ts-sdk/whirlpool/src/sysvar.ts
Outdated
@@ -0,0 +1,28 @@ | |||
import { fetchSysvarRent } from "@solana/sysvars"; |
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.
nit: This should be re-exported by @solana/web3.js so you can use that as import
ts-sdk/whirlpool/src/sysvar.ts
Outdated
*/ | ||
export async function calculateMinimumBalance( | ||
rpc: Rpc<GetAccountInfoApi>, | ||
export function calculateMinimumBalance( |
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.
nit: maybe we can be a bit more descriptive with this naming. getMinimumBalanceForRentExemption
is what the rpc method that does the same is called
mintTE1 = await setupMintTE(); | ||
mintTE2 = await setupMintTE(); | ||
mintTEFee1 = await setupMintTEFee(); | ||
mintTEFee2 = await setupMintTEFee(); |
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.
Do you really need to have this second TEFee mint?
let rent: SysvarRent; | ||
|
||
beforeAll(async () => { | ||
rent = { |
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.
This doesn't necessarily have to be in the beforeAll (since it is not asynchronous). You can just init the rent directly as a const
}); | ||
|
||
it("Should calculate the correct minimum balance for a token account", async () => { | ||
const mint = await setupMint(); |
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.
You can make this test synchronous since you don't need to store/fetch anything in bankrun
@@ -103,7 +103,19 @@ const decoders: Record<string, VariableSizeDecoder<string>> = { | |||
base64: getBase64Decoder(), | |||
}; | |||
|
|||
async function getAccountData<T>(address: unknown, opts: unknown): Promise<T> { | |||
interface AccountData { |
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.
Not 100% sure if the AccountData is always exactly in this format. IIRC data depends on the encoding specified and is not always [string, string]. lamports/rentEpoch might be numbers I think (hence why I put generic T).
Instead of adding this interface you could also just do an assertion in getBalance
to check if lamports
exists in the object and then return that:
const accountDataForBalance = await getAccountData(
addressForBalance,
config.payload.params[1],
);
assert("lamports" in accountDataForBalance);
return getResponseWithContext<T>(accountDataForBalance.lamports);
await createSplashPoolInstructions(rpc, mintA, mintB, price); | ||
|
||
const balanceBefore = await rpc.getBalance(signer.address).send(); | ||
const maybePool = await fetchMaybeWhirlpool(rpc, poolAddress); |
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.
nit: I would name them poolBefore and poolAfter
ts-sdk/whirlpool/src/token.ts
Outdated
@@ -33,6 +32,8 @@ import { | |||
getCreateAccountWithSeedInstruction, | |||
getTransferSolInstruction, | |||
} from "@solana-program/system"; | |||
import { getTokenSize } from "@solana-program/token"; | |||
import { getTokenSize as getToken22Size } from "@solana-program/token-2022"; |
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.
nit: maybe getTokenWithExtensionsSize since if it is a t22 token with no extensions we still the other one
const txFee = 15000n; // 3 signing accounts * 5000 lamports | ||
const minRentExempt = balanceChange - txFee; | ||
|
||
assertAccountExists(pool); |
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.
Interesting! Never knew this existed
assert.strictEqual(SPLASH_POOL_TICK_SPACING, pool.data.tickSpacing); | ||
}); | ||
|
||
it("Should create splash pool with 1 TE tokens (with extension)", async () => { |
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.
nit: maybe instead of with extension
be explicit about that it is the TE extension
tokenSize, | ||
); | ||
|
||
const expectedMinimumBalance = lamports((165n + OVERHEAD) * 10n); |
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.
I wouldn't do the calculation here and just assert agains the actual value. OVERHEAD is something internal of the function and shouldn't be here. From a test you need to assume that you know nothing about the internal workings of the function and only test inputs and outputs
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.
So just 1445n
ts-sdk/whirlpool/tests/token.test.ts
Outdated
assert.strictEqual(tokenSize, 165); | ||
}); | ||
|
||
it("Should get the correct token size for TOKEN_2022_PROGRAM mint with", async () => { |
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.
nit: mint with ...
Title
Add unit tests for createPool.ts
Details
estInitializationCosts
in thecreateConcentratedLiquidityPool
function.