Skip to content

Commit

Permalink
feat: enhance swap logic and refactor tests
Browse files Browse the repository at this point in the history
  • Loading branch information
techvoyagerX committed Dec 2, 2024
1 parent 5ed75fb commit 46813a2
Showing 1 changed file with 42 additions and 127 deletions.
169 changes: 42 additions & 127 deletions tests/swap/tests/swap.js
Original file line number Diff line number Diff line change
@@ -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");

Expand All @@ -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();

Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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
);
}

0 comments on commit 46813a2

Please sign in to comment.