123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- import { Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction, TransactionInstruction } from '@solana/web3.js';
- import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token';
- import { assert } from 'chai';
- import { describe, it } from 'mocha';
- import { BanksClient, ProgramTestContext, start } from 'solana-bankrun';
- import {
- createAMint,
- deserializeAmmAccount,
- deserializePoolAccount,
- getCreateAmmInstructionData,
- getCreatePoolInstructionData,
- getDepositLiquidityInstructionData,
- getSwapInstructionData,
- mintTo,
- } from './utils';
- const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35');
- describe('Token Swap Program: Create and swap', () => {
- let context: ProgramTestContext;
- let client: BanksClient;
- let payer: Keypair;
- const mint_a = Keypair.generate();
- const mint_b = Keypair.generate();
- const admin = Keypair.generate();
- const trader = Keypair.generate();
- const fee = 500; // 5%
- const id = Keypair.generate();
- const [ammPda] = PublicKey.findProgramAddressSync([id.publicKey.toBuffer()], PROGRAM_ID);
- const [poolPda] = PublicKey.findProgramAddressSync([ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer()], PROGRAM_ID);
- const [poolAuthorityPda] = PublicKey.findProgramAddressSync(
- [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('authority')],
- PROGRAM_ID,
- );
- const [mintLiquidityPda] = PublicKey.findProgramAddressSync(
- [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('liquidity')],
- PROGRAM_ID,
- );
- const poolAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, poolAuthorityPda, true);
- const poolAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, poolAuthorityPda, true);
- let depositorAccountLp: PublicKey;
- let depositorAccountA: PublicKey;
- let depositorAccountB: PublicKey;
- let traderAccountA: PublicKey;
- let traderAccountB: PublicKey;
- const MINIMUM_LIQUIDITY = 100;
- const amountA = BigInt(4 * 10 ** 9);
- const amountB = BigInt(1 * 10 ** 9);
- const amountLp = BigInt(Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY);
- before(async () => {
- context = await start([{ name: 'token_swap_program', programId: PROGRAM_ID }], []);
- client = context.banksClient;
- payer = context.payer;
- console.log(mint_a.publicKey.toBase58(), payer.publicKey.toBase58());
- await createAMint(context, payer, mint_a);
- await createAMint(context, payer, mint_b);
- depositorAccountLp = getAssociatedTokenAddressSync(mintLiquidityPda, payer.publicKey, false);
- depositorAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, payer.publicKey, false);
- depositorAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, payer.publicKey, false);
- traderAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, trader.publicKey, false);
- traderAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, trader.publicKey, false);
- await mintTo(context, payer, payer.publicKey, mint_a.publicKey);
- await mintTo(context, payer, payer.publicKey, mint_b.publicKey);
- await mintTo(context, payer, trader.publicKey, mint_a.publicKey);
- await mintTo(context, payer, trader.publicKey, mint_b.publicKey);
- });
- it('Should create a new amm successfully', async () => {
- const tx = new Transaction();
- tx.add(
- new TransactionInstruction({
- programId: PROGRAM_ID,
- keys: [
- { pubkey: payer.publicKey, isSigner: true, isWritable: true },
- { pubkey: admin.publicKey, isSigner: false, isWritable: false },
- { pubkey: ammPda, isSigner: false, isWritable: true },
- {
- pubkey: SystemProgram.programId,
- isSigner: false,
- isWritable: false,
- },
- ],
- data: getCreateAmmInstructionData(id.publicKey, fee),
- }),
- );
- tx.recentBlockhash = context.lastBlockhash;
- tx.sign(payer);
- // process the transaction
- await client.processTransaction(tx);
- const ammAccount = await client.getAccount(ammPda);
- assert.isNotNull(ammAccount);
- assert.equal(ammAccount?.owner.toBase58(), PROGRAM_ID.toBase58());
- const ammAccountData = deserializeAmmAccount(ammAccount.data);
- assert.equal(ammAccountData.id.toBase58(), id.publicKey.toBase58());
- assert.equal(ammAccountData.admin.toBase58(), admin.publicKey.toBase58());
- assert.equal(ammAccountData.fee, fee);
- });
- it('Should create a new pool successfully', async () => {
- const tx = new Transaction();
- tx.add(
- new TransactionInstruction({
- programId: PROGRAM_ID,
- keys: [
- { pubkey: payer.publicKey, isSigner: true, isWritable: true },
- { pubkey: ammPda, isSigner: false, isWritable: false },
- { pubkey: poolPda, isSigner: false, isWritable: true },
- { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
- { pubkey: mintLiquidityPda, isSigner: false, isWritable: true },
- { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
- { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
- { pubkey: poolAccountA, isSigner: false, isWritable: true },
- { pubkey: poolAccountB, isSigner: false, isWritable: true },
- {
- pubkey: TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: SystemProgram.programId,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: SYSVAR_RENT_PUBKEY,
- isSigner: false,
- isWritable: false,
- },
- ],
- data: getCreatePoolInstructionData(),
- }),
- );
- tx.recentBlockhash = context.lastBlockhash;
- tx.sign(payer);
- // process the transaction
- await client.processTransaction(tx);
- const poolPdaAccount = await client.getAccount(poolPda);
- assert.isNotNull(poolPdaAccount);
- assert.equal(poolPdaAccount?.owner.toBase58(), PROGRAM_ID.toBase58());
- const data = deserializePoolAccount(poolPdaAccount.data);
- assert.equal(data.amm.toBase58(), ammPda.toBase58());
- assert.equal(data.mintA.toBase58(), mint_a.publicKey.toBase58());
- assert.equal(data.mintB.toBase58(), mint_b.publicKey.toBase58());
- });
- it('Should deposit liquidity successfully', async () => {
- const tx = new Transaction();
- tx.add(
- new TransactionInstruction({
- programId: PROGRAM_ID,
- keys: [
- { pubkey: payer.publicKey, isSigner: true, isWritable: true },
- { pubkey: payer.publicKey, isSigner: true, isWritable: true },
- { pubkey: poolPda, isSigner: false, isWritable: true },
- { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
- { pubkey: mintLiquidityPda, isSigner: false, isWritable: true },
- { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
- { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
- { pubkey: poolAccountA, isSigner: false, isWritable: true },
- { pubkey: poolAccountB, isSigner: false, isWritable: true },
- { pubkey: depositorAccountLp, isSigner: false, isWritable: true },
- { pubkey: depositorAccountA, isSigner: false, isWritable: true },
- { pubkey: depositorAccountB, isSigner: false, isWritable: true },
- {
- pubkey: TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: SystemProgram.programId,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- ],
- data: getDepositLiquidityInstructionData(amountA, amountB),
- }),
- );
- tx.recentBlockhash = context.lastBlockhash;
- tx.sign(payer);
- // process the transaction
- await client.processTransaction(tx);
- const rawDepositorAccountLp = await client.getAccount(depositorAccountLp);
- assert.isNotNull(rawDepositorAccountLp);
- const decodedDepositorAccountLp = AccountLayout.decode(rawDepositorAccountLp?.data);
- assert.equal(decodedDepositorAccountLp.amount, amountLp);
- const expectedLpAmount = Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY;
- const rawLiquidityMint = await client.getAccount(mintLiquidityPda);
- assert.isNotNull(rawLiquidityMint);
- const decodedLiquidityMint = MintLayout.decode(rawLiquidityMint.data);
- assert.equal(decodedLiquidityMint.supply, BigInt(expectedLpAmount));
- const rawPoolAccountA = await client.getAccount(poolAccountA);
- assert.isNotNull(rawPoolAccountA);
- const decodedPoolAccountA = AccountLayout.decode(rawPoolAccountA?.data);
- assert.equal(decodedPoolAccountA.amount, amountA);
- const rawPoolAccountB = await client.getAccount(poolAccountB);
- assert.isNotNull(rawPoolAccountB);
- const decodedPoolAccountB = AccountLayout.decode(rawPoolAccountB?.data);
- assert.equal(decodedPoolAccountB.amount, amountB);
- });
- it('Should swap successfully', async () => {
- const swapAmount = BigInt(10 ** 9);
- const minimunAmountOut = BigInt(100);
- const tx = new Transaction();
- tx.add(
- new TransactionInstruction({
- programId: PROGRAM_ID,
- keys: [
- { pubkey: payer.publicKey, isSigner: true, isWritable: true },
- { pubkey: trader.publicKey, isSigner: true, isWritable: true },
- { pubkey: ammPda, isSigner: false, isWritable: true },
- { pubkey: poolPda, isSigner: false, isWritable: true },
- { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
- { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
- { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
- { pubkey: poolAccountA, isSigner: false, isWritable: true },
- { pubkey: poolAccountB, isSigner: false, isWritable: true },
- { pubkey: traderAccountA, isSigner: false, isWritable: true },
- { pubkey: traderAccountB, isSigner: false, isWritable: true },
- {
- pubkey: TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: SystemProgram.programId,
- isSigner: false,
- isWritable: false,
- },
- {
- pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
- isSigner: false,
- isWritable: false,
- },
- ],
- data: getSwapInstructionData(true, swapAmount, minimunAmountOut),
- }),
- );
- tx.recentBlockhash = context.lastBlockhash;
- tx.sign(payer, trader);
- // process the transaction
- await client.processTransaction(tx);
- const rawTraderAccountA = await client.getAccount(traderAccountA);
- assert.isNotNull(rawTraderAccountA);
- const decodedTraderAccountA = AccountLayout.decode(rawTraderAccountA?.data);
- assert.equal(decodedTraderAccountA.amount, BigInt(999000000000));
- const rawTraderAccountB = await client.getAccount(traderAccountB);
- assert.isNotNull(rawTraderAccountB);
- const decodedTraderAccountB = AccountLayout.decode(rawTraderAccountB?.data);
- assert.equal(decodedTraderAccountB.amount, BigInt(1000191919191));
- });
- });
|