escrow.ts 6.1 KB

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