escrow.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. const users = usersMintsAndTokenAccounts.users;
  62. alice = users[0];
  63. bob = users[1];
  64. const mints = usersMintsAndTokenAccounts.mints;
  65. tokenMintA = mints[0];
  66. tokenMintB = mints[1];
  67. const tokenAccounts = usersMintsAndTokenAccounts.tokenAccounts;
  68. const aliceTokenAccountA = tokenAccounts[0][0];
  69. const aliceTokenAccountB = tokenAccounts[0][1];
  70. const bobTokenAccountA = tokenAccounts[1][0];
  71. const bobTokenAccountB = tokenAccounts[1][1];
  72. // Save the accounts for later use
  73. accounts.maker = alice.publicKey;
  74. accounts.taker = bob.publicKey;
  75. accounts.tokenMintA = tokenMintA.publicKey;
  76. accounts.makerTokenAccountA = aliceTokenAccountA;
  77. accounts.takerTokenAccountA = bobTokenAccountA;
  78. accounts.tokenMintB = tokenMintB.publicKey;
  79. accounts.makerTokenAccountB = aliceTokenAccountB;
  80. accounts.takerTokenAccountB = bobTokenAccountB;
  81. });
  82. it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
  83. // Pick a random ID for the offer we'll make
  84. const offerId = getRandomBigNumber();
  85. // Then determine the account addresses we'll use for the offer and the vault
  86. const offer = PublicKey.findProgramAddressSync(
  87. [Buffer.from('offer'), accounts.maker.toBuffer(), offerId.toArrayLike(Buffer, 'le', 8)],
  88. program.programId,
  89. )[0];
  90. const vault = getAssociatedTokenAddressSync(accounts.tokenMintA, offer, true, TOKEN_PROGRAM);
  91. accounts.offer = offer;
  92. accounts.vault = vault;
  93. const transactionSignature = await program.methods
  94. .makeOffer(offerId, tokenAOfferedAmount, tokenBWantedAmount)
  95. .accounts({ ...accounts })
  96. .signers([alice])
  97. .rpc();
  98. await confirmTransaction(connection, transactionSignature);
  99. // Check our vault contains the tokens offered
  100. const vaultBalanceResponse = await connection.getTokenAccountBalance(vault);
  101. const vaultBalance = new BN(vaultBalanceResponse.value.amount);
  102. assert(vaultBalance.eq(tokenAOfferedAmount));
  103. // Check our Offer account contains the correct data
  104. const offerAccount = await program.account.offer.fetch(offer);
  105. assert(offerAccount.maker.equals(alice.publicKey));
  106. assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
  107. assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
  108. assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
  109. }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
  110. it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
  111. const transactionSignature = await program.methods
  112. .takeOffer()
  113. .accounts({ ...accounts })
  114. .signers([bob])
  115. .rpc();
  116. await confirmTransaction(connection, transactionSignature);
  117. // Check the offered tokens are now in Bob's account
  118. // (note: there is no before balance as Bob didn't have any offered tokens before the transaction)
  119. const bobTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.takerTokenAccountA);
  120. const bobTokenAccountBalanceAfter = new BN(bobTokenAccountBalanceAfterResponse.value.amount);
  121. assert(bobTokenAccountBalanceAfter.eq(tokenAOfferedAmount));
  122. // Check the wanted tokens are now in Alice's account
  123. // (note: there is no before balance as Alice didn't have any wanted tokens before the transaction)
  124. const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
  125. const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
  126. assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
  127. }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
  128. });