From 46813a2185ff327ad4d366a90f6a895fda6cb43c Mon Sep 17 00:00:00 2001 From: techvoyagerX Date: Mon, 2 Dec 2024 04:23:47 -0500 Subject: [PATCH] feat: enhance swap logic and refactor tests --- tests/swap/tests/swap.js | 169 ++++++++++----------------------------- 1 file changed, 42 insertions(+), 127 deletions(-) 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 + ); }