diff --git a/Anchor.toml b/Anchor.toml index efff5fa77..c2df5f8cd 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -12,7 +12,7 @@ url = "https://anchor.projectserum.com" [provider] cluster = "localnet" -wallet = "/home/mc/.config/solana/id.json" +wallet = "~/.config/solana/id.json" [scripts] test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 anchor-tests/*.ts" diff --git a/Cargo.lock b/Cargo.lock index 5d976a2cc..0d665bd12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2919,6 +2919,7 @@ dependencies = [ "fixed", "fixed-macro", "futures 0.3.21", + "itertools 0.10.3", "log 0.4.17", "mango-v4", "pyth-sdk-solana", diff --git a/anchor b/anchor index 2058b6461..b52f23614 160000 --- a/anchor +++ b/anchor @@ -1 +1 @@ -Subproject commit 2058b6461cb0de5af90b04eb8fae4225a368251e +Subproject commit b52f23614601652a99ec6c27aec77bd327363b31 diff --git a/anchor-tests/test.ts b/anchor-tests/test.ts index c8ae192e9..d8bab88e1 100644 --- a/anchor-tests/test.ts +++ b/anchor-tests/test.ts @@ -283,4 +283,8 @@ describe('mango-v4', () => { 1000, ); }); + + it('update index and rate', async () => { + envClient.updateIndexAndRate(group, 'USDC'); + }); }); diff --git a/client/src/client.rs b/client/src/client.rs index 15fcbeb24..8d069d2c8 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -15,7 +15,7 @@ use bincode::Options; use fixed::types::I80F48; use itertools::Itertools; use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; -use mango_v4::state::{AccountSize, Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex}; +use mango_v4::state::{Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex}; use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync; use solana_client::rpc_client::RpcClient; @@ -206,7 +206,10 @@ impl MangoClient { data: anchor_lang::InstructionData::data(&mango_v4::instruction::AccountCreate { account_num, name: mango_account_name.to_owned(), - account_size: AccountSize::Small, + token_count: 8, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, }), }) .signer(owner) diff --git a/keeper/.env.devnet b/keeper/.env.devnet deleted file mode 100644 index ad55d8f2b..000000000 --- a/keeper/.env.devnet +++ /dev/null @@ -1,4 +0,0 @@ -RPC_URL=https://mango.devnet.rpcpool.com -PAYER_KEYPAIR=~/.config/solana/mango-devnet.json -GROUP_FROM_ADMIN_KEYPAIR=~/.config/solana/admin.json -MANGO_ACCOUNT_NAME=Account \ No newline at end of file diff --git a/keeper/.env.mainnet-beta b/keeper/.env.mainnet-beta deleted file mode 100644 index d1e330de3..000000000 --- a/keeper/.env.mainnet-beta +++ /dev/null @@ -1,4 +0,0 @@ -RPC_URL=https://mango.rpcpool.com/ -PAYER_KEYPAIR=~/.config/solana/mango-mainnet.json -GROUP=grouppubkey -MANGO_ACCOUNT_NAME=Account \ No newline at end of file diff --git a/keeper/Cargo.toml b/keeper/Cargo.toml index 73724e89a..8d8090c02 100644 --- a/keeper/Cargo.toml +++ b/keeper/Cargo.toml @@ -17,6 +17,7 @@ env_logger = "0.8.4" fixed = { version = "=1.11.0", features = ["serde", "borsh"] } fixed-macro = "^1.1.1" futures = "0.3.21" +itertools = "0.10.3" log = "0.4.0" mango-v4 = { path = "../programs/mango-v4", features = ["no-entrypoint", "client"] } pyth-sdk-solana = "0.1.0" diff --git a/keeper/src/crank.rs b/keeper/src/crank.rs index 9a1814193..edcd13a4b 100644 --- a/keeper/src/crank.rs +++ b/keeper/src/crank.rs @@ -1,12 +1,14 @@ use std::{sync::Arc, time::Duration}; use crate::MangoClient; +use itertools::Itertools; use anchor_lang::{__private::bytemuck::cast_ref, solana_program}; use client::prettify_client_error; use futures::Future; use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex}; use solana_sdk::{ + compute_budget::ComputeBudgetInstruction, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; @@ -22,7 +24,16 @@ pub async fn runner( .context .tokens .keys() - .map(|&token_index| loop_update_index_and_rate(mango_client.clone(), token_index)) + // TokenUpdateIndexAndRate is known to take max 71k cu + // from cargo test-bpf local tests + .chunks(15) + .into_iter() + .map(|chunk| { + loop_update_index_and_rate( + mango_client.clone(), + chunk.copied().collect::>(), + ) + }) .collect::>(); let handles2 = mango_client @@ -49,57 +60,66 @@ pub async fn runner( Ok(()) } -pub async fn loop_update_index_and_rate(mango_client: Arc, token_index: TokenIndex) { +pub async fn loop_update_index_and_rate( + mango_client: Arc, + token_indices: Vec, +) { let mut interval = time::interval(Duration::from_secs(5)); loop { interval.tick().await; let client = mango_client.clone(); - let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { - let token = client.context.token(token_index); - let banks_for_a_token = token.mint_info.banks(); - let token_name = &token.name; - let oracle = token.mint_info.oracle; + let token_indices_clone = token_indices.clone(); - let sig_result = client - .program() - .request() - .instruction({ - let mut ix = Instruction { - program_id: mango_v4::id(), - accounts: anchor_lang::ToAccountMetas::to_account_metas( - &mango_v4::accounts::TokenUpdateIndexAndRate { - mint_info: token.mint_info_address, - oracle, - instructions: solana_program::sysvar::instructions::id(), - }, - None, - ), - data: anchor_lang::InstructionData::data( - &mango_v4::instruction::TokenUpdateIndexAndRate {}, - ), - }; - let mut banks = banks_for_a_token - .iter() - .map(|bank_pubkey| AccountMeta { - pubkey: *bank_pubkey, - is_signer: false, - is_writable: true, - }) - .collect::>(); - ix.accounts.append(&mut banks); - ix - }) - .send() - .map_err(prettify_client_error); + let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { + let token_names = token_indices_clone + .iter() + .map(|token_index| client.context.token(*token_index).name.to_owned()) + .join(", "); + + let program = client.program(); + let mut req = program.request(); + req = req.instruction(ComputeBudgetInstruction::set_compute_unit_price(1)); + for token_index in token_indices_clone.iter() { + let token = client.context.token(*token_index); + let banks_for_a_token = token.mint_info.banks(); + let oracle = token.mint_info.oracle; + + let mut ix = Instruction { + program_id: mango_v4::id(), + accounts: anchor_lang::ToAccountMetas::to_account_metas( + &mango_v4::accounts::TokenUpdateIndexAndRate { + group: token.mint_info.group, + mint_info: token.mint_info_address, + oracle, + instructions: solana_program::sysvar::instructions::id(), + }, + None, + ), + data: anchor_lang::InstructionData::data( + &mango_v4::instruction::TokenUpdateIndexAndRate {}, + ), + }; + let mut banks = banks_for_a_token + .iter() + .map(|bank_pubkey| AccountMeta { + pubkey: *bank_pubkey, + is_signer: false, + is_writable: true, + }) + .collect::>(); + ix.accounts.append(&mut banks); + req = req.instruction(ix); + } + let sig_result = req.send().map_err(prettify_client_error); if let Err(e) = sig_result { log::error!("{:?}", e) } else { log::info!( "update_index_and_rate {} {:?}", - token_name, + token_names, sig_result.unwrap() ) } @@ -240,6 +260,7 @@ pub async fn loop_update_funding( program_id: mango_v4::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas( &mango_v4::accounts::PerpUpdateFunding { + group: perp_market.group, perp_market: pk, asks: perp_market.asks, bids: perp_market.bids, diff --git a/keeper/src/main.rs b/keeper/src/main.rs index ed232688f..825edf095 100644 --- a/keeper/src/main.rs +++ b/keeper/src/main.rs @@ -76,7 +76,7 @@ fn main() -> Result<(), anyhow::Error> { }; let mango_client = Arc::new(MangoClient::new_for_existing_account( - Client::new(cluster, commitment, &owner, Some(Duration::from_secs(1))), + Client::new(cluster, commitment, &owner, Some(Duration::from_secs(10))), cli.mango_account, owner, )?); diff --git a/liquidator/src/main.rs b/liquidator/src/main.rs index 9cb7f72a9..11a6b9320 100644 --- a/liquidator/src/main.rs +++ b/liquidator/src/main.rs @@ -96,7 +96,7 @@ async fn main() -> anyhow::Result<()> { let rpc_url = cli.rpc_url; let ws_url = rpc_url.replace("https", "wss"); - let rpc_timeout = Duration::from_secs(1); + let rpc_timeout = Duration::from_secs(10); let cluster = Cluster::Custom(rpc_url.clone(), ws_url.clone()); let commitment = CommitmentConfig::processed(); let client = Client::new(cluster.clone(), commitment, &liqor_owner, Some(rpc_timeout)); diff --git a/programs/mango-v4/src/instructions/account_close.rs b/programs/mango-v4/src/instructions/account_close.rs index 20bc3322b..2504922f9 100644 --- a/programs/mango-v4/src/instructions/account_close.rs +++ b/programs/mango-v4/src/instructions/account_close.rs @@ -8,7 +8,12 @@ use crate::state::*; pub struct AccountClose<'info> { pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group, has_one = owner, close = sol_destination)] + #[account( + mut, + has_one = group, + has_one = owner, + close = sol_destination + )] pub account: AccountLoaderDynamic<'info, MangoAccount>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/account_create.rs b/programs/mango-v4/src/instructions/account_create.rs index 7f22ae05f..2bc122213 100644 --- a/programs/mango-v4/src/instructions/account_create.rs +++ b/programs/mango-v4/src/instructions/account_create.rs @@ -5,7 +5,7 @@ use crate::state::*; use crate::util::fill32_from_str; #[derive(Accounts)] -#[instruction(account_num: u32, account_size: AccountSize)] +#[instruction(account_num: u32, token_count: u8, serum3_count: u8, perp_count: u8, perp_oo_count: u8)] pub struct AccountCreate<'info> { pub group: AccountLoader<'info, Group>, @@ -14,7 +14,7 @@ pub struct AccountCreate<'info> { seeds = [group.key().as_ref(), b"MangoAccount".as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()], bump, payer = payer, - space = MangoAccount::space(account_size), + space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?, )] pub account: AccountLoaderDynamic<'info, MangoAccount>, pub owner: Signer<'info>, @@ -28,7 +28,10 @@ pub struct AccountCreate<'info> { pub fn account_create( ctx: Context, account_num: u32, - account_size: AccountSize, + token_count: u8, + serum3_count: u8, + perp_count: u8, + perp_oo_count: u8, name: String, ) -> Result<()> { let mut account = ctx.accounts.account.load_init()?; @@ -47,7 +50,7 @@ pub fn account_create( account.fixed.set_being_liquidated(false); account.fixed.set_bankrupt(false); - account.expand_dynamic_content(account_size)?; + account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?; Ok(()) } diff --git a/programs/mango-v4/src/instructions/account_expand.rs b/programs/mango-v4/src/instructions/account_expand.rs index 950f01835..244ddca05 100644 --- a/programs/mango-v4/src/instructions/account_expand.rs +++ b/programs/mango-v4/src/instructions/account_expand.rs @@ -20,15 +20,14 @@ pub struct AccountExpand<'info> { pub system_program: Program<'info, System>, } -pub fn account_expand(ctx: Context) -> Result<()> { - let account_size = { - let account = ctx.accounts.account.load()?; - account.size() - }; - - require_eq!(account_size, AccountSize::Small); - - let new_space = MangoAccount::space(AccountSize::Large); +pub fn account_expand( + ctx: Context, + token_count: u8, + serum3_count: u8, + perp_count: u8, + perp_oo_count: u8, +) -> Result<()> { + let new_space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?; let new_rent_minimum = Rent::get()?.minimum_balance(new_space); let realloc_account = ctx.accounts.account.as_ref(); @@ -55,7 +54,7 @@ pub fn account_expand(ctx: Context) -> Result<()> { // expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on.... let mut account = ctx.accounts.account.load_mut()?; - account.expand_dynamic_content(AccountSize::Large)?; + account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?; Ok(()) } diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 30d5717b1..f74c0c2ee 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -10,6 +10,7 @@ use crate::state::{ use crate::util::checked_math as cm; use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; +use anchor_lang::Discriminator; use anchor_spl::token::{self, Token, TokenAccount}; use fixed::types::I80F48; @@ -76,19 +77,20 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( require_keys_eq!(bank.group, ctx.accounts.group.key()); require_keys_eq!(bank.vault, *vault_ai.key); + let vault = Account::::try_from(vault_ai)?; let token_account = Account::::try_from(token_account_ai)?; bank.flash_loan_approved_amount = *amount; - bank.flash_loan_vault_initial = token_account.amount; + bank.flash_loan_token_account_initial = token_account.amount; // Transfer the loaned funds if *amount > 0 { // Provide a readable error message in case the vault doesn't have enough tokens - if token_account.amount < *amount { + if vault.amount < *amount { return err!(MangoError::InsufficentBankVaultFunds).with_context(|| { format!( "bank vault {} does not have enough tokens, need {} but have {}", - vault_ai.key, amount, token_account.amount + vault_ai.key, amount, vault.amount ) }); } @@ -141,11 +143,11 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( // must be the FlashLoanEnd instruction require!( - ix.data[0..8] == [178, 170, 2, 78, 240, 23, 190, 178], + ix.data[0..8] == crate::instruction::FlashLoanEnd::discriminator(), MangoError::SomeError ); - // check that the same vaults are passed + // check that the same vaults and token accounts are passed let begin_accounts = &ctx.remaining_accounts[num_loans..]; let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..]; for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) { @@ -180,6 +182,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>, ) -> Result<()> { let mut account = ctx.accounts.account.load_mut()?; + let group = account.fixed.group; require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt); @@ -233,7 +236,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( // The Begin instruction only checks that End ends with the same vault accounts - // but there could be an extra vault account in End, or a different bank could be // used for the same vault. - require_neq!(bank.flash_loan_vault_initial, u64::MAX); + require_neq!(bank.flash_loan_token_account_initial, u64::MAX); // Create the token position now, so we can compute the pre-health with fixed order health accounts let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?; @@ -241,7 +244,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( // Transfer any excess over the inital balance of the token account back // into the vault. Compute the total change in the vault balance. let mut change = -I80F48::from(bank.flash_loan_approved_amount); - if token_account.amount > bank.flash_loan_vault_initial { + if token_account.amount > bank.flash_loan_token_account_initial { let transfer_ctx = CpiContext::new( ctx.accounts.token_program.to_account_info(), token::Transfer { @@ -250,7 +253,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( authority: ctx.accounts.owner.to_account_info(), }, ); - let repay = token_account.amount - bank.flash_loan_vault_initial; + let repay = token_account.amount - bank.flash_loan_token_account_initial; token::transfer(transfer_ctx, repay)?; let repay = I80F48::from(repay); @@ -322,7 +325,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( } bank.flash_loan_approved_amount = 0; - bank.flash_loan_vault_initial = u64::MAX; + bank.flash_loan_token_account_initial = u64::MAX; token_loan_details.push(FlashLoanTokenDetail { token_index: position.token_index, @@ -335,6 +338,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( }); emit!(TokenBalanceLog { + mango_group: group.key(), mango_account: ctx.accounts.account.key(), token_index: bank.token_index as u16, indexed_position: position.indexed_position.to_bits(), @@ -345,6 +349,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( } emit!(FlashLoanLog { + mango_group: group.key(), mango_account: ctx.accounts.account.key(), token_loan_details }); diff --git a/programs/mango-v4/src/instructions/liq_token_with_token.rs b/programs/mango-v4/src/instructions/liq_token_with_token.rs index 72e6d8746..9454b7ef8 100644 --- a/programs/mango-v4/src/instructions/liq_token_with_token.rs +++ b/programs/mango-v4/src/instructions/liq_token_with_token.rs @@ -157,6 +157,7 @@ pub fn liq_token_with_token( ); emit!(LiquidateTokenAndTokenLog { + mango_group: ctx.accounts.group.key(), liqee: ctx.accounts.liqee.key(), liqor: ctx.accounts.liqor.key(), asset_token_index, @@ -170,6 +171,7 @@ pub fn liq_token_with_token( // liqee asset emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.liqee.key(), token_index: asset_token_index, indexed_position: liqee_asset_position_indexed.to_bits(), @@ -179,6 +181,7 @@ pub fn liq_token_with_token( }); // liqee liab emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.liqee.key(), token_index: liab_token_index, indexed_position: liqee_liab_position_indexed.to_bits(), @@ -188,6 +191,7 @@ pub fn liq_token_with_token( }); // liqor asset emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.liqor.key(), token_index: asset_token_index, indexed_position: liqor_asset_position_indexed.to_bits(), @@ -197,6 +201,7 @@ pub fn liq_token_with_token( }); // liqor liab emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.liqor.key(), token_index: liab_token_index, indexed_position: liqor_liab_position_indexed.to_bits(), diff --git a/programs/mango-v4/src/instructions/perp_consume_events.rs b/programs/mango-v4/src/instructions/perp_consume_events.rs index c55f598f4..365786920 100644 --- a/programs/mango-v4/src/instructions/perp_consume_events.rs +++ b/programs/mango-v4/src/instructions/perp_consume_events.rs @@ -61,6 +61,7 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res fill, )?; emit_perp_balances( + ctx.accounts.group.key(), fill.maker, perp_market.perp_market_index as u64, fill.price, @@ -101,6 +102,7 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res fill, )?; emit_perp_balances( + ctx.accounts.group.key(), fill.maker, perp_market.perp_market_index as u64, fill.price, @@ -110,6 +112,7 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res &perp_market, ); emit_perp_balances( + ctx.accounts.group.key(), fill.taker, perp_market.perp_market_index as u64, fill.price, diff --git a/programs/mango-v4/src/instructions/perp_update_funding.rs b/programs/mango-v4/src/instructions/perp_update_funding.rs index 00c7ec96b..265e1ad0e 100644 --- a/programs/mango-v4/src/instructions/perp_update_funding.rs +++ b/programs/mango-v4/src/instructions/perp_update_funding.rs @@ -1,15 +1,18 @@ use anchor_lang::prelude::*; use crate::accounts_zerocopy::*; -use crate::state::{oracle_price, Book, BookSide, PerpMarket}; +use crate::state::{oracle_price, Book, BookSide, Group, PerpMarket}; #[derive(Accounts)] pub struct PerpUpdateFunding<'info> { + pub group: AccountLoader<'info, Group>, // Required for group metadata parsing + #[account( mut, has_one = bids, has_one = asks, has_one = oracle, + constraint = perp_market.load()?.group.key() == group.key(), )] pub perp_market: AccountLoader<'info, PerpMarket>, #[account(mut)] diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index 5f7cc87fd..8835096c1 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -83,6 +83,7 @@ pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::(); emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), token_index, indexed_position: indexed_position.to_bits(), @@ -111,6 +112,7 @@ pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { } emit!(DepositLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), signer: ctx.accounts.token_authority.key(), token_index, diff --git a/programs/mango-v4/src/instructions/token_edit.rs b/programs/mango-v4/src/instructions/token_edit.rs index 183c54121..dccecc8b6 100644 --- a/programs/mango-v4/src/instructions/token_edit.rs +++ b/programs/mango-v4/src/instructions/token_edit.rs @@ -30,6 +30,7 @@ pub fn token_edit( bank_num: u64, oracle_opt: Option, oracle_config_opt: Option, + group_insurance_fund_opt: Option, interest_rate_params_opt: Option, loan_fee_rate_opt: Option, loan_origination_fee_rate_opt: Option, @@ -62,6 +63,10 @@ pub fn token_edit( bank.oracle_config = oracle_config; }; + if let Some(group_insurance_fund) = group_insurance_fund_opt { + mint_info.group_insurance_fund = if group_insurance_fund { 1 } else { 0 }; + }; + // unchanged - // deposit_index // borrow_index @@ -109,7 +114,7 @@ pub fn token_edit( // unchanged - // dust - // flash_loan_vault_initial + // flash_loan_token_account_initial // flash_loan_approved_amount // token_index // bump diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index 26d6931fb..f2bf50a79 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -131,13 +131,13 @@ pub fn token_register( init_liab_weight: I80F48::from_num(init_liab_weight), liquidation_fee: I80F48::from_num(liquidation_fee), dust: I80F48::ZERO, - flash_loan_vault_initial: u64::MAX, + flash_loan_token_account_initial: u64::MAX, flash_loan_approved_amount: 0, token_index, bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?, mint_decimals: ctx.accounts.mint.decimals, bank_num: 0, - reserved: [0; 256], + reserved: [0; 2560], }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); @@ -145,14 +145,14 @@ pub fn token_register( *mint_info = MintInfo { group: ctx.accounts.group.key(), token_index, + group_insurance_fund: 1, padding1: Default::default(), mint: ctx.accounts.mint.key(), banks: Default::default(), vaults: Default::default(), oracle: ctx.accounts.oracle.key(), registration_time: Clock::get()?.unix_timestamp, - group_insurance_fund: 1, - reserved: [0; 255], + reserved: [0; 2560], }; mint_info.banks[0] = ctx.accounts.bank.key(); diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index c5538c1e4..bee8a648e 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -88,28 +88,29 @@ pub fn token_register_trustless( index_last_updated: Clock::get()?.unix_timestamp, bank_rate_last_updated: Clock::get()?.unix_timestamp, avg_utilization: I80F48::ZERO, - adjustment_factor: I80F48::from_num(0.02), + // 10% daily adjustment at 0% or 100% utilization + adjustment_factor: I80F48::from_num(0.004), util0: I80F48::from_num(0.7), rate0: I80F48::from_num(0.1), - util1: I80F48::from_num(0.8), + util1: I80F48::from_num(0.85), rate1: I80F48::from_num(0.2), max_rate: I80F48::from_num(2.0), collected_fees_native: I80F48::ZERO, - loan_origination_fee_rate: I80F48::from_num(0.001), + loan_origination_fee_rate: I80F48::from_num(0.0005), loan_fee_rate: I80F48::from_num(0.005), maint_asset_weight: I80F48::from_num(0), init_asset_weight: I80F48::from_num(0), - maint_liab_weight: I80F48::from_num(1.25), - init_liab_weight: I80F48::from_num(1.5), - liquidation_fee: I80F48::from_num(0.125), + maint_liab_weight: I80F48::from_num(1.4), // 2.5x + init_liab_weight: I80F48::from_num(1.8), // 1.25x + liquidation_fee: I80F48::from_num(0.2), dust: I80F48::ZERO, - flash_loan_vault_initial: u64::MAX, + flash_loan_token_account_initial: u64::MAX, flash_loan_approved_amount: 0, token_index, bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?, mint_decimals: ctx.accounts.mint.decimals, bank_num: 0, - reserved: [0; 256], + reserved: [0; 2560], }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); @@ -117,14 +118,14 @@ pub fn token_register_trustless( *mint_info = MintInfo { group: ctx.accounts.group.key(), token_index, + group_insurance_fund: 0, padding1: Default::default(), mint: ctx.accounts.mint.key(), banks: Default::default(), vaults: Default::default(), oracle: ctx.accounts.oracle.key(), registration_time: Clock::get()?.unix_timestamp, - group_insurance_fund: 0, - reserved: [0; 255], + reserved: [0; 2560], }; mint_info.banks[0] = ctx.accounts.bank.key(); diff --git a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs index 5714024f9..7c773d6b6 100644 --- a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs +++ b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs @@ -5,16 +5,25 @@ use crate::logs::{UpdateIndexLog, UpdateRateLog}; use crate::state::HOUR; use crate::{ accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef}, - state::{oracle_price, Bank, MintInfo}, + state::{oracle_price, Bank, Group, MintInfo}, }; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; +use anchor_lang::Discriminator; use checked_math as cm; use fixed::types::I80F48; +pub mod compute_budget { + use solana_program::declare_id; + declare_id!("ComputeBudget111111111111111111111111111111"); +} + #[derive(Accounts)] pub struct TokenUpdateIndexAndRate<'info> { + pub group: AccountLoader<'info, Group>, // Required for group metadata parsing + #[account( - has_one = oracle + has_one = oracle, + constraint = mint_info.load()?.group.key() == group.key(), )] pub mint_info: AccountLoader<'info, MintInfo>, @@ -42,8 +51,10 @@ pub fn token_update_index_and_rate(ctx: Context) -> Res // for now we just whitelist to other token_update_index_and_rate ix // 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi require!( - ix.program_id == crate::id() - && ix.data[0..8] == [131, 136, 194, 39, 11, 50, 10, 198], // token_update_index_and_rate + (ix.program_id == crate::id() + && ix.data[0..8] + == crate::instruction::TokenUpdateIndexAndRate::discriminator()) + || (ix.program_id == compute_budget::id()), MangoError::SomeError ); diff --git a/programs/mango-v4/src/instructions/token_withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs index 47d989a13..ed95d4b41 100644 --- a/programs/mango-v4/src/instructions/token_withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -125,6 +125,7 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::(); emit!(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), token_index, indexed_position: indexed_position.to_bits(), @@ -151,6 +152,7 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo } emit!(WithdrawLog { + mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), signer: ctx.accounts.owner.key(), token_index, diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index c0f211db5..be25edab5 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -19,16 +19,14 @@ pub mod serum3_cpi; pub mod state; pub mod types; -use state::{ - AccountSize, OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex, -}; +use state::{OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex}; declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD"); #[program] pub mod mango_v4 { - use crate::state::{AccountSize, OracleConfig}; + use crate::state::OracleConfig; use super::*; @@ -98,6 +96,7 @@ pub mod mango_v4 { bank_num: u64, oracle_opt: Option, oracle_config_opt: Option, + group_insurance_fund_opt: Option, interest_rate_params_opt: Option, loan_fee_rate_opt: Option, loan_origination_fee_rate_opt: Option, @@ -112,6 +111,7 @@ pub mod mango_v4 { bank_num, oracle_opt, oracle_config_opt, + group_insurance_fund_opt, interest_rate_params_opt, loan_fee_rate_opt, loan_origination_fee_rate_opt, @@ -146,14 +146,31 @@ pub mod mango_v4 { pub fn account_create( ctx: Context, account_num: u32, - account_size: AccountSize, + token_count: u8, + serum3_count: u8, + perp_count: u8, + perp_oo_count: u8, name: String, ) -> Result<()> { - instructions::account_create(ctx, account_num, account_size, name) + instructions::account_create( + ctx, + account_num, + token_count, + serum3_count, + perp_count, + perp_oo_count, + name, + ) } - pub fn account_expand(ctx: Context) -> Result<()> { - instructions::account_expand(ctx) + pub fn account_expand( + ctx: Context, + token_count: u8, + serum3_count: u8, + perp_count: u8, + perp_oo_count: u8, + ) -> Result<()> { + instructions::account_expand(ctx, token_count, serum3_count, perp_count, perp_oo_count) } pub fn account_edit( diff --git a/programs/mango-v4/src/logs.rs b/programs/mango-v4/src/logs.rs index 04b4f7ae8..a61e5f106 100644 --- a/programs/mango-v4/src/logs.rs +++ b/programs/mango-v4/src/logs.rs @@ -4,6 +4,7 @@ use borsh::BorshSerialize; /// Warning: This function needs 512+ bytes free on the stack pub fn emit_perp_balances( + mango_group: Pubkey, mango_account: Pubkey, market_index: u64, price: i64, @@ -11,6 +12,7 @@ pub fn emit_perp_balances( pm: &PerpMarket, ) { emit!(PerpBalanceLog { + mango_group, mango_account, market_index, base_position: pp.base_position_lots, @@ -25,6 +27,7 @@ pub fn emit_perp_balances( #[event] pub struct PerpBalanceLog { + pub mango_group: Pubkey, pub mango_account: Pubkey, pub market_index: u64, // IDL doesn't support usize pub base_position: i64, @@ -38,6 +41,7 @@ pub struct PerpBalanceLog { #[event] pub struct TokenBalanceLog { + pub mango_group: Pubkey, pub mango_account: Pubkey, pub token_index: u16, // IDL doesn't support usize pub indexed_position: i128, // on client convert i128 to I80F48 easily by passing in the BN to I80F48 ctor @@ -46,14 +50,6 @@ pub struct TokenBalanceLog { pub price: i128, // I80F48 } -#[event] -pub struct MarginTradeLog { - pub mango_account: Pubkey, - pub token_indexes: Vec, - pub pre_indexed_positions: Vec, - pub post_indexed_positions: Vec, -} - #[derive(AnchorSerialize, AnchorDeserialize)] pub struct FlashLoanTokenDetail { pub token_index: u16, @@ -67,12 +63,14 @@ pub struct FlashLoanTokenDetail { #[event] pub struct FlashLoanLog { + pub mango_group: Pubkey, pub mango_account: Pubkey, pub token_loan_details: Vec, } #[event] pub struct WithdrawLog { + pub mango_group: Pubkey, pub mango_account: Pubkey, pub signer: Pubkey, pub token_index: u16, @@ -82,6 +80,7 @@ pub struct WithdrawLog { #[event] pub struct DepositLog { + pub mango_group: Pubkey, pub mango_account: Pubkey, pub signer: Pubkey, pub token_index: u16, @@ -147,6 +146,7 @@ pub struct UpdateRateLog { #[event] pub struct LiquidateTokenAndTokenLog { + pub mango_group: Pubkey, pub liqee: Pubkey, pub liqor: Pubkey, pub asset_token_index: u16, diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index 6277cd9b2..21c926c51 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -82,7 +82,7 @@ pub struct Bank { // Collection of all fractions-of-native-tokens that got rounded away pub dust: I80F48, - pub flash_loan_vault_initial: u64, + pub flash_loan_token_account_initial: u64, pub flash_loan_approved_amount: u64, // Index into TokenInfo on the group @@ -94,11 +94,11 @@ pub struct Bank { pub bank_num: u32, - pub reserved: [u8; 256], + pub reserved: [u8; 2560], } const_assert_eq!( size_of::(), - 32 + 16 + 32 * 3 + 16 + 16 * 6 + 8 * 2 + 16 * 16 + 8 * 2 + 2 + 1 + 1 + 4 + 256 + 32 + 16 + 32 * 3 + 16 + 16 * 6 + 8 * 2 + 16 * 16 + 8 * 2 + 2 + 1 + 1 + 4 + 2560 ); const_assert_eq!(size_of::() % 8, 0); @@ -145,7 +145,10 @@ impl std::fmt::Debug for Bank { "flash_loan_approved_amount", &self.flash_loan_approved_amount, ) - .field("flash_loan_vault_initial", &self.flash_loan_vault_initial) + .field( + "flash_loan_token_account_initial", + &self.flash_loan_token_account_initial, + ) .field("reserved", &self.reserved) .finish() } @@ -185,11 +188,11 @@ impl Bank { liquidation_fee: existing_bank.liquidation_fee, dust: I80F48::ZERO, flash_loan_approved_amount: 0, - flash_loan_vault_initial: u64::MAX, + flash_loan_token_account_initial: u64::MAX, token_index: existing_bank.token_index, bump: existing_bank.bump, mint_decimals: existing_bank.mint_decimals, - reserved: [0; 256], + reserved: [0; 2560], bank_num, } } @@ -501,10 +504,16 @@ impl Bank { // interest rate legs 2 and 3 are seen as punitive legs, encouraging utilization to move towards optimal utilization // lets choose util0 as optimal utilization and 0 to utli0 as the leg where we want the utlization to preferably be let optimal_util = self.util0; - // use avg_utilization and not instantaneous_utilization so that rates cannot be manupulated easily - let util_diff = self.avg_utilization - optimal_util; + // use avg_utilization and not instantaneous_utilization so that rates cannot be manipulated easily + let avg_util = self.avg_utilization; // move rates up when utilization is above optimal utilization, and vice versa - let adjustment = I80F48::ONE + self.adjustment_factor * util_diff; + // util factor is between -1 (avg util = 0) and +1 (avg util = 100%) + let util_factor = if avg_util > optimal_util { + cm!((avg_util - optimal_util) / (I80F48::ONE - optimal_util)) + } else { + cm!((avg_util - optimal_util) / optimal_util) + }; + let adjustment = cm!(I80F48::ONE + self.adjustment_factor * util_factor); // 1. irrespective of which leg current utilization is in, update all rates // 2. only update rates as long as new adjusted rates are above MINIMUM_MAX_RATE, diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index f2091659f..b24e92648 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -33,9 +33,9 @@ pub struct Group { pub padding2: [u8; 5], - pub reserved: [u8; 256], + pub reserved: [u8; 2560], } -const_assert_eq!(size_of::(), 32 * 5 + 4 + 4 + 1 + 1 + 6 + 256); +const_assert_eq!(size_of::(), 32 * 5 + 4 + 4 + 1 + 1 + 6 + 2560); const_assert_eq!(size_of::() % 8, 0); impl Group { diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index c7a3478a0..b3450a995 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -1,13 +1,10 @@ -use std::fmt; - use std::mem::size_of; use anchor_lang::prelude::*; use arrayref::array_ref; use fixed::types::I80F48; -use num_enum::IntoPrimitive; -use num_enum::TryFromPrimitive; + use solana_program::program_memory::sol_memmove; use static_assertions::const_assert_eq; @@ -33,42 +30,6 @@ const BORSH_VEC_PADDING_BYTES: usize = 4; const BORSH_VEC_SIZE_BYTES: usize = 4; const DEFAULT_MANGO_ACCOUNT_VERSION: u8 = 1; -#[derive( - Debug, - Eq, - PartialEq, - Clone, - Copy, - TryFromPrimitive, - IntoPrimitive, - AnchorSerialize, - AnchorDeserialize, -)] -#[repr(u8)] - -pub enum AccountSize { - Small = 0, - Large = 1, -} - -impl fmt::Display for AccountSize { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - AccountSize::Small => write!(f, "Small"), - AccountSize::Large => write!(f, "Large"), - } - } -} - -impl AccountSize { - pub fn space(&self) -> (u8, u8, u8, u8) { - match self { - AccountSize::Small => (8, 2, 2, 2), - AccountSize::Large => (16, 8, 8, 8), - } - } -} - // Mango Account // This struct definition is only for clients e.g. typescript, so that they can easily use out of the box // deserialization and not have to do custom deserialization @@ -158,11 +119,19 @@ impl Default for MangoAccount { } impl MangoAccount { - pub fn space(account_size: AccountSize) -> usize { - let (token_count, serum3_count, perp_count, perp_oo_count) = account_size.space(); + pub fn space( + token_count: u8, + serum3_count: u8, + perp_count: u8, + perp_oo_count: u8, + ) -> Result { + require_gte!(16, token_count); + require_gte!(8, serum3_count); + require_gte!(8, perp_count); + require_gte!(64, perp_oo_count); - 8 + size_of::() - + Self::dynamic_size(token_count, serum3_count, perp_count, perp_oo_count) + Ok(8 + size_of::() + + Self::dynamic_size(token_count, serum3_count, perp_count, perp_oo_count)) } pub fn dynamic_token_vec_offset() -> usize { @@ -202,7 +171,7 @@ impl MangoAccount { #[test] fn test_dynamic_offsets() { let mut account = MangoAccount::default(); - account.tokens.resize(16, TokenPosition::default()); + account.tokens.resize(8, TokenPosition::default()); account.serum3.resize(8, Serum3Orders::default()); account.perps.resize(8, PerpPositions::default()); account @@ -210,7 +179,7 @@ fn test_dynamic_offsets() { .resize(8, PerpOpenOrders::default()); assert_eq!( 8 + AnchorSerialize::try_to_vec(&account).unwrap().len(), - MangoAccount::space(AccountSize::Large) + MangoAccount::space(8, 8, 8, 8).unwrap() ); } @@ -564,13 +533,6 @@ impl< dynamic: self.dynamic(), } } - - pub fn size(&self) -> AccountSize { - if self.header().perp_count() > 4 { - return AccountSize::Large; - } - AccountSize::Small - } } impl< @@ -814,7 +776,7 @@ impl< let side = fill.taker_side.invert_side(); let (base_change, quote_change) = fill.base_quote_change(side); - pa.change_base_position(perp_market, base_change); + pa.change_base_and_entry_positions(perp_market, base_change, quote_change); let quote = I80F48::from_num( perp_market .quote_lot_size @@ -856,7 +818,7 @@ impl< let (base_change, quote_change) = fill.base_quote_change(fill.taker_side); pa.remove_taker_trade(base_change, quote_change); - pa.change_base_position(perp_market, base_change); + pa.change_base_and_entry_positions(perp_market, base_change, quote_change); let quote = I80F48::from_num(perp_market.quote_lot_size * quote_change); // fees are assessed at time of trade; no need to assess fees here @@ -915,14 +877,17 @@ impl< dst.copy_from_slice(&BorshVecLength::from(count).to_le_bytes()); } - pub fn expand_dynamic_content(&mut self, account_size: AccountSize) -> Result<()> { - let (new_token_count, new_serum3_count, new_perp_count, new_perp_oo_count) = - account_size.space(); - - require_gt!(new_token_count, self.header().token_count); - require_gt!(new_serum3_count, self.header().serum3_count); - require_gt!(new_perp_count, self.header().perp_count); - require_gt!(new_perp_oo_count, self.header().perp_oo_count); + pub fn expand_dynamic_content( + &mut self, + new_token_count: u8, + new_serum3_count: u8, + new_perp_count: u8, + new_perp_oo_count: u8, + ) -> Result<()> { + require_gte!(new_token_count, self.header().token_count); + require_gte!(new_serum3_count, self.header().serum3_count); + require_gte!(new_perp_count, self.header().perp_count); + require_gte!(new_perp_oo_count, self.header().perp_oo_count); // create a temp copy to compute new starting offsets let new_header = MangoAccountDynamicHeader { @@ -937,52 +902,63 @@ impl< // expand dynamic components by first moving existing positions, and then setting new ones to defaults // perp oo - unsafe { - sol_memmove( - &mut dynamic[new_header.perp_oo_offset(0)], - &mut dynamic[old_header.perp_oo_offset(0)], - size_of::() * old_header.perp_oo_count(), - ); - } - for i in old_header.perp_oo_count..new_perp_oo_count { - *get_helper_mut(dynamic, new_header.perp_oo_offset(i.into())) = - PerpOpenOrders::default(); + if new_header.perp_oo_count() > old_header.perp_oo_count() { + unsafe { + sol_memmove( + &mut dynamic[new_header.perp_oo_offset(0)], + &mut dynamic[old_header.perp_oo_offset(0)], + size_of::() * old_header.perp_oo_count(), + ); + } + for i in old_header.perp_oo_count..new_perp_oo_count { + *get_helper_mut(dynamic, new_header.perp_oo_offset(i.into())) = + PerpOpenOrders::default(); + } } // perp positions - unsafe { - sol_memmove( - &mut dynamic[new_header.perp_offset(0)], - &mut dynamic[old_header.perp_offset(0)], - size_of::() * old_header.perp_count(), - ); - } - for i in old_header.perp_count..new_perp_count { - *get_helper_mut(dynamic, new_header.perp_offset(i.into())) = PerpPositions::default(); + if new_header.perp_count() > old_header.perp_count() { + unsafe { + sol_memmove( + &mut dynamic[new_header.perp_offset(0)], + &mut dynamic[old_header.perp_offset(0)], + size_of::() * old_header.perp_count(), + ); + } + for i in old_header.perp_count..new_perp_count { + *get_helper_mut(dynamic, new_header.perp_offset(i.into())) = + PerpPositions::default(); + } } // serum3 positions - unsafe { - sol_memmove( - &mut dynamic[new_header.serum3_offset(0)], - &mut dynamic[old_header.serum3_offset(0)], - size_of::() * old_header.serum3_count(), - ); - } - for i in old_header.serum3_count..new_serum3_count { - *get_helper_mut(dynamic, new_header.serum3_offset(i.into())) = Serum3Orders::default(); + if new_header.serum3_count() > old_header.serum3_count() { + unsafe { + sol_memmove( + &mut dynamic[new_header.serum3_offset(0)], + &mut dynamic[old_header.serum3_offset(0)], + size_of::() * old_header.serum3_count(), + ); + } + for i in old_header.serum3_count..new_serum3_count { + *get_helper_mut(dynamic, new_header.serum3_offset(i.into())) = + Serum3Orders::default(); + } } // token positions - unsafe { - sol_memmove( - &mut dynamic[new_header.token_offset(0)], - &mut dynamic[old_header.token_offset(0)], - size_of::() * old_header.token_count(), - ); - } - for i in old_header.token_count..new_token_count { - *get_helper_mut(dynamic, new_header.token_offset(i.into())) = TokenPosition::default(); + if new_header.token_count() > old_header.token_count() { + unsafe { + sol_memmove( + &mut dynamic[new_header.token_offset(0)], + &mut dynamic[old_header.token_offset(0)], + size_of::() * old_header.token_count(), + ); + } + for i in old_header.token_count..new_token_count { + *get_helper_mut(dynamic, new_header.token_offset(i.into())) = + TokenPosition::default(); + } } // update header diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index a53bc6251..0624bdbc1 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -148,6 +148,11 @@ pub struct PerpPositions { /// measured in native quote pub quote_position_native: I80F48, + /// Tracks what the position is to calculate average entry & break even price + pub base_entry_lots: i64, + pub quote_entry_native: i64, + pub quote_exit_native: i64, + /// Already settled funding pub long_settled_funding: I80F48, pub short_settled_funding: I80F48, @@ -180,7 +185,7 @@ impl std::fmt::Debug for PerpPositions { .finish() } } -const_assert_eq!(size_of::(), 8 + 8 * 5 + 3 * 16 + 64); +const_assert_eq!(size_of::(), 8 + 8 * 8 + 3 * 16 + 64); const_assert_eq!(size_of::() % 8, 0); unsafe impl bytemuck::Pod for PerpPositions {} @@ -192,6 +197,9 @@ impl Default for PerpPositions { market_index: PerpMarketIndex::MAX, base_position_lots: 0, quote_position_native: I80F48::ZERO, + base_entry_lots: 0, + quote_entry_native: 0, + quote_exit_native: 0, bids_base_lots: 0, asks_base_lots: 0, taker_base_lots: 0, @@ -257,6 +265,67 @@ impl PerpPositions { self.long_settled_funding = perp_market.long_funding; self.short_settled_funding = perp_market.short_funding; } + + /// Update the quote entry position + pub fn change_quote_entry(&mut self, base_change: i64, quote_change: i64) { + if base_change == 0 { + return; + } + let old_position = self.base_position_lots; + let is_increasing = old_position == 0 || old_position.signum() == base_change.signum(); + match is_increasing { + true => { + self.quote_entry_native = cm!(self.quote_entry_native + quote_change); + self.base_entry_lots = cm!(self.base_entry_lots + base_change); + } + false => { + let new_position = cm!(old_position + base_change); + self.quote_exit_native = cm!(self.quote_exit_native + quote_change); + let is_overflow = old_position.signum() == -new_position.signum(); + if new_position == 0 { + self.quote_entry_native = 0; + self.quote_exit_native = 0; + self.base_entry_lots = 0; + } + if is_overflow { + self.quote_entry_native = cm!(((new_position as f64) * (quote_change as f64) + / (base_change as f64)) + .round()) as i64; + self.quote_exit_native = 0; + self.base_entry_lots = new_position; + } + } + } + } + + /// Change the base and quote positions as the result of a trade + pub fn change_base_and_entry_positions( + &mut self, + perp_market: &mut PerpMarket, + base_change: i64, + quote_change: i64, + ) { + self.change_quote_entry(base_change, quote_change); + self.change_base_position(perp_market, base_change); + } + + /// Calculate the average entry price of the position + pub fn get_avg_entry_price(&self) -> I80F48 { + if self.base_entry_lots == 0 { + return I80F48::ZERO; // TODO: What should this actually return? Error? NaN? + } + (I80F48::from(self.quote_entry_native) / I80F48::from(self.base_entry_lots)).abs() + } + + /// Calculate the break even price of the position + pub fn get_break_even_price(&self) -> I80F48 { + if self.base_position_lots == 0 { + return I80F48::ZERO; // TODO: What should this actually return? Error? NaN? + } + (I80F48::from(self.quote_entry_native + self.quote_exit_native) + / I80F48::from(self.base_position_lots)) + .abs() + } } #[zero_copy] @@ -305,3 +374,227 @@ macro_rules! account_seeds { } pub use account_seeds; + +#[cfg(test)] +mod tests { + use crate::state::{OracleConfig, PerpMarket}; + use anchor_lang::prelude::Pubkey; + use fixed::types::I80F48; + use rand::Rng; + + use super::PerpPositions; + + fn create_perp_position(base_pos: i64, quote_pos: i64, entry_pos: i64) -> PerpPositions { + let mut pos = PerpPositions::default(); + pos.base_position_lots = base_pos; + pos.quote_position_native = I80F48::from(quote_pos); + pos.quote_entry_native = entry_pos; + pos.quote_exit_native = 0; + pos.base_entry_lots = base_pos; + pos + } + + fn create_perp_market() -> PerpMarket { + return PerpMarket { + group: Pubkey::new_unique(), + base_token_index: 0, + perp_market_index: 0, + name: Default::default(), + oracle: Pubkey::new_unique(), + oracle_config: OracleConfig { + conf_filter: I80F48::ZERO, + }, + bids: Pubkey::new_unique(), + asks: Pubkey::new_unique(), + event_queue: Pubkey::new_unique(), + quote_lot_size: 1, + base_lot_size: 1, + maint_asset_weight: I80F48::from(1), + init_asset_weight: I80F48::from(1), + maint_liab_weight: I80F48::from(1), + init_liab_weight: I80F48::from(1), + liquidation_fee: I80F48::ZERO, + maker_fee: I80F48::ZERO, + taker_fee: I80F48::ZERO, + min_funding: I80F48::ZERO, + max_funding: I80F48::ZERO, + impact_quantity: 0, + long_funding: I80F48::ZERO, + short_funding: I80F48::ZERO, + funding_last_updated: 0, + open_interest: 0, + seq_num: 0, + fees_accrued: I80F48::ZERO, + bump: 0, + base_token_decimals: 0, + reserved: [0; 128], + padding1: Default::default(), + padding2: Default::default(), + registration_time: 0, + }; + } + + #[test] + fn test_quote_entry_long_increasing_from_zero() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(0, 0, 0); + // Go long 10 @ 10 + pos.change_base_and_entry_positions(&mut market, 10, -100); + assert_eq!(pos.quote_entry_native, -100); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); + } + + #[test] + fn test_quote_entry_short_increasing_from_zero() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(0, 0, 0); + // Go short 10 @ 10 + pos.change_base_and_entry_positions(&mut market, -10, 100); + assert_eq!(pos.quote_entry_native, 100); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); + } + + #[test] + fn test_quote_entry_long_increasing_from_long() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(10, -100, -100); + // Go long 10 @ 30 + pos.change_base_and_entry_positions(&mut market, 10, -300); + assert_eq!(pos.quote_entry_native, -400); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); + } + + #[test] + fn test_quote_entry_short_increasing_from_short() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(-10, 100, 100); + // Go short 10 @ 10 + pos.change_base_and_entry_positions(&mut market, -10, 300); + assert_eq!(pos.quote_entry_native, 400); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); + } + + #[test] + fn test_quote_entry_long_decreasing_from_short() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(-10, 100, 100); + // Go long 5 @ 50 + pos.change_base_and_entry_positions(&mut market, 5, 250); + assert_eq!(pos.quote_entry_native, 100); + assert_eq!(pos.base_entry_lots, -10); + assert_eq!(pos.quote_exit_native, 250); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing + } + + #[test] + fn test_quote_entry_short_decreasing_from_long() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(10, -100, -100); + // Go short 5 @ 50 + pos.change_base_and_entry_positions(&mut market, -5, -250); + assert_eq!(pos.quote_entry_native, -100); + assert_eq!(pos.base_entry_lots, 10); + assert_eq!(pos.quote_exit_native, -250); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing + } + + #[test] + fn test_quote_entry_long_close_with_short() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(10, -100, -100); + // Go short 10 @ 50 + pos.change_base_and_entry_positions(&mut market, -10, 250); + assert_eq!(pos.quote_entry_native, 0); + assert_eq!(pos.quote_exit_native, 0); + assert_eq!(pos.base_entry_lots, 0); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position + } + + #[test] + fn test_quote_entry_short_close_with_long() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(-10, 100, 100); + // Go long 10 @ 50 + pos.change_base_and_entry_positions(&mut market, 10, -250); + assert_eq!(pos.quote_entry_native, 0); + assert_eq!(pos.quote_exit_native, 0); + assert_eq!(pos.base_entry_lots, 0); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position + } + + #[test] + fn test_quote_entry_long_close_short_with_overflow() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(10, -100, -100); + // Go short 15 @ 20 + pos.change_base_and_entry_positions(&mut market, -15, 300); + assert_eq!(pos.quote_entry_native, 100); + assert_eq!(pos.quote_exit_native, 0); + assert_eq!(pos.base_entry_lots, -5); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position + } + + #[test] + fn test_quote_entry_short_close_long_with_overflow() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(-10, 100, 100); + // Go short 15 @ 20 + pos.change_base_and_entry_positions(&mut market, 15, -300); + assert_eq!(pos.quote_entry_native, -100); + assert_eq!(pos.quote_exit_native, 0); + assert_eq!(pos.base_entry_lots, 5); + assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position + } + + #[test] + fn test_quote_entry_break_even_price() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(0, 0, 0); + // Buy 11 @ 10,000 + pos.change_base_and_entry_positions(&mut market, 11, -11 * 10_000); + // Sell 1 @ 12,000 + pos.change_base_and_entry_positions(&mut market, -1, 12_000); + assert_eq!(pos.quote_entry_native, -11 * 10_000); + assert_eq!(pos.quote_exit_native, 12_000); + assert_eq!(pos.base_entry_lots, 11); + assert_eq!(pos.base_position_lots, 10); + assert_eq!(pos.get_break_even_price(), I80F48::from(9_800)); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each + } + + #[test] + fn test_quote_entry_multiple_and_reversed_changes_return_entry_to_zero() { + let mut market = create_perp_market(); + let mut pos = create_perp_position(0, 0, 0); + + // Generate array of random trades + let mut rng = rand::thread_rng(); + let mut trades: Vec<[i64; 2]> = Vec::with_capacity(500); + for _ in 0..trades.capacity() { + let qty: i64 = rng.gen_range(-1000..=1000); + let px: f64 = rng.gen_range(0.1..=100.0); + let quote: i64 = (-qty as f64 * px).round() as i64; + trades.push([qty, quote]); + } + // Apply all of the trades going forward + trades.iter().for_each(|[qty, quote]| { + pos.change_base_and_entry_positions(&mut market, *qty, *quote); + }); + // base_position should be sum of all base quantities + assert_eq!( + pos.base_position_lots, + trades.iter().map(|[qty, _]| qty).sum::() + ); + // Reverse out all the trades + trades.iter().for_each(|[qty, quote]| { + pos.change_base_and_entry_positions(&mut market, -*qty, -*quote); + }); + // base position should be 0 + assert_eq!(pos.base_position_lots, 0); + // quote entry position should be 0 + assert_eq!(pos.quote_entry_native, 0); + // quote exit should be 0 + assert_eq!(pos.quote_exit_native, 0); + // base entry lots should be 0 + assert_eq!(pos.base_entry_lots, 0); + } +} diff --git a/programs/mango-v4/src/state/mint_info.rs b/programs/mango-v4/src/state/mint_info.rs index 4552f8e65..f741a3d39 100644 --- a/programs/mango-v4/src/state/mint_info.rs +++ b/programs/mango-v4/src/state/mint_info.rs @@ -21,20 +21,20 @@ pub struct MintInfo { // ABI: Clients rely on this being at offset 40 pub token_index: TokenIndex, - pub padding1: [u8; 6], + pub group_insurance_fund: u8, + pub padding1: [u8; 5], pub mint: Pubkey, pub banks: [Pubkey; MAX_BANKS], pub vaults: [Pubkey; MAX_BANKS], pub oracle: Pubkey, pub registration_time: i64, - pub group_insurance_fund: u8, - pub reserved: [u8; 255], + pub reserved: [u8; 2560], } const_assert_eq!( size_of::(), - MAX_BANKS * 2 * 32 + 3 * 32 + 2 + 8 + 6 + 1 + 255 + MAX_BANKS * 2 * 32 + 3 * 32 + 2 + 8 + 6 + 2560 ); const_assert_eq!(size_of::() % 8, 0); diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 34edc5176..367d470b1 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1083,7 +1083,10 @@ impl<'keypair> ClientInstruction for GroupCloseInstruction<'keypair> { pub struct AccountCreateInstruction<'keypair> { pub account_num: u32, - pub account_size: AccountSize, + pub token_count: u8, + pub serum3_count: u8, + pub perp_count: u8, + pub perp_oo_count: u8, pub group: Pubkey, pub owner: &'keypair Keypair, pub payer: &'keypair Keypair, @@ -1099,7 +1102,10 @@ impl<'keypair> ClientInstruction for AccountCreateInstruction<'keypair> { let program_id = mango_v4::id(); let instruction = mango_v4::instruction::AccountCreate { account_num: self.account_num, - account_size: self.account_size, + token_count: self.token_count, + serum3_count: self.serum3_count, + perp_count: self.perp_count, + perp_oo_count: self.perp_oo_count, name: "my_mango_account".to_string(), }; @@ -1136,6 +1142,10 @@ pub struct AccountExpandInstruction<'keypair> { pub group: Pubkey, pub owner: &'keypair Keypair, pub payer: &'keypair Keypair, + pub token_count: u8, + pub serum3_count: u8, + pub perp_count: u8, + pub perp_oo_count: u8, } #[async_trait::async_trait(?Send)] impl<'keypair> ClientInstruction for AccountExpandInstruction<'keypair> { @@ -1146,7 +1156,12 @@ impl<'keypair> ClientInstruction for AccountExpandInstruction<'keypair> { _account_loader: impl ClientAccountLoader + 'async_trait, ) -> (Self::Accounts, instruction::Instruction) { let program_id = mango_v4::id(); - let instruction = mango_v4::instruction::AccountExpand {}; + let instruction = mango_v4::instruction::AccountExpand { + token_count: self.token_count, + serum3_count: self.serum3_count, + perp_count: self.perp_count, + perp_oo_count: self.perp_oo_count, + }; let account = Pubkey::find_program_address( &[ @@ -2374,6 +2389,7 @@ impl ClientInstruction for PerpConsumeEventsInstruction { } pub struct PerpUpdateFundingInstruction { + pub group: Pubkey, pub perp_market: Pubkey, pub bids: Pubkey, pub asks: Pubkey, @@ -2391,6 +2407,7 @@ impl ClientInstruction for PerpUpdateFundingInstruction { let program_id = mango_v4::id(); let instruction = Self::Instruction {}; let accounts = Self::Accounts { + group: self.group, perp_market: self.perp_market, bids: self.bids, asks: self.asks, @@ -2444,6 +2461,7 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction { let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap(); let accounts = Self::Accounts { + group: mint_info.group, mint_info: self.mint_info, oracle: mint_info.oracle, instructions: solana_program::sysvar::instructions::id(), diff --git a/programs/mango-v4/tests/test_bankrupt_tokens.rs b/programs/mango-v4/tests/test_bankrupt_tokens.rs index e347629cb..6250eaaca 100644 --- a/programs/mango-v4/tests/test_bankrupt_tokens.rs +++ b/programs/mango-v4/tests/test_bankrupt_tokens.rs @@ -44,7 +44,10 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 2, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -90,7 +93,10 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -353,7 +359,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 2, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -399,7 +408,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_basic.rs b/programs/mango-v4/tests/test_basic.rs index c6b024888..e6cb9c1de 100644 --- a/programs/mango-v4/tests/test_basic.rs +++ b/programs/mango-v4/tests/test_basic.rs @@ -41,7 +41,10 @@ async fn test_basic() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Small, + token_count: 8, + serum3_count: 0, + perp_count: 0, + perp_oo_count: 0, group, owner, payer, @@ -55,6 +58,10 @@ async fn test_basic() -> Result<(), TransportError> { solana, AccountExpandInstruction { account_num: 0, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_delegate.rs b/programs/mango-v4/tests/test_delegate.rs index 115bcc260..18e669b83 100644 --- a/programs/mango-v4/tests/test_delegate.rs +++ b/programs/mango-v4/tests/test_delegate.rs @@ -37,7 +37,10 @@ async fn test_delegate() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_health_compute.rs b/programs/mango-v4/tests/test_health_compute.rs index bd22f6131..cf338cabc 100644 --- a/programs/mango-v4/tests/test_health_compute.rs +++ b/programs/mango-v4/tests/test_health_compute.rs @@ -37,7 +37,10 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -102,7 +105,10 @@ async fn test_health_compute_serum() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -214,7 +220,10 @@ async fn test_health_compute_perp() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_liq_tokens.rs b/programs/mango-v4/tests/test_liq_tokens.rs index 69a6d1da5..0c2d845a7 100644 --- a/programs/mango-v4/tests/test_liq_tokens.rs +++ b/programs/mango-v4/tests/test_liq_tokens.rs @@ -42,7 +42,10 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 2, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -98,7 +101,10 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -253,7 +259,10 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 2, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -284,7 +293,10 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_margin_trade.rs b/programs/mango-v4/tests/test_margin_trade.rs index 57254b720..a6e3c1c85 100644 --- a/programs/mango-v4/tests/test_margin_trade.rs +++ b/programs/mango-v4/tests/test_margin_trade.rs @@ -51,7 +51,10 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { solana, AccountCreateInstruction { account_num: 1, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -94,7 +97,10 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index d96d58440..64a8c5aef 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -36,7 +36,10 @@ async fn test_perp() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -50,7 +53,10 @@ async fn test_perp() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 1, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_position_lifetime.rs b/programs/mango-v4/tests/test_position_lifetime.rs index 4b341ec49..a8a47bcf6 100644 --- a/programs/mango-v4/tests/test_position_lifetime.rs +++ b/programs/mango-v4/tests/test_position_lifetime.rs @@ -38,7 +38,10 @@ async fn test_position_lifetime() -> Result<()> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -52,7 +55,10 @@ async fn test_position_lifetime() -> Result<()> { solana, AccountCreateInstruction { account_num: 1, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_serum.rs b/programs/mango-v4/tests/test_serum.rs index 0d8f914e4..c460b5807 100644 --- a/programs/mango-v4/tests/test_serum.rs +++ b/programs/mango-v4/tests/test_serum.rs @@ -40,7 +40,10 @@ async fn test_serum() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/programs/mango-v4/tests/test_token_update_index_and_rate.rs b/programs/mango-v4/tests/test_token_update_index_and_rate.rs index f77dc4d48..dda8c7464 100644 --- a/programs/mango-v4/tests/test_token_update_index_and_rate.rs +++ b/programs/mango-v4/tests/test_token_update_index_and_rate.rs @@ -36,7 +36,10 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 0, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, @@ -64,7 +67,10 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> { solana, AccountCreateInstruction { account_num: 1, - account_size: AccountSize::Large, + token_count: 16, + serum3_count: 8, + perp_count: 8, + perp_oo_count: 8, group, owner, payer, diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index 5bf22cfcf..b343f5275 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -63,7 +63,7 @@ export class Bank { initLiabWeight: I80F48Dto; liquidationFee: I80F48Dto; dust: I80F48Dto; - flashLoanVaultInitial: BN; + flashLoanTokenAccountInitial: BN; flashLoanApprovedAmount: BN; tokenIndex: number; mintDecimals: number; @@ -102,7 +102,7 @@ export class Bank { obj.initLiabWeight, obj.liquidationFee, obj.dust, - obj.flashLoanVaultInitial, + obj.flashLoanTokenAccountInitial, obj.flashLoanApprovedAmount, obj.tokenIndex, obj.mintDecimals, @@ -142,7 +142,7 @@ export class Bank { initLiabWeight: I80F48Dto, liquidationFee: I80F48Dto, dust: I80F48Dto, - flashLoanVaultInitial: BN, + flashLoanTokenAccountInitial: BN, flashLoanApprovedAmount: BN, public tokenIndex: number, public mintDecimals: number, diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 7fc0461e4..987666c3d 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -594,8 +594,3 @@ export class EquityDto { tokens: { tokenIndex: number; value: I80F48Dto }[]; perps: { perpMarketIndex: number; value: I80F48Dto }[]; } - -export class AccountSize { - static small = { small: {} }; - static large = { large: {} }; -} diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index eb5564d2f..af37e77c2 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -30,11 +30,7 @@ import bs58 from 'bs58'; import { Bank, MintInfo } from './accounts/bank'; import { Group } from './accounts/group'; import { I80F48 } from './accounts/I80F48'; -import { - AccountSize, - MangoAccount, - MangoAccountData, -} from './accounts/mangoAccount'; +import { MangoAccount, MangoAccountData } from './accounts/mangoAccount'; import { StubOracle } from './accounts/oracle'; import { OrderType, PerpMarket, Side } from './accounts/perp'; import { @@ -236,6 +232,7 @@ export class MangoClient { tokenName: string, oracle: PublicKey, oracleConfFilter: number, + groupInsuranceFund: boolean, adjustmentFactor: number, util0: number, rate0: number, @@ -262,6 +259,7 @@ export class MangoClient { val: I80F48.fromNumber(oracleConfFilter).getData(), }, } as any, // future: nested custom types dont typecheck, fix if possible? + groupInsuranceFund, { adjustmentFactor, util0, rate0, util1, rate1, maxRate }, loanFeeRate, loanOriginationFeeRate, @@ -467,12 +465,11 @@ export class MangoClient { group: Group, ownerPk: PublicKey, accountNumber?: number, - accountSize?: AccountSize, name?: string, ): Promise { let mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk); if (mangoAccounts.length === 0) { - await this.createMangoAccount(group, accountNumber, accountSize, name); + await this.createMangoAccount(group, accountNumber, name); mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk); } return mangoAccounts[0]; @@ -481,15 +478,10 @@ export class MangoClient { public async createMangoAccount( group: Group, accountNumber?: number, - accountSize?: AccountSize, name?: string, ): Promise { return await this.program.methods - .accountCreate( - accountNumber ?? 0, - accountSize ?? AccountSize.small, - name ?? '', - ) + .accountCreate(accountNumber ?? 0, 8, 0, 0, 0, name ?? '') .accounts({ group: group.publicKey, owner: (this.program.provider as AnchorProvider).wallet.publicKey, @@ -501,9 +493,13 @@ export class MangoClient { public async expandMangoAccount( group: Group, account: MangoAccount, + tokenCount: number, + serum3Count: number, + perpCount: number, + perpOoCount: number, ): Promise { return await this.program.methods - .accountExpand() + .accountExpand(tokenCount, serum3Count, perpCount, perpOoCount) .accounts({ group: group.publicKey, account: account.publicKey, @@ -620,7 +616,7 @@ export class MangoClient { mangoAccount: MangoAccount, tokenName: string, amount: number, - ) { + ): Promise { const bank = group.banksMap.get(tokenName)!; const tokenAccountPk = await getAssociatedTokenAddress( @@ -696,7 +692,7 @@ export class MangoClient { tokenName: string, amount: number, allowBorrow: boolean, - ) { + ): Promise { const bank = group.banksMap.get(tokenName)!; const tokenAccountPk = await getAssociatedTokenAddress( @@ -737,7 +733,7 @@ export class MangoClient { tokenName: string, nativeAmount: number, allowBorrow: boolean, - ) { + ): Promise { const bank = group.banksMap.get(tokenName)!; const tokenAccountPk = await getAssociatedTokenAddress( @@ -1559,6 +1555,28 @@ export class MangoClient { return this.program.provider.sendAndConfirm(tx); } + async updateIndexAndRate(group: Group, tokenName: string) { + let bank = group.banksMap.get(tokenName)!; + let mintInfo = group.mintInfosMap.get(bank.tokenIndex)!; + + await this.program.methods + .tokenUpdateIndexAndRate() + .accounts({ + group: group.publicKey, + mintInfo: mintInfo.publicKey, + oracle: mintInfo.oracle, + instructions: SYSVAR_INSTRUCTIONS_PUBKEY, + }) + .remainingAccounts([ + { + pubkey: bank.publicKey, + isWritable: true, + isSigner: false, + } as AccountMeta, + ]) + .rpc(); + } + /// liquidations async liqTokenWithToken( @@ -1688,7 +1706,7 @@ export class MangoClient { } } - const mintInfos = [...new Set(tokenIndices.sort())].map( + const mintInfos = [...new Set(tokenIndices)].map( (tokenIndex) => group.mintInfosMap.get(tokenIndex)!, ); healthRemainingAccounts.push( diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index d9eab7f64..1a44c4fd2 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -504,6 +504,12 @@ export type MangoV4 = { } } }, + { + "name": "groupInsuranceFundOpt", + "type": { + "option": "bool" + } + }, { "name": "interestRateParamsOpt", "type": { @@ -738,6 +744,11 @@ export type MangoV4 = { { "name": "tokenUpdateIndexAndRate", "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, { "name": "mintInfo", "isMut": false, @@ -815,10 +826,20 @@ export type MangoV4 = { "type": "u32" }, { - "name": "accountSize", - "type": { - "defined": "AccountSize" - } + "name": "tokenCount", + "type": "u8" + }, + { + "name": "serum3Count", + "type": "u8" + }, + { + "name": "perpCount", + "type": "u8" + }, + { + "name": "perpOoCount", + "type": "u8" }, { "name": "name", @@ -855,7 +876,24 @@ export type MangoV4 = { "isSigner": false } ], - "args": [] + "args": [ + { + "name": "tokenCount", + "type": "u8" + }, + { + "name": "serum3Count", + "type": "u8" + }, + { + "name": "perpCount", + "type": "u8" + }, + { + "name": "perpOoCount", + "type": "u8" + } + ] }, { "name": "accountEdit", @@ -2572,6 +2610,11 @@ export type MangoV4 = { { "name": "perpUpdateFunding", "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, { "name": "perpMarket", "isMut": true, @@ -2820,7 +2863,7 @@ export type MangoV4 = { } }, { - "name": "flashLoanVaultInitial", + "name": "flashLoanTokenAccountInitial", "type": "u64" }, { @@ -3970,6 +4013,21 @@ export type MangoV4 = { "defined": "I80F48" } }, + { + "name": "baseEntryLots", + "docs": [ + "Tracks what the position is to calculate average entry & break even price" + ], + "type": "i64" + }, + { + "name": "quoteEntryNative", + "type": "i64" + }, + { + "name": "quoteExitNative", + "type": "i64" + }, { "name": "longSettledFunding", "docs": [ @@ -4277,20 +4335,6 @@ export type MangoV4 = { ] } }, - { - "name": "AccountSize", - "type": { - "kind": "enum", - "variants": [ - { - "name": "Small" - }, - { - "name": "Large" - } - ] - } - }, { "name": "OracleType", "type": { @@ -4440,6 +4484,11 @@ export type MangoV4 = { { "name": "PerpBalanceLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -4490,6 +4539,11 @@ export type MangoV4 = { { "name": "TokenBalanceLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -4523,39 +4577,13 @@ export type MangoV4 = { ] }, { - "name": "MarginTradeLog", + "name": "FlashLoanLog", "fields": [ { - "name": "mangoAccount", + "name": "mangoGroup", "type": "publicKey", "index": false }, - { - "name": "tokenIndexes", - "type": { - "vec": "u16" - }, - "index": false - }, - { - "name": "preIndexedPositions", - "type": { - "vec": "i128" - }, - "index": false - }, - { - "name": "postIndexedPositions", - "type": { - "vec": "i128" - }, - "index": false - } - ] - }, - { - "name": "FlashLoanLog", - "fields": [ { "name": "mangoAccount", "type": "publicKey", @@ -4575,6 +4603,11 @@ export type MangoV4 = { { "name": "WithdrawLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -4605,6 +4638,11 @@ export type MangoV4 = { { "name": "DepositLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -4830,6 +4868,11 @@ export type MangoV4 = { { "name": "LiquidateTokenAndTokenLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "liqee", "type": "publicKey", @@ -5508,6 +5551,12 @@ export const IDL: MangoV4 = { } } }, + { + "name": "groupInsuranceFundOpt", + "type": { + "option": "bool" + } + }, { "name": "interestRateParamsOpt", "type": { @@ -5742,6 +5791,11 @@ export const IDL: MangoV4 = { { "name": "tokenUpdateIndexAndRate", "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, { "name": "mintInfo", "isMut": false, @@ -5819,10 +5873,20 @@ export const IDL: MangoV4 = { "type": "u32" }, { - "name": "accountSize", - "type": { - "defined": "AccountSize" - } + "name": "tokenCount", + "type": "u8" + }, + { + "name": "serum3Count", + "type": "u8" + }, + { + "name": "perpCount", + "type": "u8" + }, + { + "name": "perpOoCount", + "type": "u8" }, { "name": "name", @@ -5859,7 +5923,24 @@ export const IDL: MangoV4 = { "isSigner": false } ], - "args": [] + "args": [ + { + "name": "tokenCount", + "type": "u8" + }, + { + "name": "serum3Count", + "type": "u8" + }, + { + "name": "perpCount", + "type": "u8" + }, + { + "name": "perpOoCount", + "type": "u8" + } + ] }, { "name": "accountEdit", @@ -7576,6 +7657,11 @@ export const IDL: MangoV4 = { { "name": "perpUpdateFunding", "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, { "name": "perpMarket", "isMut": true, @@ -7824,7 +7910,7 @@ export const IDL: MangoV4 = { } }, { - "name": "flashLoanVaultInitial", + "name": "flashLoanTokenAccountInitial", "type": "u64" }, { @@ -8974,6 +9060,21 @@ export const IDL: MangoV4 = { "defined": "I80F48" } }, + { + "name": "baseEntryLots", + "docs": [ + "Tracks what the position is to calculate average entry & break even price" + ], + "type": "i64" + }, + { + "name": "quoteEntryNative", + "type": "i64" + }, + { + "name": "quoteExitNative", + "type": "i64" + }, { "name": "longSettledFunding", "docs": [ @@ -9281,20 +9382,6 @@ export const IDL: MangoV4 = { ] } }, - { - "name": "AccountSize", - "type": { - "kind": "enum", - "variants": [ - { - "name": "Small" - }, - { - "name": "Large" - } - ] - } - }, { "name": "OracleType", "type": { @@ -9444,6 +9531,11 @@ export const IDL: MangoV4 = { { "name": "PerpBalanceLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -9494,6 +9586,11 @@ export const IDL: MangoV4 = { { "name": "TokenBalanceLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -9527,39 +9624,13 @@ export const IDL: MangoV4 = { ] }, { - "name": "MarginTradeLog", + "name": "FlashLoanLog", "fields": [ { - "name": "mangoAccount", + "name": "mangoGroup", "type": "publicKey", "index": false }, - { - "name": "tokenIndexes", - "type": { - "vec": "u16" - }, - "index": false - }, - { - "name": "preIndexedPositions", - "type": { - "vec": "i128" - }, - "index": false - }, - { - "name": "postIndexedPositions", - "type": { - "vec": "i128" - }, - "index": false - } - ] - }, - { - "name": "FlashLoanLog", - "fields": [ { "name": "mangoAccount", "type": "publicKey", @@ -9579,6 +9650,11 @@ export const IDL: MangoV4 = { { "name": "WithdrawLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -9609,6 +9685,11 @@ export const IDL: MangoV4 = { { "name": "DepositLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "mangoAccount", "type": "publicKey", @@ -9834,6 +9915,11 @@ export const IDL: MangoV4 = { { "name": "LiquidateTokenAndTokenLog", "fields": [ + { + "name": "mangoGroup", + "type": "publicKey", + "index": false + }, { "name": "liqee", "type": "publicKey", diff --git a/ts/client/src/scripts/example1-admin.ts b/ts/client/src/scripts/example1-admin.ts index 2855a3cee..dea291ad7 100644 --- a/ts/client/src/scripts/example1-admin.ts +++ b/ts/client/src/scripts/example1-admin.ts @@ -200,7 +200,12 @@ async function main() { console.log( `Editing group, setting existing admin as fastListingAdmin to be able to add MNGO truslessly...`, ); - await client.groupEdit(group, group.admin, group.admin); + let sig = await client.groupEdit( + group, + group.admin, + new PublicKey('Efhak3qj3MiyzgJr3cUUqXXz5wr3oYHt9sPzuqJf9eBN'), + ); + console.log(`sig https://explorer.solana.com/tx/${sig}?cluster=devnet`); console.log(`Registering MNGO...`); const mngoDevnetMint = new PublicKey(DEVNET_MINTS.get('MNGO')!); const mngoDevnetOracle = new PublicKey(DEVNET_ORACLES.get('MNGO')!); @@ -296,6 +301,7 @@ async function main() { 'USDC', btcDevnetOracle, 0.1, + undefined, 0.01, 0.3, 0.08, @@ -323,6 +329,7 @@ async function main() { 'USDC', usdcDevnetOracle.publicKey, 0.1, + undefined, 0.01, 0.4, 0.07, diff --git a/ts/client/src/scripts/example1-user-close-account.ts b/ts/client/src/scripts/example1-user-close-account.ts index 6486cdc44..ea875c271 100644 --- a/ts/client/src/scripts/example1-user-close-account.ts +++ b/ts/client/src/scripts/example1-user-close-account.ts @@ -107,9 +107,8 @@ async function main() { group, mangoAccount, group.findBank(token.tokenIndex)!.name, - nativeFlooredNumber, + nativeFlooredNumber - 1 /* see comment in token_withdraw in program */, false, - user, ); } diff --git a/ts/client/src/scripts/example1-user.ts b/ts/client/src/scripts/example1-user.ts index 887093f40..1cfdfd759 100644 --- a/ts/client/src/scripts/example1-user.ts +++ b/ts/client/src/scripts/example1-user.ts @@ -92,6 +92,14 @@ async function main() { console.log(mangoAccount.toString()); } + if (true) { + console.log( + `...expanding mango account to have serum3 and perp position slots`, + ); + await client.expandMangoAccount(group, mangoAccount, 16, 8, 8, 8); + await mangoAccount.reload(client, group); + } + if (true) { // deposit and withdraw