123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- 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 serumCmn = require("@project-serum/common");
- const utils = require("./utils");
- // Taker fee rate (bps).
- 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.
- 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();
- it("BOILERPLATE: Sets up two markets with resting orders", async () => {
- ORDERBOOK_ENV = await utils.setupTwoMarkets({
- provider: program.provider,
- });
- });
- it("BOILERPLATE: Sets up reusable accounts", async () => {
- const marketA = ORDERBOOK_ENV.marketA;
- const marketB = ORDERBOOK_ENV.marketB;
- const [vaultSignerA] = await utils.getVaultOwnerAndNonce(
- marketA._decoded.ownAddress
- );
- const [vaultSignerB] = await utils.getVaultOwnerAndNonce(
- marketB._decoded.ownAddress
- );
- marketAVaultSigner = vaultSignerA;
- marketBVaultSigner = vaultSignerB;
- SWAP_USDC_A_ACCOUNTS = {
- market: {
- 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,
- 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,
- market: {
- ...SWAP_USDC_A_ACCOUNTS.market,
- orderPayerTokenAccount: 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 bestOfferPrice = 6.041;
- const amountToSpend = expectedResultantAmount * bestOfferPrice;
- const swapAmount = new BN((amountToSpend / (1 - TAKER_FEE)) * 10 ** 6);
- const [tokenAChange, usdcChange] = await withBalanceChange(
- program.provider,
- [ORDERBOOK_ENV.godA, ORDERBOOK_ENV.godUsdc],
- async () => {
- 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,
- 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,
- program.provider.wallet.publicKey,
- openOrdersB.publicKey,
- utils.DEX_PID
- ),
- ],
- signers: [openOrdersA, openOrdersB],
- });
- }
- );
- assert.strictEqual(tokenAChange, expectedResultantAmount);
- assert.strictEqual(usdcChange, -swapAmount.toNumber() / 10 ** 6);
- });
- 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(
- program.provider,
- [ORDERBOOK_ENV.godA, ORDERBOOK_ENV.godUsdc],
- async () => {
- await program.rpc.swap(
- Side.Ask,
- new BN(swapAmount * 10 ** 6),
- new BN(swapAmount),
- {
- accounts: SWAP_A_USDC_ACCOUNTS,
- }
- );
- }
- );
- assert.strictEqual(tokenAChange, -swapAmount);
- assert.strictEqual(usdcChange, resultantAmount.toNumber() / 10 ** 6);
- });
- 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,
- },
- pcWallet: ORDERBOOK_ENV.godUsdc,
- authority: program.provider.wallet.publicKey,
- dexProgram: utils.DEX_PID,
- tokenProgram: TOKEN_PROGRAM_ID,
- rent: anchor.web3.SYSVAR_RENT_PUBKEY,
- },
- }
- );
- }
- );
- 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,
- },
- pcWallet: ORDERBOOK_ENV.godUsdc,
- authority: program.provider.wallet.publicKey,
- dexProgram: utils.DEX_PID,
- tokenProgram: TOKEN_PROGRAM_ID,
- rent: anchor.web3.SYSVAR_RENT_PUBKEY,
- },
- }
- );
- }
- );
- // TODO: calculate this dynamically from the swap amount.
- assert.strictEqual(tokenAChange, 22.6);
- assert.strictEqual(tokenBChange, -swapAmount);
- assert.strictEqual(usdcChange, 0);
- });
- });
- // Side rust enum used for the program's RPC API.
- const Side = {
- Bid: { bid: {} },
- 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
- );
- }
- await fn();
- const afterBalances = [];
- for (let k = 0; k < addrs.length; k += 1) {
- afterBalances.push(
- (await serumCmn.getTokenAccount(provider, addrs[k])).amount
- );
- }
- const deltas = [];
- for (let k = 0; k < addrs.length; k += 1) {
- deltas.push(
- (afterBalances[k].toNumber() - beforeBalances[k].toNumber()) / 10 ** 6
- );
- }
- return deltas;
- }
|