escrow.test.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { randomBytes } from 'node:crypto';
  2. import * as anchor from '@coral-xyz/anchor';
  3. import { BN, type Program } from '@coral-xyz/anchor';
  4. import { TOKEN_2022_PROGRAM_ID, type TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token';
  5. import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
  6. import { assert } from 'chai';
  7. import type { Escrow } from '../target/types/escrow';
  8. import { confirmTransaction, createAccountsMintsAndTokenAccounts, makeKeypairs } from '@solana-developers/helpers';
  9. // Work on both Token Program and new Token Extensions Program
  10. const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID = TOKEN_2022_PROGRAM_ID;
  11. const SECONDS = 1000;
  12. // Tests must complete within half this time otherwise
  13. // they are marked as slow. Since Anchor involves a little
  14. // network IO, these tests usually take about 15 seconds.
  15. const ANCHOR_SLOW_TEST_THRESHOLD = 40 * SECONDS;
  16. const getRandomBigNumber = (size = 8) => {
  17. return new BN(randomBytes(size));
  18. };
  19. describe('escrow', async () => {
  20. // Use the cluster and the keypair from Anchor.toml
  21. const provider = anchor.AnchorProvider.env();
  22. anchor.setProvider(provider);
  23. // See https://github.com/coral-xyz/anchor/issues/3122
  24. const user = (provider.wallet as anchor.Wallet).payer;
  25. const payer = user;
  26. const connection = provider.connection;
  27. const program = anchor.workspace.Escrow as Program<Escrow>;
  28. // We're going to reuse these accounts across multiple tests
  29. const accounts: Record<string, PublicKey> = {
  30. tokenProgram: TOKEN_PROGRAM,
  31. };
  32. let alice: anchor.web3.Keypair;
  33. let bob: anchor.web3.Keypair;
  34. let tokenMintA: anchor.web3.Keypair;
  35. let tokenMintB: anchor.web3.Keypair;
  36. [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
  37. const tokenAOfferedAmount = new BN(1_000_000);
  38. const tokenBWantedAmount = new BN(1_000_000);
  39. before('Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users', async () => {
  40. const usersMintsAndTokenAccounts = await createAccountsMintsAndTokenAccounts(
  41. [
  42. // Alice's token balances
  43. [
  44. // 1_000_000_000 of token A
  45. 1_000_000_000,
  46. // 0 of token B
  47. 0,
  48. ],
  49. // Bob's token balances
  50. [
  51. // 0 of token A
  52. 0,
  53. // 1_000_000_000 of token B
  54. 1_000_000_000,
  55. ],
  56. ],
  57. 1 * LAMPORTS_PER_SOL,
  58. connection,
  59. payer,
  60. );
  61. // Alice will be the maker (creator) of the offer
  62. // Bob will be the taker (acceptor) of the offer
  63. const users = usersMintsAndTokenAccounts.users;
  64. alice = users[0];
  65. bob = users[1];
  66. // tokenMintA represents the token Alice is offering
  67. // tokenMintB represents the token Alice wants in return
  68. const mints = usersMintsAndTokenAccounts.mints;
  69. tokenMintA = mints[0];
  70. tokenMintB = mints[1];
  71. const tokenAccounts = usersMintsAndTokenAccounts.tokenAccounts;
  72. // aliceTokenAccountA is Alice's account for tokenA (the token she's offering)
  73. // aliceTokenAccountB is Alice's account for tokenB (the token she wants)
  74. const aliceTokenAccountA = tokenAccounts[0][0];
  75. const aliceTokenAccountB = tokenAccounts[0][1];
  76. // bobTokenAccountA is Bob's account for tokenA (the token Alice is offering)
  77. // bobTokenAccountB is Bob's account for tokenB (the token Alice wants)
  78. const bobTokenAccountA = tokenAccounts[1][0];
  79. const bobTokenAccountB = tokenAccounts[1][1];
  80. // Save the accounts for later use
  81. accounts.maker = alice.publicKey;
  82. accounts.taker = bob.publicKey;
  83. accounts.tokenMintA = tokenMintA.publicKey;
  84. accounts.makerTokenAccountA = aliceTokenAccountA;
  85. accounts.takerTokenAccountA = bobTokenAccountA;
  86. accounts.tokenMintB = tokenMintB.publicKey;
  87. accounts.makerTokenAccountB = aliceTokenAccountB;
  88. accounts.takerTokenAccountB = bobTokenAccountB;
  89. });
  90. it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
  91. // Pick a random ID for the offer we'll make
  92. const offerId = getRandomBigNumber();
  93. // Then determine the account addresses we'll use for the offer and the vault
  94. const offer = PublicKey.findProgramAddressSync(
  95. [Buffer.from('offer'), accounts.maker.toBuffer(), offerId.toArrayLike(Buffer, 'le', 8)],
  96. program.programId,
  97. )[0];
  98. const vault = getAssociatedTokenAddressSync(accounts.tokenMintA, offer, true, TOKEN_PROGRAM);
  99. accounts.offer = offer;
  100. accounts.vault = vault;
  101. const transactionSignature = await program.methods
  102. .makeOffer(offerId, tokenAOfferedAmount, tokenBWantedAmount)
  103. .accounts({ ...accounts })
  104. .signers([alice])
  105. .rpc();
  106. await confirmTransaction(connection, transactionSignature);
  107. // Check our vault contains the tokens offered
  108. const vaultBalanceResponse = await connection.getTokenAccountBalance(vault);
  109. const vaultBalance = new BN(vaultBalanceResponse.value.amount);
  110. assert(vaultBalance.eq(tokenAOfferedAmount));
  111. // Check our Offer account contains the correct data
  112. const offerAccount = await program.account.offer.fetch(offer);
  113. assert(offerAccount.maker.equals(alice.publicKey));
  114. assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
  115. assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
  116. assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
  117. }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
  118. it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
  119. const transactionSignature = await program.methods
  120. .takeOffer()
  121. .accounts({ ...accounts })
  122. .signers([bob])
  123. .rpc();
  124. await confirmTransaction(connection, transactionSignature);
  125. // Check the offered tokens are now in Bob's account
  126. // (note: there is no before balance as Bob didn't have any offered tokens before the transaction)
  127. const bobTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.takerTokenAccountA);
  128. const bobTokenAccountBalanceAfter = new BN(bobTokenAccountBalanceAfterResponse.value.amount);
  129. assert(bobTokenAccountBalanceAfter.eq(tokenAOfferedAmount));
  130. // Check the wanted tokens are now in Alice's account
  131. // (note: there is no before balance as Alice didn't have any wanted tokens before the transaction)
  132. const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
  133. const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
  134. assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
  135. }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
  136. });