diff --git a/keeper/src/mango_client.rs b/keeper/src/mango_client.rs index b9eb1f939..919cd4e95 100644 --- a/keeper/src/mango_client.rs +++ b/keeper/src/mango_client.rs @@ -339,7 +339,7 @@ impl MangoClient { .collect()) } - pub fn deposit( + pub fn token_deposit( &self, token_name: &str, amount: u64, @@ -356,7 +356,7 @@ impl MangoClient { program_id: mango_v4::id(), accounts: { let mut ams = anchor_lang::ToAccountMetas::to_account_metas( - &mango_v4::accounts::Deposit { + &mango_v4::accounts::TokenDeposit { group: self.group(), account: self.mango_account_cache.0, bank: bank.0, @@ -373,7 +373,7 @@ impl MangoClient { ams.extend(health_check_metas.into_iter()); ams }, - data: anchor_lang::InstructionData::data(&mango_v4::instruction::Deposit { + data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit { amount, }), }) diff --git a/keeper/src/taker.rs b/keeper/src/taker.rs index 2b0ab740a..b6a00c961 100644 --- a/keeper/src/taker.rs +++ b/keeper/src/taker.rs @@ -113,7 +113,7 @@ fn ensure_deposit(mango_client: &Arc) -> Result<(), anyhow::Error> } log::info!("Depositing {} {}", deposit_native, bank.name()); - mango_client.deposit(bank.name(), desired_balance.to_num())?; + mango_client.token_deposit(bank.name(), desired_balance.to_num())?; } Ok(()) diff --git a/programs/mango-v4/src/instructions/close_account.rs b/programs/mango-v4/src/instructions/close_account.rs index 36cbc7e6d..e438899eb 100644 --- a/programs/mango-v4/src/instructions/close_account.rs +++ b/programs/mango-v4/src/instructions/close_account.rs @@ -19,8 +19,20 @@ pub struct CloseAccount<'info> { pub token_program: Program<'info, Token>, } -pub fn close_account(_ctx: Context) -> Result<()> { - // CRITICAL: currently can close any account, even one with bad health - // TODO: Implement +pub fn close_account(ctx: Context) -> Result<()> { + let account = ctx.accounts.account.load()?; + require_eq!(account.being_liquidated, 0); + require_eq!(account.delegate, Pubkey::default()); + require_eq!(account.is_bankrupt, 0); + for ele in account.tokens.values { + require_eq!(ele.is_active(), false); + } + for ele in account.serum3.values { + require_eq!(ele.is_active(), false); + } + for ele in account.perps.accounts { + require_eq!(ele.is_active(), false); + } + Ok(()) } diff --git a/programs/mango-v4/src/instructions/close_group.rs b/programs/mango-v4/src/instructions/close_group.rs new file mode 100644 index 000000000..6f676ce0b --- /dev/null +++ b/programs/mango-v4/src/instructions/close_group.rs @@ -0,0 +1,26 @@ +use crate::state::*; +use anchor_lang::prelude::*; +use anchor_spl::token::Token; + +#[derive(Accounts)] +pub struct CloseGroup<'info> { + #[account( + mut, + constraint = group.load()?.testing == 1, + has_one = admin, + close = sol_destination + )] + pub group: AccountLoader<'info, Group>, + + pub admin: Signer<'info>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, +} + +pub fn close_group(_ctx: Context) -> Result<()> { + // TODO: checks + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/close_stub_oracle.rs b/programs/mango-v4/src/instructions/close_stub_oracle.rs new file mode 100644 index 000000000..c3e1d90a7 --- /dev/null +++ b/programs/mango-v4/src/instructions/close_stub_oracle.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Token; + +use crate::state::*; + +#[derive(Accounts)] +pub struct CloseStubOracle<'info> { + #[account( + constraint = group.load()?.testing == 1, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + pub admin: Signer<'info>, + + // match stub oracle to group + #[account( + mut, + has_one = group, + close = sol_destination + )] + pub oracle: AccountLoader<'info, StubOracle>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, +} + +pub fn close_stub_oracle(_ctx: Context) -> Result<()> { + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/create_group.rs b/programs/mango-v4/src/instructions/create_group.rs index 078b33979..24e49ad34 100644 --- a/programs/mango-v4/src/instructions/create_group.rs +++ b/programs/mango-v4/src/instructions/create_group.rs @@ -23,10 +23,11 @@ pub struct CreateGroup<'info> { pub system_program: Program<'info, System>, } -pub fn create_group(ctx: Context, group_num: u32) -> Result<()> { +pub fn create_group(ctx: Context, group_num: u32, testing: u8) -> Result<()> { let mut group = ctx.accounts.group.load_init()?; group.admin = ctx.accounts.admin.key(); group.bump = *ctx.bumps.get("group").ok_or(MangoError::SomeError)?; group.group_num = group_num; + group.testing = testing; Ok(()) } diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 9ff423538..231427528 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -1,53 +1,67 @@ pub use self::margin_trade::*; pub use benchmark::*; pub use close_account::*; +pub use close_group::*; +pub use close_stub_oracle::*; pub use create_account::*; pub use create_group::*; pub use create_stub_oracle::*; -pub use deposit::*; pub use liq_token_with_token::*; pub use perp_cancel_all_orders::*; pub use perp_cancel_all_orders_by_side::*; pub use perp_cancel_order::*; pub use perp_cancel_order_by_client_order_id::*; +pub use perp_close_market::*; pub use perp_consume_events::*; pub use perp_create_market::*; pub use perp_place_order::*; pub use perp_update_funding::*; -pub use register_token::*; +pub use serum3_cancel_all_orders::*; pub use serum3_cancel_order::*; +pub use serum3_close_open_orders::*; pub use serum3_create_open_orders::*; +pub use serum3_deregister_market::*; pub use serum3_liq_force_cancel_orders::*; pub use serum3_place_order::*; pub use serum3_register_market::*; pub use serum3_settle_funds::*; pub use set_stub_oracle::*; +pub use token_deposit::*; +pub use token_deregister::*; +pub use token_register::*; +pub use token_withdraw::*; pub use update_index::*; -pub use withdraw::*; mod benchmark; mod close_account; +mod close_group; +mod close_stub_oracle; mod create_account; mod create_group; mod create_stub_oracle; -mod deposit; mod liq_token_with_token; -pub mod margin_trade; +mod margin_trade; mod perp_cancel_all_orders; mod perp_cancel_all_orders_by_side; mod perp_cancel_order; mod perp_cancel_order_by_client_order_id; +mod perp_close_market; mod perp_consume_events; mod perp_create_market; mod perp_place_order; mod perp_update_funding; -mod register_token; +mod serum3_cancel_all_orders; mod serum3_cancel_order; +mod serum3_close_open_orders; mod serum3_create_open_orders; +mod serum3_deregister_market; mod serum3_liq_force_cancel_orders; mod serum3_place_order; mod serum3_register_market; mod serum3_settle_funds; mod set_stub_oracle; +mod token_deposit; +mod token_deregister; +mod token_register; +mod token_withdraw; mod update_index; -mod withdraw; diff --git a/programs/mango-v4/src/instructions/perp_close_market.rs b/programs/mango-v4/src/instructions/perp_close_market.rs new file mode 100644 index 000000000..5198ab998 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_close_market.rs @@ -0,0 +1,52 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Token; + +use crate::state::*; + +#[derive(Accounts)] +pub struct PerpCloseMarket<'info> { + #[account( + constraint = group.load()?.testing == 1, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + pub admin: Signer<'info>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks, + has_one = event_queue, + close = sol_destination + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + + #[account( + mut, + close = sol_destination + )] + pub bids: AccountLoader<'info, BookSide>, + + #[account( + mut, + close = sol_destination + )] + pub asks: AccountLoader<'info, BookSide>, + + #[account( + mut, + close = sol_destination + )] + pub event_queue: AccountLoader<'info, EventQueue>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, +} + +#[allow(clippy::too_many_arguments)] +pub fn perp_close_market(_ctx: Context) -> Result<()> { + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs new file mode 100644 index 000000000..5e39bda1c --- /dev/null +++ b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs @@ -0,0 +1,86 @@ +use anchor_lang::prelude::*; + +use crate::error::*; +use crate::state::*; + +#[derive(Accounts)] +pub struct Serum3CancelAllOrders<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + pub owner: Signer<'info>, + + // Validated inline + #[account(mut)] + pub open_orders: UncheckedAccount<'info>, + + #[account( + has_one = group, + has_one = serum_program, + has_one = serum_market_external, + )] + pub serum_market: AccountLoader<'info, Serum3Market>, + pub serum_program: UncheckedAccount<'info>, + #[account(mut)] + pub serum_market_external: UncheckedAccount<'info>, + + // These accounts are forwarded directly to the serum cpi call + // and are validated there. + #[account(mut)] + pub market_bids: UncheckedAccount<'info>, + #[account(mut)] + pub market_asks: UncheckedAccount<'info>, + #[account(mut)] + pub market_event_queue: UncheckedAccount<'info>, +} + +pub fn serum3_cancel_all_orders(ctx: Context, limit: u8) -> Result<()> { + // + // Validation + // + { + let account = ctx.accounts.account.load()?; + require!(account.is_bankrupt == 0, MangoError::IsBankrupt); + + let serum_market = ctx.accounts.serum_market.load()?; + + // Validate open_orders + require!( + account + .serum3 + .find(serum_market.market_index) + .ok_or_else(|| error!(MangoError::SomeError))? + .open_orders + == ctx.accounts.open_orders.key(), + MangoError::SomeError + ); + } + + // + // Cancel + // + cpi_cancel_all_orders(ctx.accounts, limit)?; + + Ok(()) +} + +fn cpi_cancel_all_orders(ctx: &Serum3CancelAllOrders, limit: u8) -> Result<()> { + use crate::serum3_cpi; + let group = ctx.group.load()?; + serum3_cpi::CancelOrder { + program: ctx.serum_program.to_account_info(), + market: ctx.serum_market_external.to_account_info(), + bids: ctx.market_bids.to_account_info(), + asks: ctx.market_asks.to_account_info(), + event_queue: ctx.market_event_queue.to_account_info(), + + open_orders: ctx.open_orders.to_account_info(), + open_orders_authority: ctx.group.to_account_info(), + } + .cancel_all(&group, limit) +} diff --git a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs new file mode 100644 index 000000000..bb10e6080 --- /dev/null +++ b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs @@ -0,0 +1,72 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::*; + +#[derive(Accounts)] +pub struct Serum3CloseOpenOrders<'info> { + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = owner, + )] + pub account: AccountLoader<'info, MangoAccount>, + pub owner: Signer<'info>, + + #[account( + has_one = group, + has_one = serum_program, + has_one = serum_market_external, + )] + pub serum_market: AccountLoader<'info, Serum3Market>, + pub serum_program: UncheckedAccount<'info>, + pub serum_market_external: UncheckedAccount<'info>, + + #[account(mut)] + pub open_orders: UncheckedAccount<'info>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, +} + +pub fn serum3_close_open_orders(ctx: Context) -> Result<()> { + // + // Validation + // + let mut account = ctx.accounts.account.load_mut()?; + let serum_market = ctx.accounts.serum_market.load()?; + require!(account.is_bankrupt == 0, MangoError::IsBankrupt); + // Validate open_orders + require!( + account + .serum3 + .find(serum_market.market_index) + .ok_or_else(|| error!(MangoError::SomeError))? + .open_orders + == ctx.accounts.open_orders.key(), + MangoError::SomeError + ); + + // + // close OO + // + cpi_close_open_orders(ctx.accounts)?; + account.serum3.deactivate(serum_market.market_index)?; + + Ok(()) +} + +fn cpi_close_open_orders(ctx: &Serum3CloseOpenOrders) -> Result<()> { + use crate::serum3_cpi; + let group = ctx.group.load()?; + serum3_cpi::CloseOpenOrders { + program: ctx.serum_program.to_account_info(), + market: ctx.serum_market_external.to_account_info(), + open_orders: ctx.open_orders.to_account_info(), + open_orders_authority: ctx.group.to_account_info(), + sol_destination: ctx.sol_destination.to_account_info(), + } + .call(&group) +} diff --git a/programs/mango-v4/src/instructions/serum3_deregister_market.rs b/programs/mango-v4/src/instructions/serum3_deregister_market.rs new file mode 100644 index 000000000..98e0445bc --- /dev/null +++ b/programs/mango-v4/src/instructions/serum3_deregister_market.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Token; + +use crate::state::*; + +#[derive(Accounts)] +pub struct Serum3DeregisterMarket<'info> { + #[account( + mut, + constraint = group.load()?.testing == 1, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + pub admin: Signer<'info>, + + #[account( + mut, + has_one = group, + close = sol_destination + )] + pub serum_market: AccountLoader<'info, Serum3Market>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, +} + +pub fn serum3_deregister_market(_ctx: Context) -> Result<()> { + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs similarity index 95% rename from programs/mango-v4/src/instructions/deposit.rs rename to programs/mango-v4/src/instructions/token_deposit.rs index 251dcef87..70ad3a02f 100644 --- a/programs/mango-v4/src/instructions/deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -8,7 +8,7 @@ use crate::error::*; use crate::state::*; #[derive(Accounts)] -pub struct Deposit<'info> { +pub struct TokenDeposit<'info> { pub group: AccountLoader<'info, Group>, #[account( @@ -36,7 +36,7 @@ pub struct Deposit<'info> { pub token_program: Program<'info, Token>, } -impl<'info> Deposit<'info> { +impl<'info> TokenDeposit<'info> { pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { let program = self.token_program.to_account_info(); let accounts = token::Transfer { @@ -51,7 +51,7 @@ impl<'info> Deposit<'info> { // TODO: It may make sense to have the token_index passed in from the outside. // That would save a lot of computation that needs to go into finding the // right index for the mint. -pub fn deposit(ctx: Context, amount: u64) -> Result<()> { +pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { require!(amount > 0, MangoError::SomeError); let token_index = ctx.accounts.bank.load()?.token_index; diff --git a/programs/mango-v4/src/instructions/token_deregister.rs b/programs/mango-v4/src/instructions/token_deregister.rs new file mode 100644 index 000000000..8af5bdc8c --- /dev/null +++ b/programs/mango-v4/src/instructions/token_deregister.rs @@ -0,0 +1,60 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{self, CloseAccount, Token, TokenAccount}; + +use crate::state::*; + +#[derive(Accounts)] +pub struct TokenDeregister<'info> { + #[account( + constraint = group.load()?.testing == 1, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + pub admin: Signer<'info>, + + // match bank to group + // match bank to vault + #[account( + mut, + has_one = group, + has_one = vault, + close = sol_destination + )] + pub bank: AccountLoader<'info, Bank>, + + #[account(mut)] + pub vault: Account<'info, TokenAccount>, + + // match mint info to bank + #[account( + mut, + has_one = bank, + close = sol_destination + )] + pub mint_info: AccountLoader<'info, MintInfo>, + + #[account(mut)] + pub sol_destination: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, +} + +#[allow(clippy::too_many_arguments)] +pub fn token_deregister(ctx: Context) -> Result<()> { + let group = ctx.accounts.group.load()?; + let group_seeds = group_seeds!(group); + let cpi_accounts = CloseAccount { + account: ctx.accounts.vault.to_account_info(), + destination: ctx.accounts.sol_destination.to_account_info(), + authority: ctx.accounts.group.to_account_info(), + }; + let cpi_program = ctx.accounts.token_program.to_account_info(); + token::close_account(CpiContext::new_with_signer( + cpi_program, + cpi_accounts, + &[group_seeds], + ))?; + ctx.accounts.vault.exit(ctx.program_id)?; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/register_token.rs b/programs/mango-v4/src/instructions/token_register.rs similarity index 98% rename from programs/mango-v4/src/instructions/register_token.rs rename to programs/mango-v4/src/instructions/token_register.rs index f88f15ac4..7243c6bd3 100644 --- a/programs/mango-v4/src/instructions/register_token.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -13,7 +13,7 @@ const INDEX_START: I80F48 = I80F48!(1_000_000); #[derive(Accounts)] #[instruction(token_index: TokenIndex)] -pub struct RegisterToken<'info> { +pub struct TokenRegister<'info> { #[account( has_one = admin, )] @@ -86,8 +86,8 @@ pub struct InterestRateParams { // TODO: should this be "configure_mint", we pass an explicit index, and allow // overwriting config as long as the mint account stays the same? #[allow(clippy::too_many_arguments)] -pub fn register_token( - ctx: Context, +pub fn token_register( + ctx: Context, token_index: TokenIndex, name: String, interest_rate_params: InterestRateParams, diff --git a/programs/mango-v4/src/instructions/withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs similarity index 95% rename from programs/mango-v4/src/instructions/withdraw.rs rename to programs/mango-v4/src/instructions/token_withdraw.rs index 573c5539f..114d54a7c 100644 --- a/programs/mango-v4/src/instructions/withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -7,7 +7,7 @@ use anchor_spl::token::TokenAccount; use fixed::types::I80F48; #[derive(Accounts)] -pub struct Withdraw<'info> { +pub struct TokenWithdraw<'info> { pub group: AccountLoader<'info, Group>, #[account( @@ -36,7 +36,7 @@ pub struct Withdraw<'info> { pub token_program: Program<'info, Token>, } -impl<'info> Withdraw<'info> { +impl<'info> TokenWithdraw<'info> { pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { let program = self.token_program.to_account_info(); let accounts = token::Transfer { @@ -52,7 +52,7 @@ impl<'info> Withdraw<'info> { // That would save a lot of computation that needs to go into finding the // right index for the mint. // TODO: https://github.com/blockworks-foundation/mango-v4/commit/15961ec81c7e9324b37d79d0e2a1650ce6bd981d comments -pub fn withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Result<()> { +pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Result<()> { require!(amount > 0, MangoError::SomeError); let group = ctx.accounts.group.load()?; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index beb43e537..cc19306a0 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -26,13 +26,17 @@ pub mod mango_v4 { use super::*; - pub fn create_group(ctx: Context, group_num: u32) -> Result<()> { - instructions::create_group(ctx, group_num) + pub fn create_group(ctx: Context, group_num: u32, testing: u8) -> Result<()> { + instructions::create_group(ctx, group_num, testing) + } + + pub fn close_group(ctx: Context) -> Result<()> { + instructions::close_group(ctx) } #[allow(clippy::too_many_arguments)] - pub fn register_token( - ctx: Context, + pub fn token_register( + ctx: Context, token_index: TokenIndex, name: String, interest_rate_params: InterestRateParams, @@ -44,7 +48,7 @@ pub mod mango_v4 { init_liab_weight: f32, liquidation_fee: f32, ) -> Result<()> { - instructions::register_token( + instructions::token_register( ctx, token_index, name, @@ -59,6 +63,10 @@ pub mod mango_v4 { ) } + pub fn token_deregister(ctx: Context) -> Result<()> { + instructions::token_deregister(ctx) + } + pub fn update_index(ctx: Context) -> Result<()> { instructions::update_index(ctx) } @@ -86,16 +94,24 @@ pub mod mango_v4 { instructions::create_stub_oracle(ctx, price) } + pub fn close_stub_oracle(ctx: Context) -> Result<()> { + instructions::close_stub_oracle(ctx) + } + pub fn set_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { instructions::set_stub_oracle(ctx, price) } - pub fn deposit(ctx: Context, amount: u64) -> Result<()> { - instructions::deposit(ctx, amount) + pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { + instructions::token_deposit(ctx, amount) } - pub fn withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Result<()> { - instructions::withdraw(ctx, amount, allow_borrow) + pub fn token_withdraw( + ctx: Context, + amount: u64, + allow_borrow: bool, + ) -> Result<()> { + instructions::token_withdraw(ctx, amount, allow_borrow) } pub fn margin_trade<'key, 'accounts, 'remaining, 'info>( @@ -121,12 +137,20 @@ pub mod mango_v4 { instructions::serum3_register_market(ctx, market_index, name) } + pub fn serum3_deregister_market(ctx: Context) -> Result<()> { + instructions::serum3_deregister_market(ctx) + } + // TODO serum3_change_spot_market_params pub fn serum3_create_open_orders(ctx: Context) -> Result<()> { instructions::serum3_create_open_orders(ctx) } + pub fn serum3_close_open_orders(ctx: Context) -> Result<()> { + instructions::serum3_close_open_orders(ctx) + } + #[allow(clippy::too_many_arguments)] pub fn serum3_place_order( ctx: Context, @@ -160,6 +184,10 @@ pub mod mango_v4 { instructions::serum3_cancel_order(ctx, side, order_id) } + pub fn serum3_cancel_all_orders(ctx: Context, limit: u8) -> Result<()> { + instructions::serum3_cancel_all_orders(ctx, limit) + } + pub fn serum3_settle_funds(ctx: Context) -> Result<()> { instructions::serum3_settle_funds(ctx) } @@ -234,6 +262,10 @@ pub mod mango_v4 { ) } + pub fn perp_close_market(ctx: Context) -> Result<()> { + instructions::perp_close_market(ctx) + } + // TODO perp_change_perp_market_params #[allow(clippy::too_many_arguments)] diff --git a/programs/mango-v4/src/serum3_cpi.rs b/programs/mango-v4/src/serum3_cpi.rs index 4f688b3d0..ffb6431f2 100644 --- a/programs/mango-v4/src/serum3_cpi.rs +++ b/programs/mango-v4/src/serum3_cpi.rs @@ -161,6 +161,42 @@ impl<'info> InitOpenOrders<'info> { } } +pub struct CloseOpenOrders<'info> { + pub program: AccountInfo<'info>, + pub market: AccountInfo<'info>, + pub open_orders: AccountInfo<'info>, + pub open_orders_authority: AccountInfo<'info>, + pub sol_destination: AccountInfo<'info>, +} + +impl<'info> CloseOpenOrders<'info> { + pub fn call(self, group: &Group) -> Result<()> { + let data = serum_dex::instruction::MarketInstruction::CloseOpenOrders.pack(); + let instruction = solana_program::instruction::Instruction { + program_id: *self.program.key, + data, + accounts: vec![ + AccountMeta::new(*self.open_orders.key, false), + AccountMeta::new_readonly(*self.open_orders_authority.key, true), + AccountMeta::new(*self.sol_destination.key, false), + AccountMeta::new_readonly(*self.market.key, false), + ], + }; + + let account_infos = [ + self.program, + self.open_orders, + self.open_orders_authority, + self.sol_destination, + self.market, + ]; + + let seeds = group_seeds!(group); + solana_program::program::invoke_signed_unchecked(&instruction, &account_infos, &[seeds])?; + Ok(()) + } +} + pub struct SettleFunds<'info> { pub program: AccountInfo<'info>, pub market: AccountInfo<'info>, diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index a3bfc1164..c02b2e9a3 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -12,7 +12,10 @@ pub struct Group { pub admin: Pubkey, pub bump: u8, - pub padding: [u8; 3], + // Only support closing/deregistering groups, stub oracles, tokens, and markets + // if testing == 1 + pub testing: u8, + pub padding: [u8; 2], pub group_num: u32, pub reserved: [u8; 8], } diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index b216dd015..ac929e799 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -277,8 +277,16 @@ impl MangoAccountSerum3 { } } - pub fn deactivate(&mut self, index: usize) { + pub fn deactivate(&mut self, market_index: Serum3MarketIndex) -> Result<()> { + let index = self + .values + .iter() + .position(|p| p.is_active_for_market(market_index)) + .ok_or(MangoError::SomeError)?; + self.values[index].market_index = Serum3MarketIndex::MAX; + + Ok(()) } pub fn iter_active(&self) -> impl Iterator { diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 058843cca..1b024be9a 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -367,7 +367,7 @@ impl<'keypair> ClientInstruction for MarginTradeInstruction<'keypair> { } } -pub struct WithdrawInstruction<'keypair> { +pub struct TokenWithdrawInstruction<'keypair> { pub amount: u64, pub allow_borrow: bool, @@ -376,9 +376,9 @@ pub struct WithdrawInstruction<'keypair> { pub token_account: Pubkey, } #[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> { - type Accounts = mango_v4::accounts::Withdraw; - type Instruction = mango_v4::instruction::Withdraw; +impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> { + type Accounts = mango_v4::accounts::TokenWithdraw; + type Instruction = mango_v4::instruction::TokenWithdraw; async fn to_instruction( &self, account_loader: impl ClientAccountLoader + 'async_trait, @@ -433,7 +433,7 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> { } } -pub struct DepositInstruction<'keypair> { +pub struct TokenDepositInstruction<'keypair> { pub amount: u64, pub account: Pubkey, @@ -441,9 +441,9 @@ pub struct DepositInstruction<'keypair> { pub token_authority: &'keypair Keypair, } #[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for DepositInstruction<'keypair> { - type Accounts = mango_v4::accounts::Deposit; - type Instruction = mango_v4::instruction::Deposit; +impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> { + type Accounts = mango_v4::accounts::TokenDeposit; + type Instruction = mango_v4::instruction::TokenDeposit; async fn to_instruction( &self, account_loader: impl ClientAccountLoader + 'async_trait, @@ -497,7 +497,7 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> { } } -pub struct RegisterTokenInstruction<'keypair> { +pub struct TokenRegisterInstruction<'keypair> { pub token_index: TokenIndex, pub decimals: u8, pub util0: f32, @@ -520,9 +520,9 @@ pub struct RegisterTokenInstruction<'keypair> { pub payer: &'keypair Keypair, } #[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for RegisterTokenInstruction<'keypair> { - type Accounts = mango_v4::accounts::RegisterToken; - type Instruction = mango_v4::instruction::RegisterToken; +impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> { + type Accounts = mango_v4::accounts::TokenRegister; + type Instruction = mango_v4::instruction::TokenRegister; async fn to_instruction( &self, _account_loader: impl ClientAccountLoader + 'async_trait, @@ -612,7 +612,74 @@ impl<'keypair> ClientInstruction for RegisterTokenInstruction<'keypair> { } } -pub struct SetStubOracle<'keypair> { +pub struct TokenDeregisterInstruction<'keypair> { + pub admin: &'keypair Keypair, + pub payer: &'keypair Keypair, + pub group: Pubkey, + pub mint: Pubkey, + pub token_index: TokenIndex, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for TokenDeregisterInstruction<'keypair> { + type Accounts = mango_v4::accounts::TokenDeregister; + type Instruction = mango_v4::instruction::TokenDeregister; + + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let bank = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"Bank".as_ref(), + &self.token_index.to_le_bytes(), + ], + &program_id, + ) + .0; + let vault = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"Vault".as_ref(), + &self.token_index.to_le_bytes(), + ], + &program_id, + ) + .0; + let mint_info = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"MintInfo".as_ref(), + self.mint.as_ref(), + ], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + admin: self.admin.pubkey(), + group: self.group, + bank, + vault, + mint_info, + sol_destination: self.sol_destination, + token_program: Token::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin] + } +} + +pub struct SetStubOracleInstruction<'keypair> { pub mint: Pubkey, pub group: Pubkey, pub admin: &'keypair Keypair, @@ -620,7 +687,7 @@ pub struct SetStubOracle<'keypair> { pub price: &'static str, } #[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for SetStubOracle<'keypair> { +impl<'keypair> ClientInstruction for SetStubOracleInstruction<'keypair> { type Accounts = mango_v4::accounts::SetStubOracle; type Instruction = mango_v4::instruction::SetStubOracle; @@ -707,6 +774,51 @@ impl<'keypair> ClientInstruction for CreateStubOracle<'keypair> { } } +pub struct CloseStubOracleInstruction<'keypair> { + pub group: Pubkey, + pub mint: Pubkey, + pub admin: &'keypair Keypair, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for CloseStubOracleInstruction<'keypair> { + type Accounts = mango_v4::accounts::CloseStubOracle; + type Instruction = mango_v4::instruction::CloseStubOracle; + + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let oracle = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"StubOracle".as_ref(), + self.mint.as_ref(), + ], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + group: self.group, + admin: self.admin.pubkey(), + oracle, + sol_destination: self.sol_destination, + token_program: Token::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin] + } +} + pub struct CreateGroupInstruction<'keypair> { pub admin: &'keypair Keypair, pub payer: &'keypair Keypair, @@ -720,7 +832,10 @@ impl<'keypair> ClientInstruction for CreateGroupInstruction<'keypair> { _account_loader: impl ClientAccountLoader + 'async_trait, ) -> (Self::Accounts, instruction::Instruction) { let program_id = mango_v4::id(); - let instruction = Self::Instruction { group_num: 0 }; + let instruction = Self::Instruction { + group_num: 0, + testing: 1, + }; let group = Pubkey::find_program_address( &[ @@ -748,6 +863,38 @@ impl<'keypair> ClientInstruction for CreateGroupInstruction<'keypair> { } } +pub struct CloseGroupInstruction<'keypair> { + pub admin: &'keypair Keypair, + pub group: Pubkey, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for CloseGroupInstruction<'keypair> { + type Accounts = mango_v4::accounts::CloseGroup; + type Instruction = mango_v4::instruction::CloseGroup; + async fn to_instruction( + &self, + _account_loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let accounts = Self::Accounts { + group: self.group, + admin: self.admin.pubkey(), + sol_destination: self.sol_destination, + token_program: Token::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin] + } +} + pub struct CreateAccountInstruction<'keypair> { pub account_num: u8, @@ -887,6 +1034,50 @@ impl<'keypair> ClientInstruction for Serum3RegisterMarketInstruction<'keypair> { } } +pub struct Serum3DeregisterMarketInstruction<'keypair> { + pub group: Pubkey, + pub admin: &'keypair Keypair, + pub serum_market_external: Pubkey, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for Serum3DeregisterMarketInstruction<'keypair> { + type Accounts = mango_v4::accounts::Serum3DeregisterMarket; + type Instruction = mango_v4::instruction::Serum3DeregisterMarket; + async fn to_instruction( + &self, + _account_loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let serum_market = Pubkey::find_program_address( + &[ + self.group.as_ref(), + b"Serum3Market".as_ref(), + self.serum_market_external.as_ref(), + ], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + group: self.group, + admin: self.admin.pubkey(), + serum_market, + sol_destination: self.sol_destination, + token_program: Token::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin] + } +} + pub struct Serum3CreateOpenOrdersInstruction<'keypair> { pub account: Pubkey, pub serum_market: Pubkey, @@ -938,6 +1129,55 @@ impl<'keypair> ClientInstruction for Serum3CreateOpenOrdersInstruction<'keypair> } } +pub struct Serum3CloseOpenOrdersInstruction<'keypair> { + pub account: Pubkey, + pub serum_market: Pubkey, + pub owner: &'keypair Keypair, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for Serum3CloseOpenOrdersInstruction<'keypair> { + type Accounts = mango_v4::accounts::Serum3CloseOpenOrders; + type Instruction = mango_v4::instruction::Serum3CloseOpenOrders; + async fn to_instruction( + &self, + account_loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let account: MangoAccount = account_loader.load(&self.account).await.unwrap(); + let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap(); + let open_orders = Pubkey::find_program_address( + &[ + self.account.as_ref(), + b"Serum3OO".as_ref(), + self.serum_market.as_ref(), + ], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + group: account.group, + account: self.account, + serum_market: self.serum_market, + serum_program: serum_market.serum_program, + serum_market_external: serum_market.serum_market_external, + open_orders, + owner: self.owner.pubkey(), + sol_destination: self.sol_destination, + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.owner] + } +} + pub struct Serum3PlaceOrderInstruction<'keypair> { pub side: Serum3Side, pub limit_price: u64, @@ -1115,6 +1355,65 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> { } } +pub struct Serum3CancelAllOrdersInstruction<'keypair> { + pub limit: u8, + pub account: Pubkey, + pub owner: &'keypair Keypair, + pub serum_market: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for Serum3CancelAllOrdersInstruction<'keypair> { + type Accounts = mango_v4::accounts::Serum3CancelAllOrders; + type Instruction = mango_v4::instruction::Serum3CancelAllOrders; + async fn to_instruction( + &self, + account_loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { limit: self.limit }; + + let account: MangoAccount = account_loader.load(&self.account).await.unwrap(); + let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap(); + let open_orders = account + .serum3 + .find(serum_market.market_index) + .unwrap() + .open_orders; + + let market_external_bytes = account_loader + .load_bytes(&serum_market.serum_market_external) + .await + .unwrap(); + let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes( + &market_external_bytes[5..5 + std::mem::size_of::()], + ); + // unpack the data, to avoid unaligned references + let bids = market_external.bids; + let asks = market_external.asks; + let event_q = market_external.event_q; + + let accounts = Self::Accounts { + group: account.group, + account: self.account, + open_orders, + serum_market: self.serum_market, + serum_program: serum_market.serum_program, + serum_market_external: serum_market.serum_market_external, + market_bids: from_serum_style_pubkey(&bids), + market_asks: from_serum_style_pubkey(&asks), + market_event_queue: from_serum_style_pubkey(&event_q), + owner: self.owner.pubkey(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.owner] + } +} + pub struct Serum3SettleFundsInstruction<'keypair> { pub account: Pubkey, pub owner: &'keypair Keypair, @@ -1415,6 +1714,46 @@ impl<'keypair> ClientInstruction for PerpCreateMarketInstruction<'keypair> { } } +pub struct PerpCloseMarketInstruction<'keypair> { + pub group: Pubkey, + pub admin: &'keypair Keypair, + pub perp_market: Pubkey, + pub asks: Pubkey, + pub bids: Pubkey, + pub event_queue: Pubkey, + pub sol_destination: Pubkey, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for PerpCloseMarketInstruction<'keypair> { + type Accounts = mango_v4::accounts::PerpCloseMarket; + type Instruction = mango_v4::instruction::PerpCloseMarket; + async fn to_instruction( + &self, + _loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, instruction::Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction {}; + + let accounts = Self::Accounts { + group: self.group, + admin: self.admin.pubkey(), + perp_market: self.perp_market, + asks: self.asks, + bids: self.bids, + event_queue: self.event_queue, + token_program: Token::id(), + sol_destination: self.sol_destination, + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.admin] + } +} + pub struct PerpPlaceOrderInstruction<'keypair> { pub group: Pubkey, pub account: Pubkey, diff --git a/programs/mango-v4/tests/program_test/mango_setup.rs b/programs/mango-v4/tests/program_test/mango_setup.rs index 5a205733f..cbd42ea7c 100644 --- a/programs/mango-v4/tests/program_test/mango_setup.rs +++ b/programs/mango-v4/tests/program_test/mango_setup.rs @@ -56,7 +56,7 @@ impl<'a> GroupWithTokensConfig<'a> { let oracle = create_stub_oracle_accounts.oracle; send_tx( solana, - SetStubOracle { + SetStubOracleInstruction { group, admin, mint: mint.pubkey, @@ -69,7 +69,7 @@ impl<'a> GroupWithTokensConfig<'a> { let token_index = index as u16; let register_token_accounts = send_tx( solana, - RegisterTokenInstruction { + TokenRegisterInstruction { token_index, decimals: mint.decimals, util0: 0.40, diff --git a/programs/mango-v4/tests/test_basic.rs b/programs/mango-v4/tests/test_basic.rs index e4f8ffbe8..34a76123c 100644 --- a/programs/mango-v4/tests/test_basic.rs +++ b/programs/mango-v4/tests/test_basic.rs @@ -59,7 +59,7 @@ async fn test_basic() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_mint0_account, @@ -94,7 +94,7 @@ async fn test_basic() -> Result<(), TransportError> { send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: withdraw_amount, allow_borrow: true, account, @@ -122,9 +122,25 @@ async fn test_basic() -> Result<(), TransportError> { } // - // TEST: Close account - // TODO: This just checks execution, preconditions etc need to be tested! + // TEST: Close account and de register bank // + + // withdraw whatever is remaining, can't close bank vault without this + let bank_data: Bank = solana.get_account(bank).await; + send_tx( + solana, + TokenWithdrawInstruction { + amount: bank_data.native_total_deposits().to_num(), + allow_borrow: false, + account, + owner, + token_account: payer_mint0_account, + }, + ) + .await + .unwrap(); + + // close account send_tx( solana, CloseAccountInstruction { @@ -136,5 +152,46 @@ async fn test_basic() -> Result<(), TransportError> { .await .unwrap(); + // deregister bank - closes bank, mint info, and bank vault + let bank_data: Bank = solana.get_account(bank).await; + send_tx( + solana, + TokenDeregisterInstruction { + admin, + payer, + group, + mint: bank_data.mint, + token_index: bank_data.token_index, + sol_destination: payer.pubkey(), + }, + ) + .await + .unwrap(); + + // close stub oracle + send_tx( + solana, + CloseStubOracleInstruction { + group, + mint: bank_data.mint, + admin, + sol_destination: payer.pubkey(), + }, + ) + .await + .unwrap(); + + // close group + send_tx( + solana, + CloseGroupInstruction { + group, + admin, + sol_destination: payer.pubkey(), + }, + ) + .await + .unwrap(); + Ok(()) } diff --git a/programs/mango-v4/tests/test_group_address_lookup_tables.rs b/programs/mango-v4/tests/test_group_address_lookup_tables.rs index 3372b65f5..a64d97fa8 100644 --- a/programs/mango-v4/tests/test_group_address_lookup_tables.rs +++ b/programs/mango-v4/tests/test_group_address_lookup_tables.rs @@ -66,7 +66,7 @@ async fn test_group_address_lookup_tables() -> Result<()> { let oracle = create_stub_oracle_accounts.oracle; send_tx( solana, - SetStubOracle { + SetStubOracleInstruction { group, admin, mint: mint.pubkey, @@ -134,7 +134,7 @@ async fn test_group_address_lookup_tables() -> Result<()> { for &payer_token in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_token, @@ -155,7 +155,7 @@ async fn test_group_address_lookup_tables() -> Result<()> { for &payer_token in payer_mint_accounts { send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: withdraw_amount, allow_borrow: true, account, diff --git a/programs/mango-v4/tests/test_health_compute.rs b/programs/mango-v4/tests/test_health_compute.rs index 9edfd04f7..d25fc4b6c 100644 --- a/programs/mango-v4/tests/test_health_compute.rs +++ b/programs/mango-v4/tests/test_health_compute.rs @@ -55,7 +55,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account, @@ -165,7 +165,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 10, account, token_account: payer_mint_accounts[0], @@ -222,7 +222,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> { // Give the account some quote currency send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 1000, account, token_account: payer_mint_accounts[0], @@ -319,7 +319,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 10, account, token_account: payer_mint_accounts[0], diff --git a/programs/mango-v4/tests/test_liq_tokens.rs b/programs/mango-v4/tests/test_liq_tokens.rs index 566b8aca6..e790a476f 100644 --- a/programs/mango-v4/tests/test_liq_tokens.rs +++ b/programs/mango-v4/tests/test_liq_tokens.rs @@ -53,7 +53,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { for &token_account in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 10000, account: vault_account, token_account, @@ -108,7 +108,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { let deposit_amount = 1000; send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_mint_accounts[1], @@ -159,7 +159,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { // send_tx( solana, - SetStubOracle { + SetStubOracleInstruction { group, admin, mint: base_token.mint.pubkey, @@ -173,7 +173,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { // can't withdraw assert!(send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: 1, allow_borrow: false, account, @@ -201,7 +201,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { // can withdraw again send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: 2, allow_borrow: false, account, @@ -258,7 +258,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { for &token_account in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 100000, account: vault_account, token_account, @@ -289,7 +289,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { let deposit2_amount = 20; send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit1_amount, account, token_account: payer_mint_accounts[2], @@ -300,7 +300,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { .unwrap(); send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit2_amount, account, token_account: payer_mint_accounts[3], @@ -314,7 +314,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { let borrow2_amount = 50; send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: borrow1_amount, allow_borrow: true, account, @@ -326,7 +326,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { .unwrap(); send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: borrow2_amount, allow_borrow: true, account, @@ -342,7 +342,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { // send_tx( solana, - SetStubOracle { + SetStubOracleInstruction { group, admin, mint: borrow_token1.mint.pubkey, diff --git a/programs/mango-v4/tests/test_margin_trade.rs b/programs/mango-v4/tests/test_margin_trade.rs index b18974eaf..e454077d5 100644 --- a/programs/mango-v4/tests/test_margin_trade.rs +++ b/programs/mango-v4/tests/test_margin_trade.rs @@ -64,7 +64,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: provided_amount, account: provider_account, token_account: payer_mint0_account, @@ -75,7 +75,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { .unwrap(); send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: provided_amount, account: provider_account, token_account: payer_mint1_account, @@ -111,7 +111,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount_initial, account, token_account: payer_mint0_account, diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index 1668edeb2..58fd3cb06 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -5,7 +5,7 @@ use fixed_macro::types::I80F48; use mango_v4::state::*; use program_test::*; use solana_program_test::*; -use solana_sdk::{signature::Keypair, transport::TransportError}; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; mod program_test; @@ -66,7 +66,7 @@ async fn test_perp() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account: account_0, token_account: payer_mint_accounts[0], @@ -78,7 +78,7 @@ async fn test_perp() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account: account_0, token_account: payer_mint_accounts[1], @@ -94,7 +94,7 @@ async fn test_perp() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account: account_1, token_account: payer_mint_accounts[0], @@ -106,7 +106,7 @@ async fn test_perp() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account: account_1, token_account: payer_mint_accounts[1], @@ -402,6 +402,21 @@ async fn test_perp() -> Result<(), TransportError> { assert_eq!(mango_account_1.perps.accounts[0].base_position_lots, -1); assert_eq!(mango_account_1.perps.accounts[0].quote_position_native, 100); + send_tx( + solana, + PerpCloseMarketInstruction { + group, + admin, + perp_market, + asks, + bids, + event_queue, + sol_destination: payer.pubkey(), + }, + ) + .await + .unwrap(); + Ok(()) } diff --git a/programs/mango-v4/tests/test_position_lifetime.rs b/programs/mango-v4/tests/test_position_lifetime.rs index 892617e51..8eafe963f 100644 --- a/programs/mango-v4/tests/test_position_lifetime.rs +++ b/programs/mango-v4/tests/test_position_lifetime.rs @@ -68,7 +68,7 @@ async fn test_position_lifetime() -> Result<()> { for &payer_token in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: funding_amount, account: funding_account, token_account: payer_token, @@ -91,7 +91,7 @@ async fn test_position_lifetime() -> Result<()> { for &payer_token in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_token, @@ -106,7 +106,7 @@ async fn test_position_lifetime() -> Result<()> { for &payer_token in payer_mint_accounts { send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: u64::MAX, allow_borrow: false, account, @@ -141,7 +141,7 @@ async fn test_position_lifetime() -> Result<()> { let collateral_amount = 1000; send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: collateral_amount, account, token_account: payer_mint_accounts[0], @@ -155,7 +155,7 @@ async fn test_position_lifetime() -> Result<()> { let borrow_amount = 10; send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: borrow_amount, allow_borrow: true, account, @@ -174,7 +174,7 @@ async fn test_position_lifetime() -> Result<()> { { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { // deposit withdraw amount + some more to cover loan origination fees amount: borrow_amount + 2, account, @@ -186,7 +186,7 @@ async fn test_position_lifetime() -> Result<()> { .unwrap(); send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { // withdraw residual amount left amount: u64::MAX, allow_borrow: false, @@ -202,7 +202,7 @@ async fn test_position_lifetime() -> Result<()> { // withdraw the collateral, closing the position send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: collateral_amount, allow_borrow: false, account, diff --git a/programs/mango-v4/tests/test_serum.rs b/programs/mango-v4/tests/test_serum.rs index 5e0d36bed..8c956a5d4 100644 --- a/programs/mango-v4/tests/test_serum.rs +++ b/programs/mango-v4/tests/test_serum.rs @@ -1,7 +1,7 @@ #![cfg(feature = "test-bpf")] use solana_program_test::*; -use solana_sdk::{signature::Keypair, transport::TransportError}; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; use mango_v4::{ instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}, @@ -65,7 +65,7 @@ async fn test_serum() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_mint_accounts[0], @@ -77,7 +77,7 @@ async fn test_serum() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: deposit_amount, account, token_account: payer_mint_accounts[1], @@ -204,5 +204,32 @@ async fn test_serum() -> Result<(), TransportError> { assert_eq!(native0, 1000); assert_eq!(native1, 1000); + // close oo account + // TODO: custom program error: 0x2a TooManyOpenOrders https://github.com/project-serum/serum-dex/blob/master/dex/src/error.rs#L88 + // send_tx( + // solana, + // Serum3CloseOpenOrdersInstruction { + // account, + // serum_market, + // owner, + // sol_destination: payer.pubkey(), + // }, + // ) + // .await + // .unwrap(); + + // deregister serum3 market + send_tx( + solana, + Serum3DeregisterMarketInstruction { + group, + admin, + serum_market_external: serum_market_cookie.market, + sol_destination: payer.pubkey(), + }, + ) + .await + .unwrap(); + Ok(()) } diff --git a/programs/mango-v4/tests/test_update_index.rs b/programs/mango-v4/tests/test_update_index.rs index 39595cb42..282bea933 100644 --- a/programs/mango-v4/tests/test_update_index.rs +++ b/programs/mango-v4/tests/test_update_index.rs @@ -47,7 +47,7 @@ async fn test_update_index() -> Result<(), TransportError> { for &token_account in payer_mint_accounts { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 10000, account: deposit_account, token_account, @@ -73,7 +73,7 @@ async fn test_update_index() -> Result<(), TransportError> { send_tx( solana, - DepositInstruction { + TokenDepositInstruction { amount: 100000, account: withdraw_account, token_account: payer_mint_accounts[1], @@ -85,7 +85,7 @@ async fn test_update_index() -> Result<(), TransportError> { send_tx( solana, - WithdrawInstruction { + TokenWithdrawInstruction { amount: 5000, allow_borrow: true, account: withdraw_account, diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index d3f07de43..a035653e7 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -1,8 +1,6 @@ import { BN } from '@project-serum/anchor'; import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { PublicKey } from '@solana/web3.js'; -import bs58 from 'bs58'; -import { MangoClient } from '../client'; import { I80F48, I80F48Dto } from './I80F48'; export const QUOTE_DECIMALS = 6; @@ -145,30 +143,3 @@ export class MintInfo { public oracle: PublicKey, ) {} } - -export async function getMintInfoForTokenIndex( - client: MangoClient, - groupPk: PublicKey, - tokenIndex: number, -): Promise { - const tokenIndexBuf = Buffer.alloc(2); - tokenIndexBuf.writeUInt16LE(tokenIndex); - return ( - await client.program.account.mintInfo.all([ - { - memcmp: { - bytes: groupPk.toBase58(), - offset: 8, - }, - }, - { - memcmp: { - bytes: bs58.encode(tokenIndexBuf), - offset: 200, - }, - }, - ]) - ).map((tuple) => { - return MintInfo.from(tuple.publicKey, tuple.account); - }); -} diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index c39ab9963..28f8a098a 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -1,6 +1,6 @@ import { PublicKey } from '@solana/web3.js'; import { MangoClient } from '../client'; -import { Bank } from './bank'; +import { Bank, MintInfo } from './bank'; import { PerpMarket } from './perp'; import { Serum3Market } from './serum3'; @@ -16,6 +16,7 @@ export class Group { new Map(), new Map(), new Map(), + new Map(), ); } @@ -26,6 +27,7 @@ export class Group { public banksMap: Map, public serum3MarketsMap: Map, public perpMarketsMap: Map, + public mintInfosMap: Map, ) {} public findBank(tokenIndex: number): Bank | undefined { @@ -36,6 +38,7 @@ export class Group { public async reload(client: MangoClient) { await this.reloadBanks(client); + await this.reloadMintInfos(client); await this.reloadSerum3Markets(client); await this.reloadPerpMarkets(client); } @@ -45,6 +48,28 @@ export class Group { this.banksMap = new Map(banks.map((bank) => [bank.name, bank])); } + public async reloadMintInfos(client: MangoClient) { + const mintInfos = await client.getMintInfosForGroup(this); + this.mintInfosMap = new Map( + mintInfos.map((mintInfo) => { + // console.log( + // Array.from(this.banksMap.values()).find( + // (bank) => bank.mint.toBase58() === mintInfo.mint.toBase58(), + // ), + // ); + return [ + Array.from(this.banksMap.values()).find( + (bank) => bank.mint.toBase58() === mintInfo.mint.toBase58(), + )?.name!, + mintInfo, + ]; + }), + ); + + // console.log(this.banksMap); + // console.log(this.mintInfosMap); + } + public async reloadSerum3Markets(client: MangoClient) { const serum3Markets = await client.serum3GetMarket(this); this.serum3MarketsMap = new Map( diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 58692dcc5..c23972d37 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -24,7 +24,7 @@ import { TransactionSignature, } from '@solana/web3.js'; import bs58 from 'bs58'; -import { Bank, getMintInfoForTokenIndex } from './accounts/bank'; +import { Bank, MintInfo } from './accounts/bank'; import { Group } from './accounts/group'; import { I80F48 } from './accounts/I80F48'; import { MangoAccount } from './accounts/mangoAccount'; @@ -56,10 +56,13 @@ export class MangoClient { // Group - public async createGroup(groupNum: number): Promise { + public async createGroup( + groupNum: number, + testing: boolean, + ): Promise { const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey; return await this.program.methods - .createGroup(groupNum) + .createGroup(groupNum, testing ? 1 : 0) .accounts({ admin: adminPk, payer: adminPk, @@ -67,6 +70,19 @@ export class MangoClient { .rpc(); } + public async closeGroup(group: Group): Promise { + const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey; + return await this.program.methods + .closeGroup() + .accounts({ + group: group.publicKey, + admin: adminPk, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async getGroup(groupPk: PublicKey): Promise { const groupAccount = await this.program.account.group.fetch(groupPk); const group = Group.from(groupPk, groupAccount); @@ -107,7 +123,7 @@ export class MangoClient { // Tokens/Banks - public async registerToken( + public async tokenRegister( group: Group, mintPk: PublicKey, oraclePk: PublicKey, @@ -127,7 +143,7 @@ export class MangoClient { liquidationFee: number, ): Promise { return await this.program.methods - .registerToken( + .tokenRegister( tokenIndex, name, { util0, rate0, util1, rate1, maxRate }, @@ -150,6 +166,27 @@ export class MangoClient { .rpc(); } + public async tokenDeregister( + group: Group, + tokenName: string, + ): Promise { + const bank = group.banksMap.get(tokenName)!; + + const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey; + return await this.program.methods + .tokenDeregister() + .accounts({ + group: group.publicKey, + admin: adminPk, + bank: bank.publicKey, + vault: bank.vault, + mintInfo: group.mintInfosMap.get(bank.name)?.publicKey, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async getBanksForGroup(group: Group): Promise { return ( await this.program.account.bank.all([ @@ -163,6 +200,47 @@ export class MangoClient { ).map((tuple) => Bank.from(tuple.publicKey, tuple.account)); } + public async getMintInfosForGroup(group: Group): Promise { + return ( + await this.program.account.mintInfo.all([ + { + memcmp: { + bytes: group.publicKey.toBase58(), + offset: 8, + }, + }, + ]) + ).map((tuple) => { + return MintInfo.from(tuple.publicKey, tuple.account); + }); + } + + public async getMintInfoForTokenIndex( + group: Group, + tokenIndex: number, + ): Promise { + const tokenIndexBuf = Buffer.alloc(2); + tokenIndexBuf.writeUInt16LE(tokenIndex); + return ( + await this.program.account.mintInfo.all([ + { + memcmp: { + bytes: group.publicKey.toBase58(), + offset: 8, + }, + }, + { + memcmp: { + bytes: bs58.encode(tokenIndexBuf), + offset: 200, + }, + }, + ]) + ).map((tuple) => { + return MintInfo.from(tuple.publicKey, tuple.account); + }); + } + // Stub Oracle public async createStubOracle( @@ -181,6 +259,21 @@ export class MangoClient { .rpc(); } + public async closeStubOracle( + group: Group, + oracle: PublicKey, + ): Promise { + return await this.program.methods + .closeStubOracle() + .accounts({ + group: group.publicKey, + oracle: oracle, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async setStubOracle( group: Group, mintPk: PublicKey, @@ -290,13 +383,12 @@ export class MangoClient { .accounts({ account: mangoAccount.publicKey, owner: (this.program.provider as AnchorProvider).wallet.publicKey, - solDestination: (this.program.provider as AnchorProvider).wallet - .publicKey, + solDestination: mangoAccount.owner, }) .rpc(); } - public async deposit( + public async tokenDeposit( group: Group, mangoAccount: MangoAccount, tokenName: string, @@ -345,7 +437,7 @@ export class MangoClient { await this.buildHealthRemainingAccounts(group, mangoAccount, [bank]); return await this.program.methods - .deposit(toNativeDecimals(amount, bank.mintDecimals)) + .tokenDeposit(toNativeDecimals(amount, bank.mintDecimals)) .accounts({ group: group.publicKey, account: mangoAccount.publicKey, @@ -367,7 +459,7 @@ export class MangoClient { .rpc({ skipPreflight: true }); } - public async withdraw( + public async tokenWithdraw( group: Group, mangoAccount: MangoAccount, tokenName: string, @@ -385,7 +477,7 @@ export class MangoClient { await this.buildHealthRemainingAccounts(group, mangoAccount, [bank]); return await this.program.methods - .withdraw(toNativeDecimals(amount, bank.mintDecimals), allowBorrow) + .tokenWithdraw(toNativeDecimals(amount, bank.mintDecimals), allowBorrow) .accounts({ group: group.publicKey, account: mangoAccount.publicKey, @@ -427,6 +519,23 @@ export class MangoClient { .rpc(); } + public async serum3deregisterMarket( + group: Group, + serum3MarketName: string, + ): Promise { + const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!; + + return await this.program.methods + .serum3DeregisterMarket() + .accounts({ + group: group.publicKey, + serumMarket: serum3Market.publicKey, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async serum3GetMarket( group: Group, baseTokenIndex?: number, @@ -492,6 +601,32 @@ export class MangoClient { .rpc(); } + public async serum3CloseOpenOrders( + group: Group, + mangoAccount: MangoAccount, + serum3MarketName: string, + ): Promise { + const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!; + + let openOrders = mangoAccount.serum3.find( + (account) => account.marketIndex === serum3Market.marketIndex, + )?.openOrders; + + return await this.program.methods + .serum3CloseOpenOrders() + .accounts({ + group: group.publicKey, + account: mangoAccount.publicKey, + serumMarket: serum3Market.publicKey, + serumProgram: serum3Market.serumProgram, + serumMarketExternal: serum3Market.serumMarketExternal, + openOrders, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async serum3PlaceOrder( group: Group, mangoAccount: MangoAccount, @@ -589,6 +724,40 @@ export class MangoClient { .rpc(); } + async serum3CancelAllorders( + group: Group, + mangoAccount: MangoAccount, + serum3ProgramId: PublicKey, + serum3MarketName: string, + limit: number, + ) { + const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!; + + const serum3MarketExternal = await Market.load( + this.program.provider.connection, + serum3Market.serumMarketExternal, + { commitment: this.program.provider.connection.commitment }, + serum3ProgramId, + ); + + return await this.program.methods + .serum3CancelAllOrders(limit) + .accounts({ + group: group.publicKey, + account: mangoAccount.publicKey, + owner: (this.program.provider as AnchorProvider).wallet.publicKey, + openOrders: mangoAccount.findSerum3Account(serum3Market.marketIndex) + ?.openOrders, + serumMarket: serum3Market.publicKey, + serumProgram: serum3ProgramId, + serumMarketExternal: serum3Market.serumMarketExternal, + marketBids: serum3MarketExternal.bidsAddress, + marketAsks: serum3MarketExternal.asksAddress, + marketEventQueue: serum3MarketExternal.decoded.eventQueue, + }) + .rpc(); + } + async serum3SettleFunds( group: Group, mangoAccount: MangoAccount, @@ -788,6 +957,27 @@ export class MangoClient { .rpc(); } + async perpCloseMarket( + group: Group, + perpMarketName: string, + ): Promise { + const perpMarket = group.perpMarketsMap.get(perpMarketName)!; + + return await this.program.methods + .perpCloseMarket() + .accounts({ + group: group.publicKey, + admin: (this.program.provider as AnchorProvider).wallet.publicKey, + perpMarket: perpMarket.publicKey, + asks: perpMarket.asks, + bids: perpMarket.bids, + eventQueue: perpMarket.eventQueue, + solDestination: (this.program.provider as AnchorProvider).wallet + .publicKey, + }) + .rpc(); + } + public async perpGetMarket( group: Group, baseTokenIndex?: number, @@ -999,7 +1189,7 @@ export class MangoClient { const mintInfos = await Promise.all( [...new Set(tokenIndices)].map(async (tokenIndex) => - getMintInfoForTokenIndex(this, group.publicKey, tokenIndex), + this.getMintInfoForTokenIndex(group, tokenIndex), ), ); healthRemainingAccounts.push( diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 04d869ef8..a5bfc62de 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -49,11 +49,41 @@ export type MangoV4 = { { "name": "groupNum", "type": "u32" + }, + { + "name": "testing", + "type": "u8" } ] }, { - "name": "registerToken", + "name": "closeGroup", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "tokenRegister", "accounts": [ { "name": "group", @@ -214,6 +244,47 @@ export type MangoV4 = { } ] }, + { + "name": "tokenDeregister", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "bank", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "mintInfo", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "updateIndex", "accounts": [ @@ -378,6 +449,37 @@ export type MangoV4 = { } ] }, + { + "name": "closeStubOracle", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "oracle", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "setStubOracle", "accounts": [ @@ -412,7 +514,7 @@ export type MangoV4 = { ] }, { - "name": "deposit", + "name": "tokenDeposit", "accounts": [ { "name": "group", @@ -458,7 +560,7 @@ export type MangoV4 = { ] }, { - "name": "withdraw", + "name": "tokenWithdraw", "accounts": [ { "name": "group", @@ -629,6 +731,37 @@ export type MangoV4 = { } ] }, + { + "name": "serum3DeregisterMarket", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "serumMarket", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "serum3CreateOpenOrders", "accounts": [ @@ -704,6 +837,52 @@ export type MangoV4 = { ], "args": [] }, + { + "name": "serum3CloseOpenOrders", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "serumMarket", + "isMut": false, + "isSigner": false + }, + { + "name": "serumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "serumMarketExternal", + "isMut": false, + "isSigner": false + }, + { + "name": "openOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + } + ], + "args": [] + }, { "name": "serum3PlaceOrder", "accounts": [ @@ -911,6 +1090,67 @@ export type MangoV4 = { } ] }, + { + "name": "serum3CancelAllOrders", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "openOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "serumMarket", + "isMut": false, + "isSigner": false + }, + { + "name": "serumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "serumMarketExternal", + "isMut": true, + "isSigner": false + }, + { + "name": "marketBids", + "isMut": true, + "isSigner": false + }, + { + "name": "marketAsks", + "isMut": true, + "isSigner": false + }, + { + "name": "marketEventQueue", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "limit", + "type": "u8" + } + ] + }, { "name": "serum3SettleFunds", "accounts": [ @@ -1270,6 +1510,52 @@ export type MangoV4 = { } ] }, + { + "name": "perpCloseMarket", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false + }, + { + "name": "bids", + "isMut": true, + "isSigner": false + }, + { + "name": "asks", + "isMut": true, + "isSigner": false + }, + { + "name": "eventQueue", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "perpPlaceOrder", "accounts": [ @@ -1763,12 +2049,16 @@ export type MangoV4 = { "name": "bump", "type": "u8" }, + { + "name": "testing", + "type": "u8" + }, { "name": "padding", "type": { "array": [ "u8", - 3 + 2 ] } }, @@ -2948,11 +3238,41 @@ export const IDL: MangoV4 = { { "name": "groupNum", "type": "u32" + }, + { + "name": "testing", + "type": "u8" } ] }, { - "name": "registerToken", + "name": "closeGroup", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "tokenRegister", "accounts": [ { "name": "group", @@ -3113,6 +3433,47 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "tokenDeregister", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "bank", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "mintInfo", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "updateIndex", "accounts": [ @@ -3277,6 +3638,37 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "closeStubOracle", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "oracle", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "setStubOracle", "accounts": [ @@ -3311,7 +3703,7 @@ export const IDL: MangoV4 = { ] }, { - "name": "deposit", + "name": "tokenDeposit", "accounts": [ { "name": "group", @@ -3357,7 +3749,7 @@ export const IDL: MangoV4 = { ] }, { - "name": "withdraw", + "name": "tokenWithdraw", "accounts": [ { "name": "group", @@ -3528,6 +3920,37 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "serum3DeregisterMarket", + "accounts": [ + { + "name": "group", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "serumMarket", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "serum3CreateOpenOrders", "accounts": [ @@ -3603,6 +4026,52 @@ export const IDL: MangoV4 = { ], "args": [] }, + { + "name": "serum3CloseOpenOrders", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "serumMarket", + "isMut": false, + "isSigner": false + }, + { + "name": "serumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "serumMarketExternal", + "isMut": false, + "isSigner": false + }, + { + "name": "openOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + } + ], + "args": [] + }, { "name": "serum3PlaceOrder", "accounts": [ @@ -3810,6 +4279,67 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "serum3CancelAllOrders", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "openOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "serumMarket", + "isMut": false, + "isSigner": false + }, + { + "name": "serumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "serumMarketExternal", + "isMut": true, + "isSigner": false + }, + { + "name": "marketBids", + "isMut": true, + "isSigner": false + }, + { + "name": "marketAsks", + "isMut": true, + "isSigner": false + }, + { + "name": "marketEventQueue", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "limit", + "type": "u8" + } + ] + }, { "name": "serum3SettleFunds", "accounts": [ @@ -4169,6 +4699,52 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "perpCloseMarket", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false + }, + { + "name": "bids", + "isMut": true, + "isSigner": false + }, + { + "name": "asks", + "isMut": true, + "isSigner": false + }, + { + "name": "eventQueue", + "isMut": true, + "isSigner": false + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "perpPlaceOrder", "accounts": [ @@ -4662,12 +5238,16 @@ export const IDL: MangoV4 = { "name": "bump", "type": "u8" }, + { + "name": "testing", + "type": "u8" + }, { "name": "padding", "type": { "array": [ "u8", - 3 + 2 ] } }, diff --git a/ts/client/src/scripts/example1-admin-close.ts b/ts/client/src/scripts/example1-admin-close.ts new file mode 100644 index 000000000..a9bfcd583 --- /dev/null +++ b/ts/client/src/scripts/example1-admin-close.ts @@ -0,0 +1,93 @@ +import { AnchorProvider, Wallet } from '@project-serum/anchor'; +import { Connection, Keypair, PublicKey } from '@solana/web3.js'; +import fs from 'fs'; +import { MangoClient } from '../client'; + +export const DEVNET_MINTS = new Map([ + ['USDC', '8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN'], // use devnet usdc +]); + +async function main() { + const options = AnchorProvider.defaultOptions(); + const connection = new Connection( + 'https://mango.devnet.rpcpool.com', + options, + ); + + const admin = Keypair.fromSecretKey( + Buffer.from( + JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')), + ), + ); + const adminWallet = new Wallet(admin); + console.log(`Admin ${adminWallet.publicKey.toBase58()}`); + const adminProvider = new AnchorProvider(connection, adminWallet, options); + const client = await MangoClient.connect(adminProvider, true); + + const group = await client.getGroupForAdmin(admin.publicKey); + console.log(`Group ${group.publicKey}`); + + let sig; + + // close stub oracle + const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!); + try { + const usdcDevnetOracle = await client.getStubOracle(group, usdcDevnetMint); + let sig = await client.closeStubOracle(group, usdcDevnetOracle.publicKey); + console.log( + `Closed USDC stub oracle, sig https://explorer.solana.com/address/${sig}?cluster=devnet`, + ); + } catch (error) { + console.error(error); + } + + // close all bank + for (const bank of group.banksMap.values()) { + try { + sig = await client.tokenDeregister(group, bank.name); + console.log( + `Removed token ${bank.name}, sig https://explorer.solana.com/address/${sig}?cluster=devnet`, + ); + } catch (error) { + console.error(error); + } + } + + // deregister all serum markets + for (const market of group.serum3MarketsMap.values()) { + try { + sig = await client.serum3deregisterMarket(group, market.name); + console.log( + `Deregistered serum market ${market.name}, sig https://explorer.solana.com/address/${sig}?cluster=devnet`, + ); + } catch (error) { + console.error(error); + } + } + + // close all perp markets + for (const market of group.perpMarketsMap.values()) { + try { + sig = await client.perpCloseMarket(group, market.name); + console.log( + `Closed perp market ${market.name}, sig https://explorer.solana.com/address/${sig}?cluster=devnet`, + ); + } catch (error) { + console.error(error); + } + } + + // finally, close the group + try { + sig = await client.closeGroup(group); + console.log( + `Closed group, sig https://explorer.solana.com/address/${sig}?cluster=devnet`, + ); + } catch (error) { + console.error(error); + } + + process.exit(); +} + +main(); diff --git a/ts/client/src/scripts/example1-admin.ts b/ts/client/src/scripts/example1-admin.ts index 82b808d84..07fd75477 100644 --- a/ts/client/src/scripts/example1-admin.ts +++ b/ts/client/src/scripts/example1-admin.ts @@ -49,7 +49,7 @@ async function main() { // group console.log(`Creating Group...`); try { - await client.createGroup(0); + await client.createGroup(0, true); } catch (error) { console.log(error); } @@ -61,7 +61,7 @@ async function main() { const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!); const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!); try { - await client.registerToken( + await client.tokenRegister( group, btcDevnetMint, btcDevnetOracle, @@ -94,8 +94,9 @@ async function main() { console.log(error); } const usdcDevnetOracle = await client.getStubOracle(group, usdcDevnetMint); + console.log(`...created stub oracle ${usdcDevnetOracle.publicKey}`); try { - await client.registerToken( + await client.tokenRegister( group, usdcDevnetMint, usdcDevnetOracle.publicKey, @@ -122,7 +123,7 @@ async function main() { const solDevnetMint = new PublicKey(DEVNET_MINTS.get('SOL')!); const solDevnetOracle = new PublicKey(DEVNET_ORACLES.get('SOL')!); try { - await client.registerToken( + await client.tokenRegister( group, solDevnetMint, solDevnetOracle, @@ -151,7 +152,7 @@ async function main() { const orcaDevnetMint = new PublicKey(DEVNET_MINTS.get('ORCA')!); const orcaDevnetOracle = new PublicKey(DEVNET_ORACLES.get('ORCA')!); try { - await client.registerToken( + await client.tokenRegister( group, orcaDevnetMint, orcaDevnetOracle, diff --git a/ts/client/src/scripts/example1-user.ts b/ts/client/src/scripts/example1-user.ts index 8a2ff8f28..8ddb03cde 100644 --- a/ts/client/src/scripts/example1-user.ts +++ b/ts/client/src/scripts/example1-user.ts @@ -55,15 +55,15 @@ async function main() { if (true) { // deposit and withdraw console.log(`Depositing...5 USDC`); - await client.deposit(group, mangoAccount, 'USDC', 5); + await client.tokenDeposit(group, mangoAccount, 'USDC', 5); await mangoAccount.reload(client); console.log(`Depositing...0.0005 BTC`); - await client.deposit(group, mangoAccount, 'BTC', 0.0005); + await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005); await mangoAccount.reload(client); console.log(`Withdrawing...1 USDC`); - await client.withdraw(group, mangoAccount, 'USDC', 1, false); + await client.tokenWithdraw(group, mangoAccount, 'USDC', 1, false); await mangoAccount.reload(client); // serum3 @@ -147,9 +147,6 @@ async function main() { console.log(order); } - // console.log(`Close mango account...`); - // await client.closeMangoAccount(mangoAccount); - console.log(`Settling funds...`); await client.serum3SettleFunds( group, @@ -157,6 +154,16 @@ async function main() { DEVNET_SERUM3_PROGRAM_ID, 'BTC/USDC', ); + + // try { + // console.log(`Close OO...`); + // await client.serum3CloseOpenOrders(group, mangoAccount, 'BTC/USDC'); + // } catch (error) { + // console.log(error); + // } + + // console.log(`Close mango account...`); + // await client.closeMangoAccount(mangoAccount); } if (true) { diff --git a/ts/client/src/scripts/scratch/example1-withdraw-max.ts b/ts/client/src/scripts/scratch/example1-withdraw-max.ts index 034c88653..22a265e51 100644 --- a/ts/client/src/scripts/scratch/example1-withdraw-max.ts +++ b/ts/client/src/scripts/scratch/example1-withdraw-max.ts @@ -82,7 +82,13 @@ async function main() { while (true) { try { console.log(`Withdrawing...${amount} 'BTC'`); - await user2Client.withdraw(group, user2MangoAccount, token, amount, true); + await user2Client.tokenWithdraw( + group, + user2MangoAccount, + token, + amount, + true, + ); } catch (error) { console.log(error); break;