escrow.ts 5.6 KB

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