123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- import { randomBytes } from 'node:crypto';
- import { describe, it } from 'node:test';
- 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 { BankrunProvider } from 'anchor-bankrun';
- import { assert } from 'chai';
- import { startAnchor } from 'solana-bankrun';
- 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 IDL = require('../target/idl/escrow.json');
- const PROGRAM_ID = new PublicKey(IDL.address);
- const getRandomBigNumber = (size = 8) => {
- return new BN(randomBytes(size));
- };
- describe('Escrow Bankrun example', async () => {
- const context = await startAnchor('', [{ name: 'escrow', programId: PROGRAM_ID }], []);
- const provider = new BankrunProvider(context);
- const connection = provider.connection;
- // const payer = provider.wallet as anchor.Wallet;
- const program = new anchor.Program<Escrow>(IDL, provider);
- // 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();
- });
- });
|