|
@@ -1,32 +1,35 @@
|
|
|
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 { 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, makeKeypairs } from '@solana-developers/helpers';
|
|
|
+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 () => {
|
|
|
- anchor.setProvider(anchor.AnchorProvider.env());
|
|
|
+ // Use the cluster and the keypair from Anchor.toml
|
|
|
+ const provider = anchor.AnchorProvider.env();
|
|
|
+ anchor.setProvider(provider);
|
|
|
|
|
|
- const provider = anchor.getProvider();
|
|
|
+ // See https://github.com/coral-xyz/anchor/issues/3122
|
|
|
+ const user = (provider.wallet as anchor.Wallet).payer;
|
|
|
+ const payer = user;
|
|
|
|
|
|
const connection = provider.connection;
|
|
|
|
|
@@ -37,57 +40,62 @@ describe('escrow', async () => {
|
|
|
tokenProgram: TOKEN_PROGRAM,
|
|
|
};
|
|
|
|
|
|
- const [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
|
|
|
+ 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 [aliceTokenAccountA, aliceTokenAccountB, bobTokenAccountA, bobTokenAccountB] = [alice, bob].flatMap((keypair) =>
|
|
|
- [tokenMintA, tokenMintB].map((mint) => getAssociatedTokenAddressSync(mint.publicKey, keypair.publicKey, false, TOKEN_PROGRAM)),
|
|
|
+ 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,
|
|
|
);
|
|
|
|
|
|
- // Airdrops to users, and creates two tokens mints 'A' and 'B'"
|
|
|
- const minimumLamports = await getMinimumBalanceForRentExemptMint(connection);
|
|
|
+ // 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];
|
|
|
|
|
|
- const sendSolInstructions: Array<TransactionInstruction> = [alice, bob].map((account) =>
|
|
|
- SystemProgram.transfer({
|
|
|
- fromPubkey: provider.publicKey,
|
|
|
- toPubkey: account.publicKey,
|
|
|
- lamports: 10 * LAMPORTS_PER_SOL,
|
|
|
- }),
|
|
|
- );
|
|
|
+ // 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 createMintInstructions: Array<TransactionInstruction> = [tokenMintA, tokenMintB].map((mint) =>
|
|
|
- SystemProgram.createAccount({
|
|
|
- fromPubkey: provider.publicKey,
|
|
|
- newAccountPubkey: mint.publicKey,
|
|
|
- lamports: minimumLamports,
|
|
|
- space: MINT_SIZE,
|
|
|
- programId: TOKEN_PROGRAM,
|
|
|
- }),
|
|
|
- );
|
|
|
+ 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];
|
|
|
|
|
|
- // 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]);
|
|
|
+ // 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;
|
|
@@ -100,11 +108,7 @@ describe('escrow', async () => {
|
|
|
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 () => {
|
|
|
+ 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();
|
|
|
|
|
@@ -139,10 +143,9 @@ describe('escrow', async () => {
|
|
|
assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
|
|
|
assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
|
|
|
assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
|
|
|
- };
|
|
|
+ }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
|
|
|
|
|
|
- // We'll call this function from multiple tests, so let's seperate it out
|
|
|
- const take = async () => {
|
|
|
+ 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 })
|
|
@@ -162,13 +165,5 @@ describe('escrow', async () => {
|
|
|
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();
|
|
|
- });
|
|
|
+ }).slow(ANCHOR_SLOW_TEST_THRESHOLD);
|
|
|
});
|