escrow.ts 6.1 KB

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