From f8da1f6a4033a472b8712c2f654ed0232cff7de5 Mon Sep 17 00:00:00 2001 From: Nicholas Clarke Date: Thu, 30 Jun 2022 05:35:05 -0700 Subject: [PATCH] Clarkeni/logging (#81) * Add logging * Added new_fixed_order_account_retriever to allow us to more easily access oracle prices outside of health calculations for logging purposes * Add token balances logging to token and token liquidation and add logging to margin trade * rust format * fix clippy errors * Address PR requested changes * fix flash_loan * Recalculate raw_token_index in token withdraw to account for position becoming inactive * Fix retrieving oracle for logging in get_mut_or_create(), return the raw index into the account's token positions as well as the index into active positions only. The latter index is useful for indexing into banks in the health account list. * Add logging flash_loan2 and flash_loan3 * Refactoring flash loan logging Co-authored-by: Christian Kamm --- programs/mango-v4/Cargo.toml | 1 - .../src/instructions/compute_health.rs | 3 +- .../mango-v4/src/instructions/flash_loan.rs | 62 ++++++- .../mango-v4/src/instructions/flash_loan2.rs | 53 +++++- .../mango-v4/src/instructions/flash_loan3.rs | 53 +++++- .../src/instructions/liq_token_with_token.rs | 66 +++++++ .../src/instructions/perp_consume_events.rs | 44 +++++ .../src/instructions/perp_place_order.rs | 11 +- .../instructions/serum3_create_open_orders.rs | 4 +- .../serum3_liq_force_cancel_orders.rs | 8 +- .../src/instructions/serum3_place_order.rs | 4 +- .../src/instructions/token_deposit.rs | 33 +++- .../src/instructions/token_withdraw.rs | 33 +++- .../mango-v4/src/instructions/update_index.rs | 10 ++ programs/mango-v4/src/lib.rs | 1 + programs/mango-v4/src/logs.rs | 163 ++++++++++++++++++ programs/mango-v4/src/state/health.rs | 23 +++ programs/mango-v4/src/state/mango_account.rs | 44 +++-- 18 files changed, 555 insertions(+), 61 deletions(-) create mode 100644 programs/mango-v4/src/logs.rs 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 }