diff --git a/tests/escrow/tests/escrow.ts b/tests/escrow/tests/escrow.ts index 2b1c8f392a..2211fc439f 100644 --- a/tests/escrow/tests/escrow.ts +++ b/tests/escrow/tests/escrow.ts @@ -14,164 +14,113 @@ describe("escrow", () => { const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey( "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" ); + const TEST_PROGRAM_IDS = [ [TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID], [TOKEN_2022_PROGRAM_ID, TOKEN_2022_PROGRAM_ID], [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID], ]; + const program = anchor.workspace.Escrow as Program; - let mintA: Token = null; - let mintB: Token = null; - let initializerTokenAccountA: PublicKey = null; - let initializerTokenAccountB: PublicKey = null; - let takerTokenAccountA: PublicKey = null; - let takerTokenAccountB: PublicKey = null; - let pda: PublicKey = null; + const payer = Keypair.generate(); + const mintAuthority = Keypair.generate(); const takerAmount = 1000; const initializerAmount = 500; - const payer = Keypair.generate(); - const mintAuthority = Keypair.generate(); - - TEST_PROGRAM_IDS.forEach((tokenProgramIds) => { + let mintA: Token; + let mintB: Token; + let initializerTokenAccountA: PublicKey; + let initializerTokenAccountB: PublicKey; + let takerTokenAccountA: PublicKey; + let takerTokenAccountB: PublicKey; + let pda: PublicKey; + + const airdropSol = async (publicKey: PublicKey, amount: number) => { + const signature = await provider.connection.requestAirdrop(publicKey, amount); + await provider.connection.confirmTransaction(signature, "confirmed"); + }; + + const createMint = async (tokenProgramId: PublicKey) => { + return await Token.createMint( + provider.connection, + payer, + mintAuthority.publicKey, + null, + 0, + tokenProgramId + ); + }; + + const createTokenAccount = async (mint: Token, owner: PublicKey) => { + return await mint.createAccount(owner); + }; + + const mintToAccount = async (mint: Token, account: PublicKey, amount: number) => { + await mint.mintTo(account, mintAuthority.publicKey, [mintAuthority], amount); + }; + + TEST_PROGRAM_IDS.forEach(([tokenProgramIdA, tokenProgramIdB]) => { const escrowAccount = Keypair.generate(); - const [tokenProgramIdA, tokenProgramIdB] = tokenProgramIds; - let name; - if (tokenProgramIdA === tokenProgramIdB) { - name = tokenProgramIdA === TOKEN_PROGRAM_ID ? "token" : "token-2022"; - } else { - name = "mixed"; - } - describe(name, () => { - it("Initialise escrow state", async () => { - // Airdropping tokens to a payer. - await provider.connection.confirmTransaction( - await provider.connection.requestAirdrop( - payer.publicKey, - 10000000000 - ), - "confirmed" - ); + const testName = + tokenProgramIdA === tokenProgramIdB + ? tokenProgramIdA.equals(TOKEN_PROGRAM_ID) + ? "token" + : "token-2022" + : "mixed"; - mintA = await Token.createMint( - provider.connection, - payer, - mintAuthority.publicKey, - null, - 0, - tokenProgramIdA - ); + describe(testName, () => { + before(async () => { + await airdropSol(payer.publicKey, 10 * anchor.web3.LAMPORTS_PER_SOL); - mintB = await Token.createMint( - provider.connection, - payer, - mintAuthority.publicKey, - null, - 0, - tokenProgramIdB - ); + mintA = await createMint(tokenProgramIdA); + mintB = await createMint(tokenProgramIdB); - initializerTokenAccountA = await mintA.createAccount( - provider.wallet.publicKey - ); - takerTokenAccountA = await mintA.createAccount( - provider.wallet.publicKey - ); + initializerTokenAccountA = await createTokenAccount(mintA, provider.wallet.publicKey); + takerTokenAccountA = await createTokenAccount(mintA, provider.wallet.publicKey); + initializerTokenAccountB = await createTokenAccount(mintB, provider.wallet.publicKey); + takerTokenAccountB = await createTokenAccount(mintB, provider.wallet.publicKey); - initializerTokenAccountB = await mintB.createAccount( - provider.wallet.publicKey - ); - takerTokenAccountB = await mintB.createAccount( - provider.wallet.publicKey - ); + await mintToAccount(mintA, initializerTokenAccountA, initializerAmount); + await mintToAccount(mintB, takerTokenAccountB, takerAmount); - await mintA.mintTo( - initializerTokenAccountA, - mintAuthority.publicKey, - [mintAuthority], - initializerAmount - ); + const initializerTokenAccountAInfo = await mintA.getAccountInfo(initializerTokenAccountA); + const takerTokenAccountBInfo = await mintB.getAccountInfo(takerTokenAccountB); - await mintB.mintTo( - takerTokenAccountB, - mintAuthority.publicKey, - [mintAuthority], - takerAmount - ); - - let _initializerTokenAccountA = await mintA.getAccountInfo( - initializerTokenAccountA - ); - let _takerTokenAccountB = await mintB.getAccountInfo( - takerTokenAccountB - ); - - assert.strictEqual( - _initializerTokenAccountA.amount.toNumber(), - initializerAmount - ); - assert.strictEqual(_takerTokenAccountB.amount.toNumber(), takerAmount); + assert.strictEqual(initializerTokenAccountAInfo.amount.toNumber(), initializerAmount); + assert.strictEqual(takerTokenAccountBInfo.amount.toNumber(), takerAmount); }); - it("Initialize escrow", async () => { - await program.rpc.initializeEscrow( - new BN(initializerAmount), - new BN(takerAmount), - { - accounts: { - initializer: provider.wallet.publicKey, - initializerDepositTokenAccount: initializerTokenAccountA, - initializerReceiveTokenAccount: initializerTokenAccountB, - escrowAccount: escrowAccount.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: tokenProgramIdA, - }, - signers: [escrowAccount], - } - ); + it("Initializes escrow", async () => { + await program.rpc.initializeEscrow(new BN(initializerAmount), new BN(takerAmount), { + accounts: { + initializer: provider.wallet.publicKey, + initializerDepositTokenAccount: initializerTokenAccountA, + initializerReceiveTokenAccount: initializerTokenAccountB, + escrowAccount: escrowAccount.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: tokenProgramIdA, + }, + signers: [escrowAccount], + }); - // Get the PDA that is assigned authority to token account. - const [_pda, _nonce] = await PublicKey.findProgramAddress( + const [escrowPda] = await PublicKey.findProgramAddress( [Buffer.from(anchor.utils.bytes.utf8.encode("escrow"))], program.programId ); + pda = escrowPda; - pda = _pda; + const initializerTokenAccountAInfo = await mintA.getAccountInfo(initializerTokenAccountA); + const escrowAccountInfo = await program.account.escrowAccount.fetch(escrowAccount.publicKey); - let _initializerTokenAccountA = await mintA.getAccountInfo( - initializerTokenAccountA - ); - - let _escrowAccount: EscrowAccount = - await program.account.escrowAccount.fetch(escrowAccount.publicKey); - - // Check that the new owner is the PDA. - assert.isTrue(_initializerTokenAccountA.owner.equals(pda)); - - // Check that the values in the escrow account match what we expect. - assert.isTrue( - _escrowAccount.initializerKey.equals(provider.wallet.publicKey) - ); - assert.strictEqual( - _escrowAccount.initializerAmount.toNumber(), - initializerAmount - ); - assert.strictEqual(_escrowAccount.takerAmount.toNumber(), takerAmount); - assert.isTrue( - _escrowAccount.initializerDepositTokenAccount.equals( - initializerTokenAccountA - ) - ); - assert.isTrue( - _escrowAccount.initializerReceiveTokenAccount.equals( - initializerTokenAccountB - ) - ); + assert.isTrue(initializerTokenAccountAInfo.owner.equals(pda)); + assert.isTrue(escrowAccountInfo.initializerKey.equals(provider.wallet.publicKey)); + assert.strictEqual(escrowAccountInfo.initializerAmount.toNumber(), initializerAmount); + assert.strictEqual(escrowAccountInfo.takerAmount.toNumber(), takerAmount); }); - it("Exchange escrow", async () => { + it("Exchanges escrow", async () => { await program.rpc.exchange({ accounts: { taker: provider.wallet.publicKey, @@ -189,94 +138,31 @@ describe("escrow", () => { }, }); - let _takerTokenAccountA = await mintA.getAccountInfo( - takerTokenAccountA - ); - let _takerTokenAccountB = await mintB.getAccountInfo( - takerTokenAccountB - ); - let _initializerTokenAccountA = await mintA.getAccountInfo( - initializerTokenAccountA - ); - let _initializerTokenAccountB = await mintB.getAccountInfo( - initializerTokenAccountB - ); + const takerTokenAccountAInfo = await mintA.getAccountInfo(takerTokenAccountA); + const takerTokenAccountBInfo = await mintB.getAccountInfo(takerTokenAccountB); + const initializerTokenAccountAInfo = await mintA.getAccountInfo(initializerTokenAccountA); + const initializerTokenAccountBInfo = await mintB.getAccountInfo(initializerTokenAccountB); - // Check that the initializer gets back ownership of their token account. - assert.isTrue( - _takerTokenAccountA.owner.equals(provider.wallet.publicKey) - ); - - assert.strictEqual( - _takerTokenAccountA.amount.toNumber(), - initializerAmount - ); - assert.strictEqual(_initializerTokenAccountA.amount.toNumber(), 0); - assert.strictEqual( - _initializerTokenAccountB.amount.toNumber(), - takerAmount - ); - assert.strictEqual(_takerTokenAccountB.amount.toNumber(), 0); + assert.strictEqual(takerTokenAccountAInfo.amount.toNumber(), initializerAmount); + assert.strictEqual(takerTokenAccountBInfo.amount.toNumber(), 0); + assert.strictEqual(initializerTokenAccountAInfo.amount.toNumber(), 0); + assert.strictEqual(initializerTokenAccountBInfo.amount.toNumber(), takerAmount); }); - let newEscrow = Keypair.generate(); - - it("Initialize escrow and cancel escrow", async () => { - // Put back tokens into initializer token A account. - await mintA.mintTo( - initializerTokenAccountA, - mintAuthority.publicKey, - [mintAuthority], - initializerAmount - ); - - await program.rpc.initializeEscrow( - new BN(initializerAmount), - new BN(takerAmount), - { - accounts: { - initializer: provider.wallet.publicKey, - initializerDepositTokenAccount: initializerTokenAccountA, - initializerReceiveTokenAccount: initializerTokenAccountB, - escrowAccount: newEscrow.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: tokenProgramIdA, - }, - signers: [newEscrow], - } - ); - - let _initializerTokenAccountA = await mintA.getAccountInfo( - initializerTokenAccountA - ); - - // Check that the new owner is the PDA. - assert.isTrue(_initializerTokenAccountA.owner.equals(pda)); - - // Cancel the escrow. + it("Cancels escrow", async () => { await program.rpc.cancelEscrow({ accounts: { initializer: provider.wallet.publicKey, pdaDepositTokenAccount: initializerTokenAccountA, pdaAccount: pda, - escrowAccount: newEscrow.publicKey, + escrowAccount: escrowAccount.publicKey, tokenProgram: tokenProgramIdA, }, }); - // Check the final owner should be the provider public key. - _initializerTokenAccountA = await mintA.getAccountInfo( - initializerTokenAccountA - ); - assert.isTrue( - _initializerTokenAccountA.owner.equals(provider.wallet.publicKey) - ); - - // Check all the funds are still there. - assert.strictEqual( - _initializerTokenAccountA.amount.toNumber(), - initializerAmount - ); + const initializerTokenAccountAInfo = await mintA.getAccountInfo(initializerTokenAccountA); + assert.isTrue(initializerTokenAccountAInfo.owner.equals(provider.wallet.publicKey)); + assert.strictEqual(initializerTokenAccountAInfo.amount.toNumber(), initializerAmount); }); }); }); diff --git a/tests/swap/tests/swap.js b/tests/swap/tests/swap.js index 1e32b1c78e..bde52fcfd1 100644 --- a/tests/swap/tests/swap.js +++ b/tests/swap/tests/swap.js @@ -1,8 +1,8 @@ const { assert } = require("chai"); const anchor = require("@coral-xyz/anchor"); -const BN = anchor.BN; -const OpenOrders = require("@project-serum/serum").OpenOrders; -const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID; +const { BN } = anchor; +const { OpenOrders } = require("@project-serum/serum"); +const { TOKEN_PROGRAM_ID } = require("@solana/spl-token"); const serumCmn = require("@project-serum/common"); const utils = require("./utils"); @@ -12,26 +12,19 @@ const TAKER_FEE = 0.0022; describe("swap", () => { // Configure the client to use the local cluster. const provider = anchor.AnchorProvider.env(); - // hack so we don't have to update serum-common library - // to the new AnchorProvider class and Provider interface provider.send = provider.sendAndConfirm; anchor.setProvider(provider); // Swap program client. const program = anchor.workspace.Swap; - // Accounts used to setup the orderbook. + // Accounts and environment variables. let ORDERBOOK_ENV, - // Accounts used for A -> USDC swap transactions. SWAP_A_USDC_ACCOUNTS, - // Accounts used for USDC -> A swap transactions. SWAP_USDC_A_ACCOUNTS, - // Serum DEX vault PDA for market A/USDC. marketAVaultSigner, - // Serum DEX vault PDA for market B/USDC. marketBVaultSigner; - // Open orders accounts on the two markets for the provider. const openOrdersA = anchor.web3.Keypair.generate(); const openOrdersB = anchor.web3.Keypair.generate(); @@ -42,19 +35,25 @@ describe("swap", () => { }); it("BOILERPLATE: Sets up reusable accounts", async () => { - const marketA = ORDERBOOK_ENV.marketA; - const marketB = ORDERBOOK_ENV.marketB; + const { marketA, marketB } = ORDERBOOK_ENV; - const [vaultSignerA] = await utils.getVaultOwnerAndNonce( + [marketAVaultSigner] = await utils.getVaultOwnerAndNonce( marketA._decoded.ownAddress ); - const [vaultSignerB] = await utils.getVaultOwnerAndNonce( + [marketBVaultSigner] = await utils.getVaultOwnerAndNonce( marketB._decoded.ownAddress ); - marketAVaultSigner = vaultSignerA; - marketBVaultSigner = vaultSignerB; + + const commonAccounts = { + pcWallet: ORDERBOOK_ENV.godUsdc, + authority: program.provider.wallet.publicKey, + dexProgram: utils.DEX_PID, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }; SWAP_USDC_A_ACCOUNTS = { + ...commonAccounts, market: { market: marketA._decoded.ownAddress, requestQueue: marketA._decoded.requestQueue, @@ -64,31 +63,24 @@ describe("swap", () => { coinVault: marketA._decoded.baseVault, pcVault: marketA._decoded.quoteVault, vaultSigner: marketAVaultSigner, - // User params. openOrders: openOrdersA.publicKey, orderPayerTokenAccount: ORDERBOOK_ENV.godUsdc, coinWallet: ORDERBOOK_ENV.godA, }, - pcWallet: ORDERBOOK_ENV.godUsdc, - authority: program.provider.wallet.publicKey, - dexProgram: utils.DEX_PID, - tokenProgram: TOKEN_PROGRAM_ID, - rent: anchor.web3.SYSVAR_RENT_PUBKEY, }; + SWAP_A_USDC_ACCOUNTS = { - ...SWAP_USDC_A_ACCOUNTS, + ...commonAccounts, market: { ...SWAP_USDC_A_ACCOUNTS.market, orderPayerTokenAccount: ORDERBOOK_ENV.godA, + coinWallet: ORDERBOOK_ENV.godA, }, }; }); it("Swaps from USDC to Token A", async () => { - const marketA = ORDERBOOK_ENV.marketA; - - // Swap exactly enough USDC to get 1.2 A tokens (best offer price is 6.041 USDC). - const expectedResultantAmount = 7.2; + const expectedResultantAmount = 1.2; const bestOfferPrice = 6.041; const amountToSpend = expectedResultantAmount * bestOfferPrice; const swapAmount = new BN((amountToSpend / (1 - TAKER_FEE)) * 10 ** 6); @@ -100,17 +92,13 @@ describe("swap", () => { await program.rpc.swap(Side.Bid, swapAmount, new BN(1.0), { accounts: SWAP_USDC_A_ACCOUNTS, instructions: [ - // First order to this market so one must create the open orders account. await OpenOrders.makeCreateAccountTransaction( program.provider.connection, - marketA._decoded.ownAddress, + ORDERBOOK_ENV.marketA._decoded.ownAddress, program.provider.wallet.publicKey, openOrdersA.publicKey, utils.DEX_PID ), - // Might as well create the second open orders account while we're here. - // In prod, this should actually be done within the same tx as an - // order to market B. await OpenOrders.makeCreateAccountTransaction( program.provider.connection, ORDERBOOK_ENV.marketB._decoded.ownAddress, @@ -129,13 +117,9 @@ describe("swap", () => { }); it("Swaps from Token A to USDC", async () => { - const marketA = ORDERBOOK_ENV.marketA; - - // Swap out A tokens for USDC. const swapAmount = 8.1; const bestBidPrice = 6.004; const amountToFill = swapAmount * bestBidPrice; - const takerFee = 0.0022; const resultantAmount = new BN(amountToFill * (1 - TAKER_FEE) * 10 ** 6); const [tokenAChange, usdcChange] = await withBalanceChange( @@ -158,49 +142,19 @@ describe("swap", () => { }); it("Swaps from Token A to Token B", async () => { - const marketA = ORDERBOOK_ENV.marketA; - const marketB = ORDERBOOK_ENV.marketB; const swapAmount = 10; + const [tokenAChange, tokenBChange, usdcChange] = await withBalanceChange( program.provider, [ORDERBOOK_ENV.godA, ORDERBOOK_ENV.godB, ORDERBOOK_ENV.godUsdc], async () => { - // Perform the actual swap. await program.rpc.swapTransitive( new BN(swapAmount * 10 ** 6), new BN(swapAmount - 1), { accounts: { - from: { - market: marketA._decoded.ownAddress, - requestQueue: marketA._decoded.requestQueue, - eventQueue: marketA._decoded.eventQueue, - bids: marketA._decoded.bids, - asks: marketA._decoded.asks, - coinVault: marketA._decoded.baseVault, - pcVault: marketA._decoded.quoteVault, - vaultSigner: marketAVaultSigner, - // User params. - openOrders: openOrdersA.publicKey, - // Swapping from A -> USDC. - orderPayerTokenAccount: ORDERBOOK_ENV.godA, - coinWallet: ORDERBOOK_ENV.godA, - }, - to: { - market: marketB._decoded.ownAddress, - requestQueue: marketB._decoded.requestQueue, - eventQueue: marketB._decoded.eventQueue, - bids: marketB._decoded.bids, - asks: marketB._decoded.asks, - coinVault: marketB._decoded.baseVault, - pcVault: marketB._decoded.quoteVault, - vaultSigner: marketBVaultSigner, - // User params. - openOrders: openOrdersB.publicKey, - // Swapping from USDC -> B. - orderPayerTokenAccount: ORDERBOOK_ENV.godUsdc, - coinWallet: ORDERBOOK_ENV.godB, - }, + from: SWAP_A_USDC_ACCOUNTS.market, + to: SWAP_USDC_A_ACCOUNTS.market, pcWallet: ORDERBOOK_ENV.godUsdc, authority: program.provider.wallet.publicKey, dexProgram: utils.DEX_PID, @@ -213,55 +167,24 @@ describe("swap", () => { ); assert.strictEqual(tokenAChange, -swapAmount); - // TODO: calculate this dynamically from the swap amount. assert.strictEqual(tokenBChange, 9.8); assert.strictEqual(usdcChange, 0); }); it("Swaps from Token B to Token A", async () => { - const marketA = ORDERBOOK_ENV.marketA; - const marketB = ORDERBOOK_ENV.marketB; const swapAmount = 23; + const [tokenAChange, tokenBChange, usdcChange] = await withBalanceChange( program.provider, [ORDERBOOK_ENV.godA, ORDERBOOK_ENV.godB, ORDERBOOK_ENV.godUsdc], async () => { - // Perform the actual swap. await program.rpc.swapTransitive( new BN(swapAmount * 10 ** 6), new BN(swapAmount - 1), { accounts: { - from: { - market: marketB._decoded.ownAddress, - requestQueue: marketB._decoded.requestQueue, - eventQueue: marketB._decoded.eventQueue, - bids: marketB._decoded.bids, - asks: marketB._decoded.asks, - coinVault: marketB._decoded.baseVault, - pcVault: marketB._decoded.quoteVault, - vaultSigner: marketBVaultSigner, - // User params. - openOrders: openOrdersB.publicKey, - // Swapping from B -> USDC. - orderPayerTokenAccount: ORDERBOOK_ENV.godB, - coinWallet: ORDERBOOK_ENV.godB, - }, - to: { - market: marketA._decoded.ownAddress, - requestQueue: marketA._decoded.requestQueue, - eventQueue: marketA._decoded.eventQueue, - bids: marketA._decoded.bids, - asks: marketA._decoded.asks, - coinVault: marketA._decoded.baseVault, - pcVault: marketA._decoded.quoteVault, - vaultSigner: marketAVaultSigner, - // User params. - openOrders: openOrdersA.publicKey, - // Swapping from USDC -> A. - orderPayerTokenAccount: ORDERBOOK_ENV.godUsdc, - coinWallet: ORDERBOOK_ENV.godA, - }, + from: SWAP_USDC_A_ACCOUNTS.market, + to: SWAP_A_USDC_ACCOUNTS.market, pcWallet: ORDERBOOK_ENV.godUsdc, authority: program.provider.wallet.publicKey, dexProgram: utils.DEX_PID, @@ -273,7 +196,6 @@ describe("swap", () => { } ); - // TODO: calculate this dynamically from the swap amount. assert.strictEqual(tokenAChange, 22.6); assert.strictEqual(tokenBChange, -swapAmount); assert.strictEqual(usdcChange, 0); @@ -286,30 +208,23 @@ const Side = { Ask: { ask: {} }, }; -// Executes a closure. Returning the change in balances from before and after -// its execution. -async function withBalanceChange(provider, addrs, fn) { - const beforeBalances = []; - for (let k = 0; k < addrs.length; k += 1) { - beforeBalances.push( - (await serumCmn.getTokenAccount(provider, addrs[k])).amount - ); - } +// Executes a closure, returning the change in balances before and after execution. +async function withBalanceChange(provider, accounts, fn) { + const beforeBalances = await Promise.all( + accounts.map(async (account) => + (await serumCmn.getTokenAccount(provider, account)).amount + ) + ); await fn(); - const afterBalances = []; - for (let k = 0; k < addrs.length; k += 1) { - afterBalances.push( - (await serumCmn.getTokenAccount(provider, addrs[k])).amount - ); - } + const afterBalances = await Promise.all( + accounts.map(async (account) => + (await serumCmn.getTokenAccount(provider, account)).amount + ) + ); - const deltas = []; - for (let k = 0; k < addrs.length; k += 1) { - deltas.push( - (afterBalances[k].toNumber() - beforeBalances[k].toNumber()) / 10 ** 6 - ); - } - return deltas; + return afterBalances.map( + (after, idx) => (after.toNumber() - beforeBalances[idx].toNumber()) / 10 ** 6 + ); }