123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- import { randomBytes } from 'node:crypto';
- import * as anchor from '@coral-xyz/anchor';
- import { BN, type Program } from '@coral-xyz/anchor';
- import { TOKEN_2022_PROGRAM_ID, type TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token';
- import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
- import { assert } from 'chai';
- import type { Escrow } from '../target/types/escrow';
- import { confirmTransaction, createAccountsMintsAndTokenAccounts, makeKeypairs } from '@solana-developers/helpers';
- // Work on both Token Program and new Token Extensions Program
- const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID = TOKEN_2022_PROGRAM_ID;
- const SECONDS = 1000;
- // Tests must complete within half this time otherwise
- // they are marked as slow. Since Anchor involves a little
- // network IO, these tests usually take about 15 seconds.
- const ANCHOR_SLOW_TEST_THRESHOLD = 40 * SECONDS;
- const getRandomBigNumber = (size = 8) => {
- return new BN(randomBytes(size));
- };
- describe('escrow', async () => {
- // Use the cluster and the keypair from Anchor.toml
- const provider = anchor.AnchorProvider.env();
- anchor.setProvider(provider);
- // See https://github.com/coral-xyz/anchor/issues/3122
- const user = (provider.wallet as anchor.Wallet).payer;
- const payer = user;
- const connection = provider.connection;
- const program = anchor.workspace.Escrow as Program<Escrow>;
- // We're going to reuse these accounts across multiple tests
- const accounts: Record<string, PublicKey> = {
- tokenProgram: TOKEN_PROGRAM,
- };
- let alice: anchor.web3.Keypair;
- let bob: anchor.web3.Keypair;
- let tokenMintA: anchor.web3.Keypair;
- let tokenMintB: anchor.web3.Keypair;
- [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
- const tokenAOfferedAmount = new BN(1_000_000);
- const tokenBWantedAmount = new BN(1_000_000);
- before('Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users', async () => {
- const usersMintsAndTokenAccounts = await createAccountsMintsAndTokenAccounts(
- [
- // Alice's token balances
- [
- // 1_000_000_000 of token A
- 1_000_000_000,
- // 0 of token B
- 0,
- ],
- // Bob's token balances
- [
- // 0 of token A
- 0,
- // 1_000_000_000 of token B
- 1_000_000_000,
- ],
- ],
- 1 * LAMPORTS_PER_SOL,
- connection,
- payer,
- );
- // Alice will be the maker (creator) of the offer
- // Bob will be the taker (acceptor) of the offer
- const users = usersMintsAndTokenAccounts.users;
- alice = users[0];
- bob = users[1];
- // tokenMintA represents the token Alice is offering
- // tokenMintB represents the token Alice wants in return
- const mints = usersMintsAndTokenAccounts.mints;
- tokenMintA = mints[0];
- tokenMintB = mints[1];
- const tokenAccounts = usersMintsAndTokenAccounts.tokenAccounts;
- // aliceTokenAccountA is Alice's account for tokenA (the token she's offering)
- // aliceTokenAccountB is Alice's account for tokenB (the token she wants)
- const aliceTokenAccountA = tokenAccounts[0][0];
- const aliceTokenAccountB = tokenAccounts[0][1];
- // bobTokenAccountA is Bob's account for tokenA (the token Alice is offering)
- // bobTokenAccountB is Bob's account for tokenB (the token Alice wants)
- const bobTokenAccountA = tokenAccounts[1][0];
- const bobTokenAccountB = tokenAccounts[1][1];
- // Save the accounts for later use
- accounts.maker = alice.publicKey;
- accounts.taker = bob.publicKey;
- accounts.tokenMintA = tokenMintA.publicKey;
- accounts.makerTokenAccountA = aliceTokenAccountA;
- accounts.takerTokenAccountA = bobTokenAccountA;
- accounts.tokenMintB = tokenMintB.publicKey;
- accounts.makerTokenAccountB = aliceTokenAccountB;
- accounts.takerTokenAccountB = bobTokenAccountB;
- });
- it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
- // Pick a random ID for the offer we'll make
- const offerId = getRandomBigNumber();
- // Then determine the account addresses we'll use for the offer and the vault
- const offer = PublicKey.findProgramAddressSync(
- [Buffer.from('offer'), accounts.maker.toBuffer(), offerId.toArrayLike(Buffer, 'le', 8)],
- program.programId,
- )[0];
- const vault = getAssociatedTokenAddressSync(accounts.tokenMintA, offer, true, TOKEN_PROGRAM);
- accounts.offer = offer;
- accounts.vault = vault;
- const transactionSignature = await program.methods
- .makeOffer(offerId, tokenAOfferedAmount, tokenBWantedAmount)
- .accounts({ ...accounts })
- .signers([alice])
- .rpc();
- await confirmTransaction(connection, transactionSignature);
- // Check our vault contains the tokens offered
- const vaultBalanceResponse = await connection.getTokenAccountBalance(vault);
- const vaultBalance = new BN(vaultBalanceResponse.value.amount);
- assert(vaultBalance.eq(tokenAOfferedAmount));
- // Check our Offer account contains the correct data
- const offerAccount = await program.account.offer.fetch(offer);
- assert(offerAccount.maker.equals(alice.publicKey));
- assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
- assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
- assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
- }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
- it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
- const transactionSignature = await program.methods
- .takeOffer()
- .accounts({ ...accounts })
- .signers([bob])
- .rpc();
- await confirmTransaction(connection, transactionSignature);
- // Check the offered tokens are now in Bob's account
- // (note: there is no before balance as Bob didn't have any offered tokens before the transaction)
- const bobTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.takerTokenAccountA);
- const bobTokenAccountBalanceAfter = new BN(bobTokenAccountBalanceAfterResponse.value.amount);
- assert(bobTokenAccountBalanceAfter.eq(tokenAOfferedAmount));
- // Check the wanted tokens are now in Alice's account
- // (note: there is no before balance as Alice didn't have any wanted tokens before the transaction)
- const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
- const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
- assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
- }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
- });
|