bankrun.test.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { randomBytes } from 'node:crypto';
  2. import { describe, it } from 'node:test';
  3. import * as anchor from '@coral-xyz/anchor';
  4. import { BN, type Program } from '@coral-xyz/anchor';
  5. import {
  6. MINT_SIZE,
  7. TOKEN_2022_PROGRAM_ID,
  8. type TOKEN_PROGRAM_ID,
  9. createAssociatedTokenAccountIdempotentInstruction,
  10. createInitializeMint2Instruction,
  11. createMintToInstruction,
  12. getAssociatedTokenAddressSync,
  13. getMinimumBalanceForRentExemptMint,
  14. } from '@solana/spl-token';
  15. import { LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction } from '@solana/web3.js';
  16. import { BankrunProvider } from 'anchor-bankrun';
  17. import { assert } from 'chai';
  18. import { startAnchor } from 'solana-bankrun';
  19. import type { Escrow } from '../target/types/escrow';
  20. import { confirmTransaction, makeKeypairs } from '@solana-developers/helpers';
  21. const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID = TOKEN_2022_PROGRAM_ID;
  22. const IDL = require('../target/idl/escrow.json');
  23. const PROGRAM_ID = new PublicKey(IDL.address);
  24. const getRandomBigNumber = (size = 8) => {
  25. return new BN(randomBytes(size));
  26. };
  27. describe('Escrow Bankrun example', async () => {
  28. const context = await startAnchor('', [{ name: 'escrow', programId: PROGRAM_ID }], []);
  29. const provider = new BankrunProvider(context);
  30. const connection = provider.connection;
  31. // const payer = provider.wallet as anchor.Wallet;
  32. const program = new anchor.Program<Escrow>(IDL, provider);
  33. // We're going to reuse these accounts across multiple tests
  34. const accounts: Record<string, PublicKey> = {
  35. tokenProgram: TOKEN_PROGRAM,
  36. };
  37. const [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
  38. before('Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users', async () => {
  39. const [aliceTokenAccountA, aliceTokenAccountB, bobTokenAccountA, bobTokenAccountB] = [alice, bob].flatMap((keypair) =>
  40. [tokenMintA, tokenMintB].map((mint) => getAssociatedTokenAddressSync(mint.publicKey, keypair.publicKey, false, TOKEN_PROGRAM)),
  41. );
  42. // Airdrops to users, and creates two tokens mints 'A' and 'B'"
  43. const minimumLamports = await getMinimumBalanceForRentExemptMint(connection);
  44. const sendSolInstructions: Array<TransactionInstruction> = [alice, bob].map((account) =>
  45. SystemProgram.transfer({
  46. fromPubkey: provider.publicKey,
  47. toPubkey: account.publicKey,
  48. lamports: 10 * LAMPORTS_PER_SOL,
  49. }),
  50. );
  51. const createMintInstructions: Array<TransactionInstruction> = [tokenMintA, tokenMintB].map((mint) =>
  52. SystemProgram.createAccount({
  53. fromPubkey: provider.publicKey,
  54. newAccountPubkey: mint.publicKey,
  55. lamports: minimumLamports,
  56. space: MINT_SIZE,
  57. programId: TOKEN_PROGRAM,
  58. }),
  59. );
  60. // Make tokenA and tokenB mints, mint tokens and create ATAs
  61. const mintTokensInstructions: Array<TransactionInstruction> = [
  62. {
  63. mint: tokenMintA.publicKey,
  64. authority: alice.publicKey,
  65. ata: aliceTokenAccountA,
  66. },
  67. {
  68. mint: tokenMintB.publicKey,
  69. authority: bob.publicKey,
  70. ata: bobTokenAccountB,
  71. },
  72. ].flatMap((mintDetails) => [
  73. createInitializeMint2Instruction(mintDetails.mint, 6, mintDetails.authority, null, TOKEN_PROGRAM),
  74. createAssociatedTokenAccountIdempotentInstruction(provider.publicKey, mintDetails.ata, mintDetails.authority, mintDetails.mint, TOKEN_PROGRAM),
  75. createMintToInstruction(mintDetails.mint, mintDetails.ata, mintDetails.authority, 1_000_000_000, [], TOKEN_PROGRAM),
  76. ]);
  77. // Add all these instructions to our transaction
  78. const tx = new Transaction();
  79. tx.instructions = [...sendSolInstructions, ...createMintInstructions, ...mintTokensInstructions];
  80. await provider.sendAndConfirm(tx, [tokenMintA, tokenMintB, alice, bob]);
  81. // Save the accounts for later use
  82. accounts.maker = alice.publicKey;
  83. accounts.taker = bob.publicKey;
  84. accounts.tokenMintA = tokenMintA.publicKey;
  85. accounts.makerTokenAccountA = aliceTokenAccountA;
  86. accounts.takerTokenAccountA = bobTokenAccountA;
  87. accounts.tokenMintB = tokenMintB.publicKey;
  88. accounts.makerTokenAccountB = aliceTokenAccountB;
  89. accounts.takerTokenAccountB = bobTokenAccountB;
  90. });
  91. const tokenAOfferedAmount = new BN(1_000_000);
  92. const tokenBWantedAmount = new BN(1_000_000);
  93. // We'll call this function from multiple tests, so let's seperate it out
  94. const make = async () => {
  95. // Pick a random ID for the offer we'll make
  96. const offerId = getRandomBigNumber();
  97. // Then determine the account addresses we'll use for the offer and the vault
  98. const offer = PublicKey.findProgramAddressSync(
  99. [Buffer.from('offer'), accounts.maker.toBuffer(), offerId.toArrayLike(Buffer, 'le', 8)],
  100. program.programId,
  101. )[0];
  102. const vault = getAssociatedTokenAddressSync(accounts.tokenMintA, offer, true, TOKEN_PROGRAM);
  103. accounts.offer = offer;
  104. accounts.vault = vault;
  105. const transactionSignature = await program.methods
  106. .makeOffer(offerId, tokenAOfferedAmount, tokenBWantedAmount)
  107. .accounts({ ...accounts })
  108. .signers([alice])
  109. .rpc();
  110. await confirmTransaction(connection, transactionSignature);
  111. // Check our vault contains the tokens offered
  112. const vaultBalanceResponse = await connection.getTokenAccountBalance(vault);
  113. const vaultBalance = new BN(vaultBalanceResponse.value.amount);
  114. assert(vaultBalance.eq(tokenAOfferedAmount));
  115. // Check our Offer account contains the correct data
  116. const offerAccount = await program.account.offer.fetch(offer);
  117. assert(offerAccount.maker.equals(alice.publicKey));
  118. assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
  119. assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
  120. assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
  121. };
  122. // We'll call this function from multiple tests, so let's seperate it out
  123. const take = async () => {
  124. const transactionSignature = await program.methods
  125. .takeOffer()
  126. .accounts({ ...accounts })
  127. .signers([bob])
  128. .rpc();
  129. await confirmTransaction(connection, transactionSignature);
  130. // Check the offered tokens are now in Bob's account
  131. // (note: there is no before balance as Bob didn't have any offered tokens before the transaction)
  132. const bobTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.takerTokenAccountA);
  133. const bobTokenAccountBalanceAfter = new BN(bobTokenAccountBalanceAfterResponse.value.amount);
  134. assert(bobTokenAccountBalanceAfter.eq(tokenAOfferedAmount));
  135. // Check the wanted tokens are now in Alice's account
  136. // (note: there is no before balance as Alice didn't have any wanted tokens before the transaction)
  137. const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
  138. const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
  139. assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
  140. };
  141. it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
  142. await make();
  143. });
  144. it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
  145. await take();
  146. });
  147. });