create perp market wip

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-03-18 10:08:53 +01:00
parent af2f4a0923
commit 4f7e3a5738
10 changed files with 408 additions and 0 deletions

View File

@ -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::<PerpMarket>(),
)]
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::<PerpMarket>(),
)]
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::<PerpMarket>(),
)]
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::<PerpMarket>(),
)]
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<CreatePerpMarket>,
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(())
}

View File

@ -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;

View File

@ -96,6 +96,14 @@ pub mod mango_v4 {
) -> Result<()> {
instructions::place_serum_order(ctx, order)
}
pub fn create_perp_market(
ctx: Context<CreatePerpMarket>,
quote_lot_size: i64,
base_lot_size: i64,
) -> Result<()> {
instructions::create_perp_market(ctx, quote_lot_size, base_lot_size)
}
}
#[derive(Clone)]

View File

@ -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;

View File

@ -0,0 +1,4 @@
use anchor_lang::prelude::*;
#[account(zero_copy)]
pub struct Book {}

View File

@ -0,0 +1,4 @@
use anchor_lang::prelude::*;
#[account(zero_copy)]
pub struct EventQueue {}

View File

@ -0,0 +1,7 @@
pub use book::*;
pub use event_queue::*;
pub use perp_market::*;
mod book;
mod event_queue;
mod perp_market;

View File

@ -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,
}

View File

@ -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]
}
}

View File

@ -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(())
}