123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- import { randomBytes } from "node:crypto";
- import * as anchor from "@coral-xyz/anchor";
- import { BN, type Program } from "@coral-xyz/anchor";
- import {
- MINT_SIZE,
- TOKEN_2022_PROGRAM_ID,
- type TOKEN_PROGRAM_ID,
- createAssociatedTokenAccountIdempotentInstruction,
- createInitializeMint2Instruction,
- createMintToInstruction,
- getAssociatedTokenAddressSync,
- getMinimumBalanceForRentExemptMint,
- } from "@solana/spl-token";
- import {
- LAMPORTS_PER_SOL,
- PublicKey,
- SystemProgram,
- Transaction,
- type TransactionInstruction,
- } from "@solana/web3.js";
- import { assert } from "chai";
- import type { Escrow } from "../target/types/escrow";
- import { confirmTransaction, makeKeypairs } from "@solana-developers/helpers";
- const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID =
- TOKEN_2022_PROGRAM_ID;
- const getRandomBigNumber = (size = 8) => {
- return new BN(randomBytes(size));
- };
- describe("escrow", async () => {
- anchor.setProvider(anchor.AnchorProvider.env());
- const provider = anchor.getProvider();
- 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,
- };
- const [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
- before(
- "Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users",
- async () => {
- const [
- aliceTokenAccountA,
- aliceTokenAccountB,
- bobTokenAccountA,
- bobTokenAccountB,
- ] = [alice, bob].flatMap((keypair) =>
- [tokenMintA, tokenMintB].map((mint) =>
- getAssociatedTokenAddressSync(
- mint.publicKey,
- keypair.publicKey,
- false,
- TOKEN_PROGRAM
- )
- )
- );
- // Airdrops to users, and creates two tokens mints 'A' and 'B'"
- const minimumLamports = await getMinimumBalanceForRentExemptMint(
- connection
- );
- const sendSolInstructions: Array<TransactionInstruction> = [
- alice,
- bob,
- ].map((account) =>
- SystemProgram.transfer({
- fromPubkey: provider.publicKey,
- toPubkey: account.publicKey,
- lamports: 10 * LAMPORTS_PER_SOL,
- })
- );
- const createMintInstructions: Array<TransactionInstruction> = [
- tokenMintA,
- tokenMintB,
- ].map((mint) =>
- SystemProgram.createAccount({
- fromPubkey: provider.publicKey,
- newAccountPubkey: mint.publicKey,
- lamports: minimumLamports,
- space: MINT_SIZE,
- programId: TOKEN_PROGRAM,
- })
- );
- // Make tokenA and tokenB mints, mint tokens and create ATAs
- const mintTokensInstructions: Array<TransactionInstruction> = [
- {
- mint: tokenMintA.publicKey,
- authority: alice.publicKey,
- ata: aliceTokenAccountA,
- },
- {
- mint: tokenMintB.publicKey,
- authority: bob.publicKey,
- ata: bobTokenAccountB,
- },
- ].flatMap((mintDetails) => [
- createInitializeMint2Instruction(
- mintDetails.mint,
- 6,
- mintDetails.authority,
- null,
- TOKEN_PROGRAM
- ),
- createAssociatedTokenAccountIdempotentInstruction(
- provider.publicKey,
- mintDetails.ata,
- mintDetails.authority,
- mintDetails.mint,
- TOKEN_PROGRAM
- ),
- createMintToInstruction(
- mintDetails.mint,
- mintDetails.ata,
- mintDetails.authority,
- 1_000_000_000,
- [],
- TOKEN_PROGRAM
- ),
- ]);
- // Add all these instructions to our transaction
- const tx = new Transaction();
- tx.instructions = [
- ...sendSolInstructions,
- ...createMintInstructions,
- ...mintTokensInstructions,
- ];
- await provider.sendAndConfirm(tx, [tokenMintA, tokenMintB, alice, bob]);
- // 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;
- }
- );
- const tokenAOfferedAmount = new BN(1_000_000);
- const tokenBWantedAmount = new BN(1_000_000);
- // We'll call this function from multiple tests, so let's seperate it out
- const make = 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));
- };
- // We'll call this function from multiple tests, so let's seperate it out
- const take = 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));
- };
- it("Puts the tokens Alice offers into the vault when Alice makes an offer", async () => {
- await make();
- });
- it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
- await take();
- });
- });
|