Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante committed Apr 26, 2021
1 parent 35714ca commit 7ca861d
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 64 deletions.
2 changes: 1 addition & 1 deletion examples/swap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ for performing instantly settled token swaps.
## Usage

This example requires building the Serum DEX from source, which is done using
git submodles.
git submodules.

### Install Submodules

Expand Down
17 changes: 11 additions & 6 deletions examples/swap/programs/swap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub mod swap {
//
// When side is "bid", then swaps B for A. When side is "ask", then swaps
// A for B.
//
// Amount is the amount to swap *from*.
#[access_control(is_valid_swap(&ctx))]
pub fn swap<'info>(
ctx: Context<'_, '_, '_, 'info, Swap<'info>>,
Expand Down Expand Up @@ -65,7 +67,7 @@ pub mod swap {
// Calculate change in balances, i.e. the amount actually swapped.
let from_amount = from_amount_before.checked_sub(from_amount_after).unwrap();
let to_amount = to_amount_after.checked_sub(to_amount_before).unwrap();
let spill_amount = 0; // TODO
let spill_amount = 0;

apply_risk_checks(
&ctx,
Expand Down Expand Up @@ -293,11 +295,14 @@ struct OrderbookClient<'info> {

impl<'info> OrderbookClient<'info> {
// Executes the sell order portion of the swap. Instantly settles.
fn sell(&self, size: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
let market = MarketState::load(&self.market.market, &dex::ID)?;
fn sell(&self, base_amount: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
let limit_price = 1;
let max_coin_qty = coin_lots(&market, size);
let max_native_pc_qty = 1; // Not used for asks.
let max_coin_qty = {
// The loaded market must be dropped before CPI.
let market = MarketState::load(&self.market.market, &dex::ID)?;
coin_lots(&market, base_amount)
};
let max_native_pc_qty = u64::MAX;
self.order_cpi(
limit_price,
max_coin_qty,
Expand All @@ -312,7 +317,7 @@ impl<'info> OrderbookClient<'info> {
// settles.
fn buy(&self, quote_amount: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
let limit_price = u64::MAX;
let max_coin_qty = 1; // Not used for bids.
let max_coin_qty = u64::MAX;
let max_native_pc_qty = quote_amount;
self.order_cpi(
limit_price,
Expand Down
174 changes: 121 additions & 53 deletions examples/swap/tests/swap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,27 @@ 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 = .0022;

describe("swap", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());

// Swap program client.
const program = anchor.workspace.Swap;

let ORDERBOOK_ENV;
const openOrdersA = new anchor.web3.Account();
// Accounts used to setup the orderbook.
let ORDERBOOK_ENV,
// Accounts used for A <-> USDC swap transactions.
SWAP_A_USDC_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 = new anchor.web3.Account();
const openOrdersB = new anchor.web3.Account();

it("BOILERPLATE: Sets up two markets with resting orders", async () => {
Expand All @@ -21,74 +34,129 @@ describe("swap", () => {
});
});

it("Swaps from token USDC to A", async () => {
it("BOILERPLATE: Sets up reusable accounts", async () => {
const marketA = ORDERBOOK_ENV.marketA;
const [vaultSigner] = await utils.getVaultOwnerAndNonce(
const marketB = ORDERBOOK_ENV.marketA;

const [vaultSignerA] = await utils.getVaultOwnerAndNonce(
marketA._decoded.ownAddress
);
const [vaultSignerB] = await utils.getVaultOwnerAndNonce(
marketB._decoded.ownAddress
);
marketAVaultSigner = vaultSignerA;
marketBVaultSigner = vaultSignerB;

const tokenABefore = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godA
SWAP_A_USDC_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,
};
});

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).
// This amount includes the 22 bps taker fee.
const expectedResultantAmount = 7.2;
const bestOfferPrice = 6.041;
const amountToSpend = expectedResultantAmount * bestOfferPrice;
const swapAmount = new anchor.BN(
(amountToSpend / (1 - TAKER_FEE)) * 10 ** 6
);
const usdcBefore = await serumCmn.getTokenAccount(

const [tokenAChange, usdcChange] = await withBalanceChange(
program.provider,
ORDERBOOK_ENV.godUsdc
ORDERBOOK_ENV.godA,
ORDERBOOK_ENV.godUsdc,
async () => {
await program.rpc.swap(Side.Bid, swapAmount, {
accounts: SWAP_A_USDC_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
),
],
signers: [openOrdersA],
});
}
);

const tx = await program.rpc.swap(Side.Bid, new anchor.BN(2), {
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,
// 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,
},
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
),
],
signers: [openOrdersA],
});
assert.ok(tokenAChange === expectedResultantAmount);
assert.ok(usdcChange === -swapAmount.toNumber() / 10 ** 6);
});

const tokenAAFter = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godA
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 anchor.BN(
(amountToFill / (1 - TAKER_FEE)) * 10 ** 6
);
const usdcAfter = await serumCmn.getTokenAccount(

const [tokenAChange, usdcChange] = await withBalanceChange(
program.provider,
ORDERBOOK_ENV.godUsdc
ORDERBOOK_ENV.godA,
ORDERBOOK_ENV.godUsdc,
async () => {
await program.rpc.swap(Side.Ask, new anchor.BN(swapAmount * 10 ** 6), {
accounts: SWAP_A_USDC_ACCOUNTS,
});
}
);

console.log(
"Token A",
tokenAAFter.amount.sub(tokenABefore.amount).toNumber()
);
console.log("usdc", usdcBefore.amount.sub(usdcAfter.amount).toNumber());
assert.ok(tokenAChange === -swapAmount);
assert.ok(usdcChange === resultantAmount.toNumber() / 10 ** 6);
});
});

// 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, addr1, addr2, fn) {
const tokenABefore = await serumCmn.getTokenAccount(provider, addr1);
const usdcBefore = await serumCmn.getTokenAccount(provider, addr2);

await fn();

const tokenAAFter = await serumCmn.getTokenAccount(provider, addr1);
const usdcAfter = await serumCmn.getTokenAccount(provider, addr2);

const tokenAChange =
(tokenAAFter.amount.toNumber() - tokenABefore.amount.toNumber()) / 10 ** 6;
const usdcChange =
(usdcAfter.amount.toNumber() - usdcBefore.amount.toNumber()) / 10 ** 6;

return [tokenAChange, usdcChange];
}
9 changes: 5 additions & 4 deletions examples/swap/tests/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const TokenInstructions = require("@project-serum/serum").TokenInstructions;
const Market = require("@project-serum/serum").Market;
const DexInstructions = require("@project-serum/serum").DexInstructions;
const web3 = require("@project-serum/anchor").web3;
const Connection = web3.Connection;
const BN = require("@project-serum/anchor").BN;
const serumCmn = require("@project-serum/common");
const Account = web3.Account;
Expand Down Expand Up @@ -263,8 +264,8 @@ async function setupMarket({
wallet: provider.wallet,
baseMint: baseMint,
quoteMint: quoteMint,
baseLotSize: 1000000,
quoteLotSize: 10000,
baseLotSize: 100000,
quoteLotSize: 100,
dexProgramId: DEX_PID,
feeRateBps: 0,
});
Expand All @@ -286,7 +287,7 @@ async function setupMarket({
price: ask[0],
size: ask[1],
orderType: "postOnly",
clientId: undefined, // todo?
clientId: undefined,
openOrdersAddressKey: undefined,
openOrdersAccount: undefined,
feeDiscountPubkey: null,
Expand All @@ -307,7 +308,7 @@ async function setupMarket({
price: bid[0],
size: bid[1],
orderType: "postOnly",
clientId: undefined, // todo?
clientId: undefined,
openOrdersAddressKey: undefined,
openOrdersAccount: undefined,
feeDiscountPubkey: null,
Expand Down

0 comments on commit 7ca861d

Please sign in to comment.