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 <mail@ckamm.de>
This commit is contained in:
Nicholas Clarke 2022-06-30 05:35:05 -07:00 committed by GitHub
parent ecbffe499f
commit f8da1f6a40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 555 additions and 61 deletions

View File

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

View File

@ -14,7 +14,8 @@ pub struct ComputeHealth<'info> {
pub fn compute_health(ctx: Context<ComputeHealth>, health_type: HealthType) -> Result<I80F48> {
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)

View File

@ -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::<Bank>() {
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::<Result<HashMap<_, _>>>()?;
// 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);
}

View File

@ -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::<Bank>()?;
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)?;

View File

@ -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::<Bank>()?;
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)?;

View File

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

View File

@ -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<PerpConsumeEvents>, 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<PerpConsumeEvents>, 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);

View File

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

View File

@ -61,11 +61,11 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> 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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<u16>,
pub pre_indexed_positions: Vec<i128>,
pub post_indexed_positions: Vec<i128>,
}
#[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<FlashLoanTokenDetail>,
}
#[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
}

View File

@ -50,6 +50,29 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub begin_serum3: usize,
}
pub fn new_fixed_order_account_retriever<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccount,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
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::<Result<Vec<_>>>()?,
n_banks: active_token_len,
begin_perp: cm!(active_token_len * 2),
begin_serum3: cm!(active_token_len * 2 + active_perp_len),
})
}
impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
fn bank(&self, group: &Pubkey, account_index: usize) -> Result<&Bank> {
let bank = self.ais[account_index].load::<Bank>()?;

View File

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