Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance swap logic and refactor tests #3400

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
);
}