Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante committed Apr 25, 2021
1 parent c4a2796 commit 35714ca
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 117 deletions.
2 changes: 1 addition & 1 deletion examples/swap/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ wallet = "~/.config/solana/id.json"

[[test.genesis]]
address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
program = "./deps/serum-dex/dex/target/bpfel-unknown-unknown/release/serum_dex.so"
program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"
70 changes: 37 additions & 33 deletions examples/swap/programs/swap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
//! Program to perform instantly settled token swaps on the Serum DEX.
//!
//! Before using any method here, a user must first create an open orders
//! account on all markets being used. This only needs to be done once. As a
//! convention established by the DEX, this should be done via the system
//! program create account instruction in the same transaction as the user's
//! first trade. The DEX will lazily initialize the account.
use anchor_lang::prelude::*;
use anchor_spl::dex::serum_dex::instruction::SelfTradeBehavior;
use anchor_spl::dex::serum_dex::matching::{OrderType, Side as SerumSide};
use anchor_spl::dex::serum_dex::state::MarketState;
use anchor_spl::dex::{self, DEX_PROGRAM_ID};
use anchor_spl::dex::{self};
use anchor_spl::token::TokenAccount;
use std::num::NonZeroU64;

#[program]
pub mod swap {
use super::*;

// Swaps two tokens on a single A/B market. This is just a direct IOC trade
// that instantly settles.
// Swaps two tokens on a single A/B market, where A is the base currency
// and B is the quote currency. This is just a direct IOC trade that
// instantly settles.
//
// When side is "bid", then swaps B for A. When side is "ask", then swaps
// A for B.
//
// Before using this method, a user must first create an open orders account
// on the market being used. This should be done via the system program
// create account instruction in the same transaction as the user's first
// trade. The DEX will lazily initialize the account.
#[access_control(is_valid_swap(&ctx))]
pub fn swap<'info>(
ctx: Context<'_, '_, '_, 'info, Swap<'info>>,
Expand Down Expand Up @@ -61,9 +63,9 @@ pub mod swap {
(from_token.reload()?.amount, to_token.reload()?.amount);

// Calculate change in balances, i.e. the amount actually swapped.
let from_amount = from_amount_after - from_amount_before;
let to_amount = to_amount_after - to_amount_before;
let spill_amount = 0;
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

apply_risk_checks(
&ctx,
Expand Down Expand Up @@ -94,11 +96,6 @@ pub mod swap {
// * Settle open orders to get USD(x).
// * IOC buy order on B/USD(x) market to convert USD(x) to token B.
// * Settle open orders to get token B.
//
// Before using this method, a user must first create open orders accounts
// on both markets being used. This should be done via the system program
// create account instruction in the same transaction as the user's first
// trade. The DEX will lazily initialize the account.
#[access_control(is_valid_swap_transitive(&ctx))]
pub fn swap_transitive<'info>(
ctx: Context<'_, '_, '_, 'info, SwapTransitive<'info>>,
Expand Down Expand Up @@ -139,7 +136,10 @@ pub mod swap {
};

// Report the delta.
(base_after - base_before, quote_after - quote_before)
(
base_after.checked_sub(base_before).unwrap(),
quote_after.checked_sub(quote_before).unwrap(),
)
};

// The amount of the base token gained from the buy trade.
Expand All @@ -166,7 +166,10 @@ pub mod swap {
};

// Report the delta.
(base_after - base_before, quote_after - quote_before)
(
base_after.checked_sub(base_before).unwrap(),
quote_after.checked_sub(quote_before).unwrap(),
)
};

apply_risk_checks_transitive(
Expand All @@ -188,6 +191,7 @@ pub mod swap {
}

fn apply_risk_checks(ctx: &Context<Swap>, event: DidSwap) -> Result<()> {
// msg!("event: {:?}", event);
// todo
//
// TODO: check that the executed within some % from mid/fair.
Expand All @@ -196,6 +200,7 @@ fn apply_risk_checks(ctx: &Context<Swap>, event: DidSwap) -> Result<()> {
}

fn apply_risk_checks_transitive(ctx: &Context<SwapTransitive>, event: DidSwap) -> Result<()> {
// msg!("event: {:?}", event);
// todo
//
// TODO: check that the executed within some % from mid/fair.
Expand Down Expand Up @@ -276,6 +281,7 @@ impl<'info> SwapTransitive<'info> {
}
}

// CPI client for sending orders to the Serum DEX.
struct OrderbookClient<'info> {
market: MarketAccounts<'info>,
authority: AccountInfo<'info>,
Expand All @@ -288,16 +294,15 @@ 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_PROGRAM_ID)?;
let market = MarketState::load(&self.market.market, &dex::ID)?;
let limit_price = 1;
let max_coin_qty = coin_lots(&market, size);
let max_native_pc_qty = 0; // Not used for asks.
//pc_native(&market, size, limit_price);
let max_native_pc_qty = 1; // Not used for asks.
self.order_cpi(
limit_price,
max_coin_qty,
max_native_pc_qty,
SerumSide::Ask,
Side::Ask,
referral,
)
}
Expand All @@ -306,15 +311,14 @@ impl<'info> OrderbookClient<'info> {
// base currency as possible, for the given `quote_amount`. Instantly
// settles.
fn buy(&self, quote_amount: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
let market = MarketState::load(&self.market.market, &*DEX_PROGRAM_ID)?;
let limit_price = u64::MAX;
let max_coin_qty = 0; // Not used for bids.
let max_coin_qty = 1; // Not used for bids.
let max_native_pc_qty = quote_amount;
self.order_cpi(
limit_price,
max_coin_qty,
max_native_pc_qty,
SerumSide::Bid,
Side::Bid,
referral,
)
}
Expand All @@ -326,7 +330,7 @@ impl<'info> OrderbookClient<'info> {
limit_price: u64,
max_coin_qty: u64,
max_native_pc_qty: u64,
side: SerumSide,
side: Side,
referral: Option<AccountInfo<'info>>,
) -> ProgramResult {
// Client order id is only used for cancels. Not used here so hardcode.
Expand All @@ -343,7 +347,7 @@ impl<'info> OrderbookClient<'info> {
event_queue: self.market.event_queue.clone(),
market_bids: self.market.bids.clone(),
market_asks: self.market.asks.clone(),
order_payer: self.market.order_payer.clone(),
order_payer_token_account: self.market.order_payer_token_account.clone(),
open_orders_authority: self.authority.clone(),
coin_vault: self.market.coin_vault.clone(),
pc_vault: self.market.pc_vault.clone(),
Expand All @@ -356,7 +360,7 @@ impl<'info> OrderbookClient<'info> {
}
dex::new_order_v3(
ctx,
side,
side.into(),
NonZeroU64::new(limit_price).unwrap(),
NonZeroU64::new(max_coin_qty).unwrap(),
NonZeroU64::new(max_native_pc_qty).unwrap(),
Expand Down Expand Up @@ -432,7 +436,7 @@ pub struct MarketAccounts<'info> {
asks: AccountInfo<'info>,
// The `spl_token::Account` that funds will be taken from.
#[account(mut)]
order_payer: AccountInfo<'info>,
order_payer_token_account: AccountInfo<'info>,
// Also known as the "base" currency. For a given A/B market,
// this is the vault for the A mint.
#[account(mut)]
Expand All @@ -453,11 +457,11 @@ pub enum Side {
Ask,
}

impl From<SerumSide> for Side {
fn from(side: SerumSide) -> Side {
impl From<Side> for SerumSide {
fn from(side: Side) -> SerumSide {
match side {
SerumSide::Bid => Side::Bid,
SerumSide::Ask => Side::Ask,
Side::Bid => SerumSide::Bid,
Side::Ask => SerumSide::Ask,
}
}
}
Expand Down
131 changes: 75 additions & 56 deletions examples/swap/tests/swap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const assert = require("assert");
const anchor = require("@project-serum/anchor");
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");

describe("swap", () => {
Expand All @@ -8,68 +11,84 @@ describe("swap", () => {

const program = anchor.workspace.Swap;

// The markets.
let MARKET_A_USDC, MARKET_B_USDC;
let ORDERBOOK_ENV;
const openOrdersA = new anchor.web3.Account();
const openOrdersB = new anchor.web3.Account();

// Account placing orders on the orderbook.

it("Sets up two markets with resting orders", async () => {
const { marketA, marketB, marketMaker } = await utils.setupTwoMarkets({
it("BOILERPLATE: Sets up two markets with resting orders", async () => {
ORDERBOOK_ENV = await utils.setupTwoMarkets({
provider: program.provider,
});
});

it("Swaps from token USDC to A", async () => {
const marketA = ORDERBOOK_ENV.marketA;
const [vaultSigner] = await utils.getVaultOwnerAndNonce(
marketA._decoded.ownAddress
);

let myOrders = await marketA.loadOrdersForOwner(
program.provider.connection,
marketMaker.account.publicKey
const tokenABefore = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godA
);
console.log("orders", myOrders);
myOrders = await marketB.loadOrdersForOwner(
program.provider.connection,
marketMaker.account.publicKey
const usdcBefore = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godUsdc
);

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],
});

const tokenAAFter = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godA
);
const usdcAfter = await serumCmn.getTokenAccount(
program.provider,
ORDERBOOK_ENV.godUsdc
);
console.log("orders", myOrders);
});

it("Is initialized!", async () => {
// await program.rpc.createUserAccount();
/*
const tx = await program.rpc.swapBase(new BN(10), {
accounts: {
from: {
market: anchor.web3.SYSVAR_RENT_PUBKEY,
openOrders: anchor.web3.SYSVAR_RENT_PUBKEY,
openOrdersAuthority: program.provider.wallet.publicKey,
requestQueue: anchor.web3.SYSVAR_RENT_PUBKEY,
eventQueue: anchor.web3.SYSVAR_RENT_PUBKEY,
bids: anchor.web3.SYSVAR_RENT_PUBKEY,
asks: anchor.web3.SYSVAR_RENT_PUBKEY,
orderPayer: anchor.web3.SYSVAR_RENT_PUBKEY,
coinVault: anchor.web3.SYSVAR_RENT_PUBKEY,
pcVault: anchor.web3.SYSVAR_RENT_PUBKEY,
vaultSigner: anchor.web3.SYSVAR_RENT_PUBKEY,
coinWallet: anchor.web3.SYSVAR_RENT_PUBKEY,
},
to: {
market: anchor.web3.SYSVAR_RENT_PUBKEY,
openOrders: anchor.web3.SYSVAR_RENT_PUBKEY,
openOrdersAuthority: program.provider.wallet.publicKey,
requestQueue: anchor.web3.SYSVAR_RENT_PUBKEY,
eventQueue: anchor.web3.SYSVAR_RENT_PUBKEY,
bids: anchor.web3.SYSVAR_RENT_PUBKEY,
asks: anchor.web3.SYSVAR_RENT_PUBKEY,
orderPayer: anchor.web3.SYSVAR_RENT_PUBKEY,
coinVault: anchor.web3.SYSVAR_RENT_PUBKEY,
pcVault: anchor.web3.SYSVAR_RENT_PUBKEY,
vaultSigner: anchor.web3.SYSVAR_RENT_PUBKEY,
coinWallet: anchor.web3.SYSVAR_RENT_PUBKEY,
},
pcWallet: anchor.web3.SYSVAR_RENT_PUBKEY,
dexProgram: anchor.web3.SYSVAR_RENT_PUBKEY,
tokenProgram: anchor.web3.SYSVAR_RENT_PUBKEY,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
}
});
console.log("Your transaction signature", tx);
*/
console.log(
"Token A",
tokenAAFter.amount.sub(tokenABefore.amount).toNumber()
);
console.log("usdc", usdcBefore.amount.sub(usdcAfter.amount).toNumber());
});
});

const Side = {
Bid: { bid: {} },
Ask: { ask: {} },
};
Loading

0 comments on commit 35714ca

Please sign in to comment.