diff --git a/programs/mango-v4/Cargo.toml b/programs/mango-v4/Cargo.toml index 50f933b25..cff3fa52f 100644 --- a/programs/mango-v4/Cargo.toml +++ b/programs/mango-v4/Cargo.toml @@ -41,7 +41,6 @@ switchboard-program = ">=0.2.0" switchboard-utils = ">=0.1.36" switchboard-v2 = "0.1.10" - [dev-dependencies] solana-sdk = { version = "~1.9.13", default-features = false } solana-program-test = "~1.9.13" diff --git a/programs/mango-v4/src/instructions/compute_health.rs b/programs/mango-v4/src/instructions/compute_health.rs index eb6082961..05e7e8567 100644 --- a/programs/mango-v4/src/instructions/compute_health.rs +++ b/programs/mango-v4/src/instructions/compute_health.rs @@ -14,7 +14,8 @@ pub struct ComputeHealth<'info> { pub fn compute_health(ctx: Context, health_type: HealthType) -> Result { let account = ctx.accounts.account.load()?; - let health = compute_health_from_fixed_accounts(&account, health_type, ctx.remaining_accounts)?; + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?; + let health = crate::state::compute_health(&account, health_type, &retriever)?; msg!("health: {}", health); Ok(health) diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 006bd9e67..edab7ac01 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -1,6 +1,10 @@ use crate::accounts_zerocopy::*; use crate::error::MangoError; -use crate::state::{compute_health_from_fixed_accounts, Bank, Group, HealthType, MangoAccount}; +use crate::logs::{MarginTradeLog, TokenBalanceLog}; +use crate::state::{ + compute_health, new_fixed_order_account_retriever, AccountRetriever, Bank, Group, HealthType, + MangoAccount, +}; use crate::{group_seeds, Mango}; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; @@ -98,7 +102,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( match ai.load::() { Ok(bank) => { require!(bank.group == account.group, MangoError::SomeError); - let (_, raw_token_index) = account.tokens.get_mut_or_create(bank.token_index)?; + let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?; allowed_vaults.insert(bank.vault, (i, raw_token_index)); allowed_banks.insert(ai.key, bank); } @@ -115,10 +119,12 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( // Check pre-cpi health // NOTE: This health check isn't strictly necessary. It will be, later, when // we want to have reduce_only or be able to move an account out of bankruptcy. - let pre_cpi_health = - compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; - require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("pre_cpi_health {:?}", pre_cpi_health); + { + let retriever = new_fixed_order_account_retriever(health_ais, &account)?; + let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?; + require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); + msg!("pre_cpi_health {:?}", pre_cpi_health); + } let all_cpi_ais = &ctx.remaining_accounts[num_health_accounts..]; let mut all_cpi_ams = all_cpi_ais @@ -173,6 +179,13 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( }) .collect::>>()?; + // Store the indexed value before the margin trade for logging purposes + let mut pre_indexed_positions = Vec::new(); + for (_, info) in used_vaults.iter() { + let position = account.tokens.get_raw(info.raw_token_index); + pre_indexed_positions.push(position.indexed_position.to_bits()); + } + // Find banks for used vaults in cpi_ais and collect signer seeds for them. // Also update withdraw_amount and loan_amount. let mut bank_signer_data = Vec::with_capacity(used_vaults.len()); @@ -314,12 +327,43 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( adjust_for_post_cpi_vault_amounts(health_ais, all_cpi_ais, &used_vaults, &mut account)?; // Check post-cpi health - let post_cpi_health = - compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; + let retriever = new_fixed_order_account_retriever(health_ais, &account)?; + let post_cpi_health = compute_health(&account, HealthType::Init, &retriever)?; require!(post_cpi_health >= 0, MangoError::HealthMustBePositive); msg!("post_cpi_health {:?}", post_cpi_health); - // Deactivate inactive token accounts after health check + // Token balances logging + let mut token_indexes = Vec::with_capacity(used_vaults.len()); + let mut post_indexed_positions = Vec::with_capacity(used_vaults.len()); + for (_, info) in used_vaults.iter() { + let position = account.tokens.get_raw(info.raw_token_index); + post_indexed_positions.push(position.indexed_position.to_bits()); + token_indexes.push(position.token_index as u16); + + let (bank, oracle_price) = retriever.bank_and_oracle( + &ctx.accounts.group.key(), + info.bank_health_ai_index, + position.token_index, + )?; + + emit!(TokenBalanceLog { + mango_account: ctx.accounts.account.key(), + token_index: bank.token_index as u16, + indexed_position: position.indexed_position.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: oracle_price.to_bits(), + }); + } + + emit!(MarginTradeLog { + mango_account: ctx.accounts.account.key(), + token_indexes, + pre_indexed_positions, + post_indexed_positions, + }); + + // Deactivate inactive token accounts at the end for raw_token_index in inactive_tokens { account.tokens.deactivate(raw_token_index); } diff --git a/programs/mango-v4/src/instructions/flash_loan2.rs b/programs/mango-v4/src/instructions/flash_loan2.rs index cea4e415d..3b61ac17e 100644 --- a/programs/mango-v4/src/instructions/flash_loan2.rs +++ b/programs/mango-v4/src/instructions/flash_loan2.rs @@ -1,7 +1,11 @@ use crate::accounts_zerocopy::*; use crate::error::MangoError; use crate::group_seeds; -use crate::state::{compute_health_from_fixed_accounts, Bank, Group, HealthType, MangoAccount}; +use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; +use crate::state::{ + compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever, + AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex, +}; use crate::util::checked_math as cm; use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; @@ -208,7 +212,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>( require_neq!(bank.flash_loan_vault_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.tokens.get_mut_or_create(bank.token_index)?; + let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?; // Revoke delegation let ix = token::spl_token::instruction::revoke( @@ -241,14 +245,29 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>( // Check pre-cpi health // NOTE: This health check isn't strictly necessary. It will be, later, when // we want to have reduce_only or be able to move an account out of bankruptcy. - let pre_cpi_health = - compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; + let retriever = new_fixed_order_account_retriever(health_ais, &account)?; + let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?; require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); msg!("pre_cpi_health {:?}", pre_cpi_health); + // Prices for logging + let mut prices = vec![]; + for change in &changes { + let (_, oracle_price) = retriever.bank_and_oracle( + &account.group, + change.bank_index, + change.raw_token_index as TokenIndex, + )?; + + prices.push(oracle_price); + } + // Drop retriever as mut bank below uses health_ais + drop(retriever); + // Apply the vault diffs to the bank positions let mut deactivated_token_positions = vec![]; - for change in changes { + let mut token_loan_details = Vec::with_capacity(changes.len()); + for (change, price) in changes.iter().zip(prices.iter()) { let mut bank = health_ais[change.bank_index].load_mut::()?; let position = account.tokens.get_mut_raw(change.raw_token_index); let native = position.native(&bank); @@ -271,8 +290,32 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>( bank.flash_loan_approved_amount = 0; bank.flash_loan_vault_initial = u64::MAX; + + token_loan_details.push(FlashLoanTokenDetail { + token_index: position.token_index, + change_amount: change.amount.to_bits(), + loan: loan.to_bits(), + loan_origination_fee: loan_origination_fee.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: price.to_bits(), + }); + + emit!(TokenBalanceLog { + mango_account: ctx.accounts.account.key(), + token_index: bank.token_index as u16, + indexed_position: position.indexed_position.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: price.to_bits(), + }); } + emit!(FlashLoanLog { + mango_account: ctx.accounts.account.key(), + token_loan_details: token_loan_details + }); + // Check post-cpi health let post_cpi_health = compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; diff --git a/programs/mango-v4/src/instructions/flash_loan3.rs b/programs/mango-v4/src/instructions/flash_loan3.rs index f7dd1f4e8..b244a5f0f 100644 --- a/programs/mango-v4/src/instructions/flash_loan3.rs +++ b/programs/mango-v4/src/instructions/flash_loan3.rs @@ -1,7 +1,11 @@ use crate::accounts_zerocopy::*; use crate::error::MangoError; use crate::group_seeds; -use crate::state::{compute_health_from_fixed_accounts, Bank, Group, HealthType, MangoAccount}; +use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; +use crate::state::{ + compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever, + AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex, +}; use crate::util::checked_math as cm; use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; @@ -212,7 +216,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>( require_neq!(bank.flash_loan_vault_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.tokens.get_mut_or_create(bank.token_index)?; + let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?; // Transfer any excess over the inital balance of the token account back // into the vault. Compute the total change in the vault balance. @@ -246,14 +250,29 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>( // Check pre-cpi health // NOTE: This health check isn't strictly necessary. It will be, later, when // we want to have reduce_only or be able to move an account out of bankruptcy. - let pre_cpi_health = - compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; + let retriever = new_fixed_order_account_retriever(health_ais, &account)?; + let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?; require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); msg!("pre_cpi_health {:?}", pre_cpi_health); + // Prices for logging + let mut prices = vec![]; + for change in &changes { + let (_, oracle_price) = retriever.bank_and_oracle( + &account.group, + change.bank_index, + change.raw_token_index as TokenIndex, + )?; + + prices.push(oracle_price); + } + // Drop retriever as mut bank below uses health_ais + drop(retriever); + // Apply the vault diffs to the bank positions let mut deactivated_token_positions = vec![]; - for change in changes { + let mut token_loan_details = Vec::with_capacity(changes.len()); + for (change, price) in changes.iter().zip(prices.iter()) { let mut bank = health_ais[change.bank_index].load_mut::()?; let position = account.tokens.get_mut_raw(change.raw_token_index); let native = position.native(&bank); @@ -276,8 +295,32 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>( bank.flash_loan_approved_amount = 0; bank.flash_loan_vault_initial = u64::MAX; + + token_loan_details.push(FlashLoanTokenDetail { + token_index: position.token_index, + change_amount: change.amount.to_bits(), + loan: loan.to_bits(), + loan_origination_fee: loan_origination_fee.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: price.to_bits(), + }); + + emit!(TokenBalanceLog { + mango_account: ctx.accounts.account.key(), + token_index: bank.token_index as u16, + indexed_position: position.indexed_position.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: price.to_bits(), + }); } + emit!(FlashLoanLog { + mango_account: ctx.accounts.account.key(), + token_loan_details: token_loan_details + }); + // Check post-cpi health let post_cpi_health = compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?; 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 c0395dd99..2929de8a7 100644 --- a/programs/mango-v4/src/instructions/liq_token_with_token.rs +++ b/programs/mango-v4/src/instructions/liq_token_with_token.rs @@ -3,6 +3,7 @@ use fixed::types::I80F48; use std::cmp::min; use crate::error::*; +use crate::logs::{LiquidateTokenAndTokenLog, TokenBalanceLog}; use crate::state::ScanningAccountRetriever; use crate::state::*; use crate::util::checked_math as cm; @@ -136,6 +137,71 @@ pub fn liq_token_with_token( liab_transfer, asset_transfer ); + + emit!(LiquidateTokenAndTokenLog { + liqee: ctx.accounts.liqee.key(), + liqor: ctx.accounts.liqor.key(), + asset_token_index: asset_token_index, + liab_token_index: liab_token_index, + asset_transfer: asset_transfer.to_bits(), + liab_transfer: liab_transfer.to_bits(), + asset_price: asset_price.to_bits(), + liab_price: liab_price.to_bits(), + // bankruptcy: + }); + + // liqee asset + emit!(TokenBalanceLog { + mango_account: ctx.accounts.liqee.key(), + token_index: asset_token_index, + indexed_position: liqee + .tokens + .get_mut(asset_token_index)? + .indexed_position + .to_bits(), + deposit_index: asset_bank.deposit_index.to_bits(), + borrow_index: asset_bank.borrow_index.to_bits(), + price: asset_price.to_bits(), + }); + // liqee liab + emit!(TokenBalanceLog { + mango_account: ctx.accounts.liqee.key(), + token_index: liab_token_index, + indexed_position: liqee + .tokens + .get_mut(liab_token_index)? + .indexed_position + .to_bits(), + deposit_index: liab_bank.deposit_index.to_bits(), + borrow_index: liab_bank.borrow_index.to_bits(), + price: liab_price.to_bits(), + }); + // liqor asset + emit!(TokenBalanceLog { + mango_account: ctx.accounts.liqor.key(), + token_index: asset_token_index, + indexed_position: liqor + .tokens + .get_mut(asset_token_index)? + .indexed_position + .to_bits(), + deposit_index: asset_bank.deposit_index.to_bits(), + borrow_index: asset_bank.borrow_index.to_bits(), + price: asset_price.to_bits(), + }); + // liqor liab + emit!(TokenBalanceLog { + mango_account: ctx.accounts.liqor.key(), + token_index: liab_token_index, + indexed_position: liqor + .tokens + .get_mut(liab_token_index)? + .indexed_position + .to_bits(), + deposit_index: liab_bank.deposit_index.to_bits(), + borrow_index: liab_bank.borrow_index.to_bits(), + price: liab_price.to_bits(), + }); } // Check liqee health again diff --git a/programs/mango-v4/src/instructions/perp_consume_events.rs b/programs/mango-v4/src/instructions/perp_consume_events.rs index 071858936..310b24f9f 100644 --- a/programs/mango-v4/src/instructions/perp_consume_events.rs +++ b/programs/mango-v4/src/instructions/perp_consume_events.rs @@ -6,6 +6,8 @@ use crate::error::MangoError; use crate::state::EventQueue; use crate::state::{EventType, FillEvent, Group, MangoAccount, OutEvent, PerpMarket}; +use crate::logs::{emit_perp_balances, FillLog}; + #[derive(Accounts)] pub struct PerpConsumeEvents<'info> { pub group: AccountLoader<'info, Group>, @@ -57,6 +59,13 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res &mut perp_market, fill, )?; + emit_perp_balances( + fill.maker, + perp_market.perp_market_index as u64, + fill.price, + &ma.perps.accounts[perp_market.perp_market_index as usize], + &perp_market, + ); } else { let mut maker = match mango_account_ais.iter().find(|ai| ai.key == &fill.maker) { @@ -85,7 +94,42 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res &mut perp_market, fill, )?; + emit_perp_balances( + fill.maker, + perp_market.perp_market_index as u64, + fill.price, + &maker.perps.accounts[perp_market.perp_market_index as usize], + &perp_market, + ); + emit_perp_balances( + fill.taker, + perp_market.perp_market_index as u64, + fill.price, + &taker.perps.accounts[perp_market.perp_market_index as usize], + &perp_market, + ); } + emit!(FillLog { + mango_group: ctx.accounts.group.key(), + market_index: perp_market.perp_market_index, + taker_side: fill.taker_side as u8, + maker_slot: fill.maker_slot, + market_fees_applied: fill.market_fees_applied, + maker_out: fill.maker_out, + timestamp: fill.timestamp, + seq_num: fill.seq_num, + maker: fill.maker, + maker_order_id: fill.maker_order_id, + maker_client_order_id: fill.maker_client_order_id, + maker_fee: fill.maker_fee.to_bits(), + maker_timestamp: fill.maker_timestamp, + taker: fill.taker, + taker_order_id: fill.taker_order_id, + taker_client_order_id: fill.taker_client_order_id, + taker_fee: fill.taker_fee.to_bits(), + price: fill.price, + quantity: fill.quantity, + }); } EventType::Out => { let out: &OutEvent = cast_ref(event); diff --git a/programs/mango-v4/src/instructions/perp_place_order.rs b/programs/mango-v4/src/instructions/perp_place_order.rs index e120c45ed..9e4cb2635 100644 --- a/programs/mango-v4/src/instructions/perp_place_order.rs +++ b/programs/mango-v4/src/instructions/perp_place_order.rs @@ -3,8 +3,8 @@ use anchor_lang::prelude::*; use crate::accounts_zerocopy::*; use crate::error::*; use crate::state::{ - compute_health_from_fixed_accounts, oracle_price, Book, BookSide, EventQueue, Group, - HealthType, MangoAccount, OrderType, PerpMarket, Side, + compute_health, new_fixed_order_account_retriever, oracle_price, Book, BookSide, EventQueue, + Group, HealthType, MangoAccount, OrderType, PerpMarket, Side, }; #[derive(Accounts)] @@ -132,11 +132,8 @@ pub fn perp_place_order( )?; } - let health = compute_health_from_fixed_accounts( - &mango_account, - HealthType::Init, - ctx.remaining_accounts, - )?; + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &mango_account)?; + let health = compute_health(&mango_account, HealthType::Init, &retriever)?; msg!("health: {}", health); require!(health >= 0, MangoError::HealthMustBePositive); diff --git a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs index c999b2975..76065e1f4 100644 --- a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs @@ -61,11 +61,11 @@ pub fn serum3_create_open_orders(ctx: Context) -> Result // Make it so that the token_account_map for the base and quote currency // stay permanently blocked. Otherwise users may end up in situations where // they can't settle a market because they don't have free token_account_map! - let (quote_position, _) = account + let (quote_position, _, _) = account .tokens .get_mut_or_create(serum_market.quote_token_index)?; quote_position.in_use_count += 1; - let (base_position, _) = account + let (base_position, _, _) = account .tokens .get_mut_or_create(serum_market.base_token_index)?; base_position.in_use_count += 1; diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index 4413e4eea..06c986384 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -111,11 +111,9 @@ pub fn serum3_liq_force_cancel_orders( // TODO: do the correct health / being_liquidated check { let account = ctx.accounts.account.load()?; - let health = compute_health_from_fixed_accounts( - &account, - HealthType::Maint, - ctx.remaining_accounts, - )?; + + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?; + let health = compute_health(&account, HealthType::Maint, &retriever)?; msg!("health: {}", health); require!(health < 0, MangoError::SomeError); } diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 6df812805..638783f87 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -274,8 +274,8 @@ pub fn serum3_place_order( // Health check // let account = ctx.accounts.account.load()?; - let health = - compute_health_from_fixed_accounts(&account, HealthType::Init, ctx.remaining_accounts)?; + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?; + let health = compute_health(&account, HealthType::Init, &retriever)?; msg!("health: {}", health); require!(health >= 0, MangoError::HealthMustBePositive); diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index 70ad3a02f..1da67984f 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -7,6 +7,8 @@ use fixed::types::I80F48; use crate::error::*; use crate::state::*; +use crate::logs::{DepositLog, TokenBalanceLog}; + #[derive(Accounts)] pub struct TokenDeposit<'info> { pub group: AccountLoader<'info, Group>, @@ -60,9 +62,9 @@ pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { let mut account = ctx.accounts.account.load_mut()?; require!(account.is_bankrupt == 0, MangoError::IsBankrupt); - let (position, position_index) = account.tokens.get_mut_or_create(token_index)?; + let (position, raw_token_index, active_token_index) = + account.tokens.get_mut_or_create(token_index)?; - // Update the bank and position let position_is_active = { let mut bank = ctx.accounts.bank.load_mut()?; bank.deposit(position, I80F48::from(amount))? @@ -71,13 +73,26 @@ pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { // Transfer the actual tokens token::transfer(ctx.accounts.transfer_ctx(), amount)?; + let indexed_position = position.indexed_position; + + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?; + let (bank, oracle_price) = + retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?; + emit!(TokenBalanceLog { + mango_account: ctx.accounts.account.key(), + token_index: token_index, + indexed_position: indexed_position.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: oracle_price.to_bits(), + }); + // // Health computation // TODO: This will be used to disable is_bankrupt or being_liquidated // when health recovers sufficiently // - let health = - compute_health_from_fixed_accounts(&account, HealthType::Init, ctx.remaining_accounts)?; + let health = compute_health(&account, HealthType::Init, &retriever)?; msg!("health: {}", health); // @@ -87,8 +102,16 @@ pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { // Deposits can deactivate a position if they cancel out a previous borrow. // if !position_is_active { - account.tokens.deactivate(position_index); + account.tokens.deactivate(raw_token_index); } + emit!(DepositLog { + mango_account: ctx.accounts.account.key(), + signer: ctx.accounts.token_authority.key(), + token_index: token_index, + quantity: amount, + price: oracle_price.to_bits(), + }); + Ok(()) } diff --git a/programs/mango-v4/src/instructions/token_withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs index 114d54a7c..4dd924979 100644 --- a/programs/mango-v4/src/instructions/token_withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -6,6 +6,9 @@ use anchor_spl::token::Token; use anchor_spl::token::TokenAccount; use fixed::types::I80F48; +use crate::logs::{TokenBalanceLog, WithdrawLog}; +use crate::state::new_fixed_order_account_retriever; + #[derive(Accounts)] pub struct TokenWithdraw<'info> { pub group: AccountLoader<'info, Group>, @@ -62,7 +65,8 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo let mut account = ctx.accounts.account.load_mut()?; require!(account.is_bankrupt == 0, MangoError::IsBankrupt); - let (position, position_index) = account.tokens.get_mut_or_create(token_index)?; + let (position, raw_token_index, active_token_index) = + account.tokens.get_mut_or_create(token_index)?; // The bank will also be passed in remainingAccounts. Use an explicit scope // to drop the &mut before we borrow it immutably again later. @@ -103,11 +107,24 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo position_is_active }; + let indexed_position = position.indexed_position; + + let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?; + let (bank, oracle_price) = + retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?; + emit!(TokenBalanceLog { + mango_account: ctx.accounts.account.key(), + token_index: token_index, + indexed_position: indexed_position.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: oracle_price.to_bits(), + }); + // // Health check // - let health = - compute_health_from_fixed_accounts(&account, HealthType::Init, ctx.remaining_accounts)?; + let health = compute_health(&account, HealthType::Init, &retriever)?; msg!("health: {}", health); require!(health >= 0, MangoError::HealthMustBePositive); @@ -117,8 +134,16 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo // deactivated. // if !position_is_active { - account.tokens.deactivate(position_index); + account.tokens.deactivate(raw_token_index); } + emit!(WithdrawLog { + mango_account: ctx.accounts.account.key(), + signer: ctx.accounts.owner.key(), + token_index: token_index, + quantity: amount, + price: oracle_price.to_bits(), + }); + Ok(()) } diff --git a/programs/mango-v4/src/instructions/update_index.rs b/programs/mango-v4/src/instructions/update_index.rs index 83901f9c3..4c035ccfe 100644 --- a/programs/mango-v4/src/instructions/update_index.rs +++ b/programs/mango-v4/src/instructions/update_index.rs @@ -1,5 +1,6 @@ use anchor_lang::prelude::*; +use crate::logs::UpdateIndexLog; use crate::{ accounts_zerocopy::{LoadMutZeroCopyRef, LoadZeroCopyRef}, error::MangoError, @@ -63,6 +64,15 @@ pub fn update_index(ctx: Context) -> Result<()> { bank.deposit_index = deposit_index; bank.borrow_index = borrow_index; + + // clarkeni TODO: add prices + emit!(UpdateIndexLog { + mango_group: bank.group.key(), + token_index: bank.token_index, + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + // price: oracle_price.to_bits(), + }); } Ok(()) diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 01039bf6e..90dfc22ab 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -13,6 +13,7 @@ pub mod accounts_zerocopy; pub mod address_lookup_table; pub mod error; pub mod instructions; +pub mod logs; mod serum3_cpi; pub mod state; pub mod types; diff --git a/programs/mango-v4/src/logs.rs b/programs/mango-v4/src/logs.rs new file mode 100644 index 000000000..c9c9da470 --- /dev/null +++ b/programs/mango-v4/src/logs.rs @@ -0,0 +1,163 @@ +use crate::state::{PerpMarket, PerpPositions}; +use anchor_lang::prelude::*; +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Warning: This function needs 512+ bytes free on the stack +pub fn emit_perp_balances( + mango_account: Pubkey, + market_index: u64, + price: i64, + pp: &PerpPositions, + pm: &PerpMarket, +) { + emit!(PerpBalanceLog { + mango_account: mango_account, + market_index: market_index, + base_position: pp.base_position_lots, + quote_position: pp.quote_position_native.to_bits(), + long_settled_funding: pp.long_settled_funding.to_bits(), + short_settled_funding: pp.short_settled_funding.to_bits(), + price, + long_funding: pm.long_funding.to_bits(), + short_funding: pm.short_funding.to_bits(), + }); +} + +#[event] +pub struct PerpBalanceLog { + pub mango_account: Pubkey, + pub market_index: u64, // IDL doesn't support usize + pub base_position: i64, + pub quote_position: i128, // I80F48 + pub long_settled_funding: i128, // I80F48 + pub short_settled_funding: i128, // I80F48 + pub price: i64, + pub long_funding: i128, // I80F48 + pub short_funding: i128, // I80F48 +} + +#[event] +pub struct TokenBalanceLog { + 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 + pub deposit_index: i128, // I80F48 + pub borrow_index: i128, // I80F48 + 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(BorshSerialize, BorshDeserialize)] +pub struct FlashLoanTokenDetail { + pub token_index: u16, + pub change_amount: i128, + pub loan: i128, + pub loan_origination_fee: i128, + pub deposit_index: i128, + pub borrow_index: i128, + pub price: i128, +} + +#[event] +pub struct FlashLoanLog { + pub mango_account: Pubkey, + pub token_loan_details: Vec, +} + +#[event] +pub struct WithdrawLog { + pub mango_account: Pubkey, + pub signer: Pubkey, + pub token_index: u16, + pub quantity: u64, + pub price: i128, // I80F48 +} + +#[event] +pub struct DepositLog { + pub mango_account: Pubkey, + pub signer: Pubkey, + pub token_index: u16, + pub quantity: u64, + pub price: i128, // I80F48 +} + +#[event] +pub struct FillLog { + pub mango_group: Pubkey, + pub market_index: u16, + pub taker_side: u8, // side from the taker's POV + pub maker_slot: u8, + pub market_fees_applied: bool, + pub maker_out: bool, // true if maker order quantity == 0 + pub timestamp: u64, + pub seq_num: u64, // note: usize same as u64 + + pub maker: Pubkey, + pub maker_order_id: i128, + pub maker_client_order_id: u64, + pub maker_fee: i128, + + // Timestamp of when the maker order was placed; copied over from the LeafNode + pub maker_timestamp: u64, + + pub taker: Pubkey, + pub taker_order_id: i128, + pub taker_client_order_id: u64, + pub taker_fee: i128, + + pub price: i64, + pub quantity: i64, // number of base lots +} + +#[event] +pub struct UpdateFundingLog { + pub mango_group: Pubkey, + pub market_index: u16, + pub long_funding: i128, // I80F48 + pub short_funding: i128, // I80F48 + pub price: i128, // I80F48 +} + +#[event] +pub struct UpdateIndexLog { + pub mango_group: Pubkey, + pub token_index: u16, + pub deposit_index: i128, // I80F48 + pub borrow_index: i128, // I80F48 + // pub price: i128, // I80F48 +} + +#[event] +pub struct LiquidateTokenAndTokenLog { + pub liqee: Pubkey, + pub liqor: Pubkey, + pub asset_token_index: u16, + pub liab_token_index: u16, + pub asset_transfer: i128, // I80F48 + pub liab_transfer: i128, // I80F48 + pub asset_price: i128, // I80F48 + pub liab_price: i128, // I80F48 + // pub bankruptcy: bool, +} + +#[event] +pub struct OpenOrdersBalanceLog { + pub mango_group: Pubkey, + pub mango_account: Pubkey, + pub market_index: u16, + pub base_total: u64, + pub base_free: u64, + /// this field does not include the referrer_rebates; need to add that in to get true total + pub quote_total: u64, + pub quote_free: u64, + pub referrer_rebates_accrued: u64, + pub price: i128, // I80F48 +} diff --git a/programs/mango-v4/src/state/health.rs b/programs/mango-v4/src/state/health.rs index 6bf5e4c8a..0ac70903c 100644 --- a/programs/mango-v4/src/state/health.rs +++ b/programs/mango-v4/src/state/health.rs @@ -50,6 +50,29 @@ pub struct FixedOrderAccountRetriever { pub begin_serum3: usize, } +pub fn new_fixed_order_account_retriever<'a, 'info>( + ais: &'a [AccountInfo<'info>], + account: &MangoAccount, +) -> Result>> { + let active_token_len = account.tokens.iter_active().count(); + let active_serum3_len = account.serum3.iter_active().count(); + let active_perp_len = account.perps.iter_active_accounts().count(); + let expected_ais = cm!(active_token_len * 2 // banks + oracles + + active_perp_len // PerpMarkets + + active_serum3_len); // open_orders + require!(ais.len() == expected_ais, MangoError::SomeError); + + Ok(FixedOrderAccountRetriever { + ais: ais + .into_iter() + .map(|ai| AccountInfoRef::borrow(ai)) + .collect::>>()?, + n_banks: active_token_len, + begin_perp: cm!(active_token_len * 2), + begin_serum3: cm!(active_token_len * 2 + active_perp_len), + }) +} + impl FixedOrderAccountRetriever { fn bank(&self, group: &Pubkey, account_index: usize) -> Result<&Bank> { let bank = self.ais[account_index].load::()?; diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index dc35598b4..f2967976a 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -139,30 +139,44 @@ impl MangoAccountTokenPositions { &mut self.values[raw_token_index] } + pub fn get_raw(&self, raw_token_index: usize) -> &TokenPosition { + &self.values[raw_token_index] + } + + /// Creates or retrieves a TokenPosition for the token_index. + /// Returns: + /// - the position + /// - the raw index into the token positions list (for use with get_raw) + /// - the active index, for use with FixedOrderAccountRetriever pub fn get_mut_or_create( &mut self, token_index: TokenIndex, - ) -> Result<(&mut TokenPosition, usize)> { - // This function looks complex because of lifetimes. - // Maybe there's a smart way to write it with double iter_mut() - // that doesn't confuse the borrow checker. - let mut pos = self - .values - .iter() - .position(|p| p.is_active_for_token(token_index)); - if pos.is_none() { - pos = self.values.iter().position(|p| !p.is_active()); - if let Some(i) = pos { - self.values[i] = TokenPosition { + ) -> Result<(&mut TokenPosition, usize, usize)> { + let mut active_index = 0; + let mut match_or_free = None; + for (raw_index, position) in self.values.iter().enumerate() { + if position.is_active_for_token(token_index) { + // Can't return early because of lifetimes + match_or_free = Some((raw_index, active_index)); + break; + } + if position.is_active() { + active_index += 1; + } else if match_or_free.is_none() { + match_or_free = Some((raw_index, active_index)); + } + } + if let Some((raw_index, bank_index)) = match_or_free { + let v = &mut self.values[raw_index]; + if !v.is_active_for_token(token_index) { + *v = TokenPosition { indexed_position: I80F48::ZERO, token_index, in_use_count: 0, reserved: Default::default(), }; } - } - if let Some(i) = pos { - Ok((&mut self.values[i], i)) + Ok((v, raw_index, bank_index)) } else { err!(MangoError::SomeError) // TODO: No free space }