From 4f7e3a57384812bbb65cdfb50d01f72598ea3f11 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 18 Mar 2022 10:08:53 +0100 Subject: [PATCH] create perp market wip Signed-off-by: microwavedcola1 --- .../src/instructions/create_perp_market.rs | 84 ++++++++++ programs/mango-v4/src/instructions/mod.rs | 2 + programs/mango-v4/src/lib.rs | 8 + programs/mango-v4/src/state/mod.rs | 2 + programs/mango-v4/src/state/perp/book.rs | 4 + .../mango-v4/src/state/perp/event_queue.rs | 4 + programs/mango-v4/src/state/perp/mod.rs | 7 + .../mango-v4/src/state/perp/perp_market.rs | 68 ++++++++ .../tests/program_test/mango_client.rs | 76 +++++++++ programs/mango-v4/tests/test_perp.rs | 153 ++++++++++++++++++ 10 files changed, 408 insertions(+) create mode 100644 programs/mango-v4/src/instructions/create_perp_market.rs create mode 100644 programs/mango-v4/src/state/perp/book.rs create mode 100644 programs/mango-v4/src/state/perp/event_queue.rs create mode 100644 programs/mango-v4/src/state/perp/mod.rs create mode 100644 programs/mango-v4/src/state/perp/perp_market.rs create mode 100644 programs/mango-v4/tests/test_perp.rs diff --git a/programs/mango-v4/src/instructions/create_perp_market.rs b/programs/mango-v4/src/instructions/create_perp_market.rs new file mode 100644 index 000000000..566b34027 --- /dev/null +++ b/programs/mango-v4/src/instructions/create_perp_market.rs @@ -0,0 +1,84 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::*; +use anchor_spl::token::Token; + +#[derive(Accounts)] +pub struct CreatePerpMarket<'info> { + #[account( + mut, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + pub admin: Signer<'info>, + + pub oracle: UncheckedAccount<'info>, + #[account( + init, + seeds = [group.key().as_ref(), b"PerpMarket".as_ref(), oracle.key().as_ref()], + bump, + payer = payer, + space = 8 + std::mem::size_of::(), + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account( + init, + seeds = [group.key().as_ref(), b"Asks".as_ref(), perp_market.key().as_ref()], + bump, + payer = payer, + space = 8 + std::mem::size_of::(), + )] + pub asks: AccountLoader<'info, crate::state::Book>, + #[account( + init, + seeds = [group.key().as_ref(), b"Bids".as_ref(), perp_market.key().as_ref()], + bump, + payer = payer, + space = 8 + std::mem::size_of::(), + )] + pub bids: AccountLoader<'info, Book>, + #[account( + init, + seeds = [group.key().as_ref(), b"EventQueue".as_ref(), perp_market.key().as_ref()], + bump, + payer = payer, + space = 8 + std::mem::size_of::(), + )] + pub event_queue: AccountLoader<'info, crate::state::EventQueue>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, + pub token_program: Program<'info, Token>, + pub rent: Sysvar<'info, Rent>, +} + +pub fn create_perp_market( + ctx: Context, + quote_lot_size: i64, + base_lot_size: i64, +) -> Result<()> { + let mut perp_market = ctx.accounts.perp_market.load_init()?; + *perp_market = PerpMarket { + group: ctx.accounts.group.key(), + oracle: ctx.accounts.oracle.key(), + bids: ctx.accounts.bids.key(), + asks: ctx.accounts.asks.key(), + event_queue: ctx.accounts.event_queue.key(), + quote_lot_size: quote_lot_size, + base_lot_size: base_lot_size, + // long_funding, + // short_funding, + // last_updated, + // open_interest, + seq_num: 0, + // fees_accrued, + // liquidity_mining_info, + // mngo_vault: ctx.accounts.mngo_vault.key(), + bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?, + }; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 59cecb63e..8a202ec92 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -1,6 +1,7 @@ pub use self::margin_trade::*; pub use create_account::*; pub use create_group::*; +pub use create_perp_market::*; pub use create_serum_open_orders::*; pub use create_stub_oracle::*; pub use deposit::*; @@ -12,6 +13,7 @@ pub use withdraw::*; mod create_account; mod create_group; +mod create_perp_market; mod create_serum_open_orders; mod create_stub_oracle; mod deposit; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index c64dcd588..8d9dec436 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -96,6 +96,14 @@ pub mod mango_v4 { ) -> Result<()> { instructions::place_serum_order(ctx, order) } + + pub fn create_perp_market( + ctx: Context, + quote_lot_size: i64, + base_lot_size: i64, + ) -> Result<()> { + instructions::create_perp_market(ctx, quote_lot_size, base_lot_size) + } } #[derive(Clone)] diff --git a/programs/mango-v4/src/state/mod.rs b/programs/mango-v4/src/state/mod.rs index b37a2cb6a..dfa835f0a 100644 --- a/programs/mango-v4/src/state/mod.rs +++ b/programs/mango-v4/src/state/mod.rs @@ -4,6 +4,7 @@ pub use health::*; pub use mango_account::*; pub use mint_info::*; pub use oracle::*; +pub use perp::*; pub use serum_market::*; mod bank; @@ -12,4 +13,5 @@ mod health; mod mango_account; mod mint_info; mod oracle; +mod perp; mod serum_market; diff --git a/programs/mango-v4/src/state/perp/book.rs b/programs/mango-v4/src/state/perp/book.rs new file mode 100644 index 000000000..0eb177988 --- /dev/null +++ b/programs/mango-v4/src/state/perp/book.rs @@ -0,0 +1,4 @@ +use anchor_lang::prelude::*; + +#[account(zero_copy)] +pub struct Book {} diff --git a/programs/mango-v4/src/state/perp/event_queue.rs b/programs/mango-v4/src/state/perp/event_queue.rs new file mode 100644 index 000000000..4df70a2ec --- /dev/null +++ b/programs/mango-v4/src/state/perp/event_queue.rs @@ -0,0 +1,4 @@ +use anchor_lang::prelude::*; + +#[account(zero_copy)] +pub struct EventQueue {} diff --git a/programs/mango-v4/src/state/perp/mod.rs b/programs/mango-v4/src/state/perp/mod.rs new file mode 100644 index 000000000..3dd90b699 --- /dev/null +++ b/programs/mango-v4/src/state/perp/mod.rs @@ -0,0 +1,7 @@ +pub use book::*; +pub use event_queue::*; +pub use perp_market::*; + +mod book; +mod event_queue; +mod perp_market; diff --git a/programs/mango-v4/src/state/perp/perp_market.rs b/programs/mango-v4/src/state/perp/perp_market.rs new file mode 100644 index 000000000..ee64cf28c --- /dev/null +++ b/programs/mango-v4/src/state/perp/perp_market.rs @@ -0,0 +1,68 @@ +use anchor_lang::prelude::*; + +#[account(zero_copy)] +pub struct PerpMarket { + // todo + /// metadata + // pub meta_data: MetaData, + + /// mango group + pub group: Pubkey, + + // todo better docs + /// + pub oracle: Pubkey, + + /// order book + pub bids: Pubkey, + pub asks: Pubkey, + + // todo better docs + /// + pub event_queue: Pubkey, + + /// number of quote native that reresents min tick + /// e.g. base lot size 100, quote lot size 10, then tick i.e. price increment is 10/100 i.e. 1 + // todo: why signed? + pub quote_lot_size: i64, + /// represents number of base native quantity; greater than 0 + /// e.g. base decimals 6, base lot size 100, base position 10000, then + /// UI position is 1 + // todo: why signed? + pub base_lot_size: i64, + + // todo + /// an always increasing number (except in case of socializing losses), incremented by + /// funding delta, funding delta is difference between book and index price which needs to be paid every day, + /// funding delta is measured per day - per base lots - the larger users position the more funding + /// he pays, funding is always paid in quote + // pub long_funding: I80F48, + // pub short_funding: I80F48, + // todo + /// timestamp when funding was last updated + // pub last_updated: u64, + + // todo + /// This is i64 to keep consistent with the units of contracts, but should always be > 0 + // todo: why signed? + // pub open_interest: i64, + + // todo + /// number of orders generated + pub seq_num: u64, + + // todo + /// in native quote currency + // pub fees_accrued: I80F48, + + // todo + /// liquidity mining + // pub liquidity_mining_info: LiquidityMiningInfo, + + // todo + /// token vault which holds mango tokens to be disbursed as liquidity incentives for this perp market + // pub mngo_vault: Pubkey, + + /// pda bump + pub bump: u8, +} diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index ab89ea1b3..b28e57378 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -845,3 +845,79 @@ impl<'keypair> ClientInstruction for PlaceSerumOrderInstruction<'keypair> { vec![self.owner] } } + +pub struct CreatePerpMarketInstruction<'keypair> { + pub group: Pubkey, + pub mint: Pubkey, + pub admin: &'keypair Keypair, + pub payer: &'keypair Keypair, + pub quote_lot_size: i64, + pub base_lot_size: i64, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for CreatePerpMarketInstruction<'keypair> { + type Accounts = mango_v4::accounts::CreatePerpMarket; + type Instruction = mango_v4::instruction::CreatePerpMarket; + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { + quote_lot_size: self.quote_lot_size, + base_lot_size: self.base_lot_size, + }; + + let oracle = Pubkey::find_program_address( + &[b"StubOracle".as_ref(), self.mint.as_ref()], + &program_id, + ) + .0; + + let perp_market = Pubkey::find_program_address( + &[self.group.as_ref(), b"PerpMarket".as_ref(), oracle.as_ref()], + &program_id, + ) + .0; + let asks = Pubkey::find_program_address( + &[self.group.as_ref(), b"Asks".as_ref(), perp_market.as_ref()], + &program_id, + ) + .0; + let bids = Pubkey::find_program_address( + &[self.group.as_ref(), b"Bids".as_ref(), perp_market.as_ref()], + &program_id, + ) + .0; + let event_queue = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"EventQueue".as_ref(), + perp_market.as_ref(), + ], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + group: self.group, + admin: self.admin.pubkey(), + oracle, + perp_market, + asks, + bids, + event_queue, + payer: self.payer.pubkey(), + system_program: System::id(), + token_program: Token::id(), + rent: sysvar::rent::Rent::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin, self.payer] + } +} diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs new file mode 100644 index 000000000..4d0af6357 --- /dev/null +++ b/programs/mango-v4/tests/test_perp.rs @@ -0,0 +1,153 @@ +#![cfg(feature = "test-bpf")] + +use solana_program::pubkey::Pubkey; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, transport::TransportError}; + +use mango_v4::{instructions::CreatePerpMarket, state::*}; +use program_test::*; + +mod program_test; + +#[tokio::test] +async fn test_perp() -> Result<(), TransportError> { + let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; + let solana = &context.solana.clone(); + + let admin = &Keypair::new(); + let owner = &context.users[0].key; + let payer = &context.users[1].key; + let mint0 = &context.mints[0]; + let mint1 = &context.mints[1]; + let payer_mint_accounts = &context.users[1].token_accounts[0..=2]; + + // + // SETUP: Create a group and an account + // + + let group = send_tx(solana, CreateGroupInstruction { admin, payer }) + .await + .unwrap() + .group; + + let account = send_tx( + solana, + CreateAccountInstruction { + account_num: 0, + group, + owner, + payer, + }, + ) + .await + .unwrap() + .account; + + // + // SETUP: Register mints (and make oracles for them) + // + + let register_mint = |index: TokenIndex, mint: MintCookie, address_lookup_table: Pubkey| async move { + let create_stub_oracle_accounts = send_tx( + solana, + CreateStubOracle { + mint: mint.pubkey, + payer, + }, + ) + .await + .unwrap(); + let oracle = create_stub_oracle_accounts.oracle; + send_tx( + solana, + SetStubOracle { + mint: mint.pubkey, + payer, + price: "1.0", + }, + ) + .await + .unwrap(); + let register_token_accounts = send_tx( + solana, + RegisterTokenInstruction { + token_index: index, + decimals: mint.decimals, + maint_asset_weight: 0.9, + init_asset_weight: 0.8, + maint_liab_weight: 1.1, + init_liab_weight: 1.2, + group, + admin, + mint: mint.pubkey, + address_lookup_table, + payer, + }, + ) + .await + .unwrap(); + let bank = register_token_accounts.bank; + + (oracle, bank) + }; + + let address_lookup_table = solana.create_address_lookup_table(admin, payer).await; + let base_token_index = 0; + let (_oracle0, _bank0) = + register_mint(base_token_index, mint0.clone(), address_lookup_table).await; + let quote_token_index = 1; + let (_oracle1, _bank1) = + register_mint(quote_token_index, mint1.clone(), address_lookup_table).await; + + // + // SETUP: Deposit user funds + // + { + let deposit_amount = 1000; + + send_tx( + solana, + DepositInstruction { + amount: deposit_amount, + account, + token_account: payer_mint_accounts[0], + token_authority: payer, + }, + ) + .await + .unwrap(); + + send_tx( + solana, + DepositInstruction { + amount: deposit_amount, + account, + token_account: payer_mint_accounts[1], + token_authority: payer, + }, + ) + .await + .unwrap(); + } + + // + // TEST: Create a perp market + // + let _perp_market = send_tx( + solana, + CreatePerpMarketInstruction { + group, + admin, + payer, + mint: mint0.pubkey, + // e.g. BTC mango-v3 mainnet.1 + quote_lot_size: 10, + base_lot_size: 100, + }, + ) + .await + .unwrap() + .perp_market; + + Ok(()) +}