create_pool_and_swap.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import { Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction, TransactionInstruction } from '@solana/web3.js';
  2. import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token';
  3. import { assert } from 'chai';
  4. import { describe, it } from 'mocha';
  5. import { BanksClient, ProgramTestContext, start } from 'solana-bankrun';
  6. import {
  7. createAMint,
  8. deserializeAmmAccount,
  9. deserializePoolAccount,
  10. getCreateAmmInstructionData,
  11. getCreatePoolInstructionData,
  12. getDepositLiquidityInstructionData,
  13. getSwapInstructionData,
  14. mintTo,
  15. } from './utils';
  16. const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35');
  17. describe('Token Swap Program: Create and swap', () => {
  18. let context: ProgramTestContext;
  19. let client: BanksClient;
  20. let payer: Keypair;
  21. const mint_a = Keypair.generate();
  22. const mint_b = Keypair.generate();
  23. const admin = Keypair.generate();
  24. const trader = Keypair.generate();
  25. const fee = 500; // 5%
  26. const id = Keypair.generate();
  27. const [ammPda] = PublicKey.findProgramAddressSync([id.publicKey.toBuffer()], PROGRAM_ID);
  28. const [poolPda] = PublicKey.findProgramAddressSync([ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer()], PROGRAM_ID);
  29. const [poolAuthorityPda] = PublicKey.findProgramAddressSync(
  30. [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('authority')],
  31. PROGRAM_ID,
  32. );
  33. const [mintLiquidityPda] = PublicKey.findProgramAddressSync(
  34. [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('liquidity')],
  35. PROGRAM_ID,
  36. );
  37. const poolAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, poolAuthorityPda, true);
  38. const poolAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, poolAuthorityPda, true);
  39. let depositorAccountLp: PublicKey;
  40. let depositorAccountA: PublicKey;
  41. let depositorAccountB: PublicKey;
  42. let traderAccountA: PublicKey;
  43. let traderAccountB: PublicKey;
  44. const MINIMUM_LIQUIDITY = 100;
  45. const amountA = BigInt(4 * 10 ** 9);
  46. const amountB = BigInt(1 * 10 ** 9);
  47. const amountLp = BigInt(Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY);
  48. before(async () => {
  49. context = await start([{ name: 'token_swap_program', programId: PROGRAM_ID }], []);
  50. client = context.banksClient;
  51. payer = context.payer;
  52. console.log(mint_a.publicKey.toBase58(), payer.publicKey.toBase58());
  53. await createAMint(context, payer, mint_a);
  54. await createAMint(context, payer, mint_b);
  55. depositorAccountLp = getAssociatedTokenAddressSync(mintLiquidityPda, payer.publicKey, false);
  56. depositorAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, payer.publicKey, false);
  57. depositorAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, payer.publicKey, false);
  58. traderAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, trader.publicKey, false);
  59. traderAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, trader.publicKey, false);
  60. await mintTo(context, payer, payer.publicKey, mint_a.publicKey);
  61. await mintTo(context, payer, payer.publicKey, mint_b.publicKey);
  62. await mintTo(context, payer, trader.publicKey, mint_a.publicKey);
  63. await mintTo(context, payer, trader.publicKey, mint_b.publicKey);
  64. });
  65. it('Should create a new amm successfully', async () => {
  66. const tx = new Transaction();
  67. tx.add(
  68. new TransactionInstruction({
  69. programId: PROGRAM_ID,
  70. keys: [
  71. { pubkey: payer.publicKey, isSigner: true, isWritable: true },
  72. { pubkey: admin.publicKey, isSigner: false, isWritable: false },
  73. { pubkey: ammPda, isSigner: false, isWritable: true },
  74. {
  75. pubkey: SystemProgram.programId,
  76. isSigner: false,
  77. isWritable: false,
  78. },
  79. ],
  80. data: getCreateAmmInstructionData(id.publicKey, fee),
  81. }),
  82. );
  83. tx.recentBlockhash = context.lastBlockhash;
  84. tx.sign(payer);
  85. // process the transaction
  86. await client.processTransaction(tx);
  87. const ammAccount = await client.getAccount(ammPda);
  88. assert.isNotNull(ammAccount);
  89. assert.equal(ammAccount?.owner.toBase58(), PROGRAM_ID.toBase58());
  90. const ammAccountData = deserializeAmmAccount(ammAccount.data);
  91. assert.equal(ammAccountData.id.toBase58(), id.publicKey.toBase58());
  92. assert.equal(ammAccountData.admin.toBase58(), admin.publicKey.toBase58());
  93. assert.equal(ammAccountData.fee, fee);
  94. });
  95. it('Should create a new pool successfully', async () => {
  96. const tx = new Transaction();
  97. tx.add(
  98. new TransactionInstruction({
  99. programId: PROGRAM_ID,
  100. keys: [
  101. { pubkey: payer.publicKey, isSigner: true, isWritable: true },
  102. { pubkey: ammPda, isSigner: false, isWritable: false },
  103. { pubkey: poolPda, isSigner: false, isWritable: true },
  104. { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
  105. { pubkey: mintLiquidityPda, isSigner: false, isWritable: true },
  106. { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
  107. { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
  108. { pubkey: poolAccountA, isSigner: false, isWritable: true },
  109. { pubkey: poolAccountB, isSigner: false, isWritable: true },
  110. {
  111. pubkey: TOKEN_PROGRAM_ID,
  112. isSigner: false,
  113. isWritable: false,
  114. },
  115. {
  116. pubkey: SystemProgram.programId,
  117. isSigner: false,
  118. isWritable: false,
  119. },
  120. {
  121. pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
  122. isSigner: false,
  123. isWritable: false,
  124. },
  125. {
  126. pubkey: SYSVAR_RENT_PUBKEY,
  127. isSigner: false,
  128. isWritable: false,
  129. },
  130. ],
  131. data: getCreatePoolInstructionData(),
  132. }),
  133. );
  134. tx.recentBlockhash = context.lastBlockhash;
  135. tx.sign(payer);
  136. // process the transaction
  137. await client.processTransaction(tx);
  138. const poolPdaAccount = await client.getAccount(poolPda);
  139. assert.isNotNull(poolPdaAccount);
  140. assert.equal(poolPdaAccount?.owner.toBase58(), PROGRAM_ID.toBase58());
  141. const data = deserializePoolAccount(poolPdaAccount.data);
  142. assert.equal(data.amm.toBase58(), ammPda.toBase58());
  143. assert.equal(data.mintA.toBase58(), mint_a.publicKey.toBase58());
  144. assert.equal(data.mintB.toBase58(), mint_b.publicKey.toBase58());
  145. });
  146. it('Should deposit liquidity successfully', async () => {
  147. const tx = new Transaction();
  148. tx.add(
  149. new TransactionInstruction({
  150. programId: PROGRAM_ID,
  151. keys: [
  152. { pubkey: payer.publicKey, isSigner: true, isWritable: true },
  153. { pubkey: payer.publicKey, isSigner: true, isWritable: true },
  154. { pubkey: poolPda, isSigner: false, isWritable: true },
  155. { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
  156. { pubkey: mintLiquidityPda, isSigner: false, isWritable: true },
  157. { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
  158. { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
  159. { pubkey: poolAccountA, isSigner: false, isWritable: true },
  160. { pubkey: poolAccountB, isSigner: false, isWritable: true },
  161. { pubkey: depositorAccountLp, isSigner: false, isWritable: true },
  162. { pubkey: depositorAccountA, isSigner: false, isWritable: true },
  163. { pubkey: depositorAccountB, isSigner: false, isWritable: true },
  164. {
  165. pubkey: TOKEN_PROGRAM_ID,
  166. isSigner: false,
  167. isWritable: false,
  168. },
  169. {
  170. pubkey: SystemProgram.programId,
  171. isSigner: false,
  172. isWritable: false,
  173. },
  174. {
  175. pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
  176. isSigner: false,
  177. isWritable: false,
  178. },
  179. ],
  180. data: getDepositLiquidityInstructionData(amountA, amountB),
  181. }),
  182. );
  183. tx.recentBlockhash = context.lastBlockhash;
  184. tx.sign(payer);
  185. // process the transaction
  186. await client.processTransaction(tx);
  187. const rawDepositorAccountLp = await client.getAccount(depositorAccountLp);
  188. assert.isNotNull(rawDepositorAccountLp);
  189. const decodedDepositorAccountLp = AccountLayout.decode(rawDepositorAccountLp?.data);
  190. assert.equal(decodedDepositorAccountLp.amount, amountLp);
  191. const expectedLpAmount = Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY;
  192. const rawLiquidityMint = await client.getAccount(mintLiquidityPda);
  193. assert.isNotNull(rawLiquidityMint);
  194. const decodedLiquidityMint = MintLayout.decode(rawLiquidityMint.data);
  195. assert.equal(decodedLiquidityMint.supply, BigInt(expectedLpAmount));
  196. const rawPoolAccountA = await client.getAccount(poolAccountA);
  197. assert.isNotNull(rawPoolAccountA);
  198. const decodedPoolAccountA = AccountLayout.decode(rawPoolAccountA?.data);
  199. assert.equal(decodedPoolAccountA.amount, amountA);
  200. const rawPoolAccountB = await client.getAccount(poolAccountB);
  201. assert.isNotNull(rawPoolAccountB);
  202. const decodedPoolAccountB = AccountLayout.decode(rawPoolAccountB?.data);
  203. assert.equal(decodedPoolAccountB.amount, amountB);
  204. });
  205. it('Should swap successfully', async () => {
  206. const swapAmount = BigInt(10 ** 9);
  207. const minimunAmountOut = BigInt(100);
  208. const tx = new Transaction();
  209. tx.add(
  210. new TransactionInstruction({
  211. programId: PROGRAM_ID,
  212. keys: [
  213. { pubkey: payer.publicKey, isSigner: true, isWritable: true },
  214. { pubkey: trader.publicKey, isSigner: true, isWritable: true },
  215. { pubkey: ammPda, isSigner: false, isWritable: true },
  216. { pubkey: poolPda, isSigner: false, isWritable: true },
  217. { pubkey: poolAuthorityPda, isSigner: false, isWritable: false },
  218. { pubkey: mint_a.publicKey, isSigner: false, isWritable: false },
  219. { pubkey: mint_b.publicKey, isSigner: false, isWritable: false },
  220. { pubkey: poolAccountA, isSigner: false, isWritable: true },
  221. { pubkey: poolAccountB, isSigner: false, isWritable: true },
  222. { pubkey: traderAccountA, isSigner: false, isWritable: true },
  223. { pubkey: traderAccountB, isSigner: false, isWritable: true },
  224. {
  225. pubkey: TOKEN_PROGRAM_ID,
  226. isSigner: false,
  227. isWritable: false,
  228. },
  229. {
  230. pubkey: SystemProgram.programId,
  231. isSigner: false,
  232. isWritable: false,
  233. },
  234. {
  235. pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
  236. isSigner: false,
  237. isWritable: false,
  238. },
  239. ],
  240. data: getSwapInstructionData(true, swapAmount, minimunAmountOut),
  241. }),
  242. );
  243. tx.recentBlockhash = context.lastBlockhash;
  244. tx.sign(payer, trader);
  245. // process the transaction
  246. await client.processTransaction(tx);
  247. const rawTraderAccountA = await client.getAccount(traderAccountA);
  248. assert.isNotNull(rawTraderAccountA);
  249. const decodedTraderAccountA = AccountLayout.decode(rawTraderAccountA?.data);
  250. assert.equal(decodedTraderAccountA.amount, BigInt(999000000000));
  251. const rawTraderAccountB = await client.getAccount(traderAccountB);
  252. assert.isNotNull(rawTraderAccountB);
  253. const decodedTraderAccountB = AccountLayout.decode(rawTraderAccountB?.data);
  254. assert.equal(decodedTraderAccountB.amount, BigInt(1000191919191));
  255. });
  256. });