diff --git a/clients/rust/marginfi-cli/src/entrypoint.rs b/clients/rust/marginfi-cli/src/entrypoint.rs index 5b161690..703a711f 100644 --- a/clients/rust/marginfi-cli/src/entrypoint.rs +++ b/clients/rust/marginfi-cli/src/entrypoint.rs @@ -124,10 +124,7 @@ pub enum GroupCommand { }, #[cfg(feature = "admin")] HandleBankruptcy { - #[clap(long)] - bank: Pubkey, - #[clap(long)] - marginfi_account: Pubkey, + accounts: Vec, }, } @@ -502,10 +499,9 @@ fn group(subcmd: GroupCommand, global_options: &GlobalOptions) -> Result<()> { risk_tier, ), #[cfg(feature = "admin")] - GroupCommand::HandleBankruptcy { - bank, - marginfi_account, - } => processor::group_handle_bankruptcy(&config, profile, bank, marginfi_account), + GroupCommand::HandleBankruptcy { accounts } => { + processor::handle_bankruptcy_for_accounts(&config, &profile, accounts) + } } } diff --git a/clients/rust/marginfi-cli/src/processor/mod.rs b/clients/rust/marginfi-cli/src/processor/mod.rs index 52aeac6f..f82c34e0 100644 --- a/clients/rust/marginfi-cli/src/processor/mod.rs +++ b/clients/rust/marginfi-cli/src/processor/mod.rs @@ -51,6 +51,11 @@ use { #[cfg(feature = "dev")] use marginfi::state::price::{OraclePriceFeedAdapter, PriceAdapter}; +use marginfi::{ + constants::ZERO_AMOUNT_THRESHOLD, state::marginfi_account::RiskEngine, + utils::NumTraitsWithTolerance, +}; +use solana_client::rpc_client::RpcClient; #[cfg(feature = "admin")] use { @@ -457,10 +462,85 @@ pub fn group_handle_bankruptcy( config, Some(profile.marginfi_group.unwrap()), )?); + let marginfi_account = config .mfi_program .account::(marginfi_account_pk)?; + handle_bankruptcy_for_an_account( + config, + &profile, + &rpc_client, + &banks, + marginfi_account_pk, + &marginfi_account, + bank_pk, + )?; + + Ok(()) +} + +pub fn group_auto_handle_bankruptcy_for_an_account( + config: &Config, + profile: Profile, + marginfi_account_pk: Pubkey, +) -> Result<()> { + let rpc_client = config.mfi_program.rpc(); + + if profile.marginfi_group.is_none() { + bail!("Marginfi group not specified in profile [{}]", profile.name); + } + + let banks = HashMap::from_iter(load_all_banks( + config, + Some(profile.marginfi_group.unwrap()), + )?); + let marginfi_account = config + .mfi_program + .account::(marginfi_account_pk)?; + + marginfi_account + .lending_account + .balances + .iter() + .filter(|b| { + b.active + && banks + .get(&b.bank_pk) + .unwrap() + .get_liability_amount(b.liability_shares.into()) + .unwrap() + .is_positive_with_tolerance(ZERO_AMOUNT_THRESHOLD) + }) + .map(|b| b.bank_pk) + .collect::>() + .iter() + .for_each(|bank_pk| { + handle_bankruptcy_for_an_account( + config, + &profile, + &rpc_client, + &banks, + marginfi_account_pk, + &marginfi_account, + *bank_pk, + ) + .unwrap(); + }); + + Ok(()) +} + +fn handle_bankruptcy_for_an_account( + config: &Config, + profile: &Profile, + rpc_client: &RpcClient, + banks: &HashMap, + marginfi_account_pk: Pubkey, + marginfi_account: &MarginfiAccount, + bank_pk: Pubkey, +) -> Result<()> { + println!("Handling bankruptcy for bank {}", bank_pk); let mut handle_bankruptcy_ix = Instruction { program_id: config.program_id, accounts: marginfi::accounts::LendingPoolHandleBankruptcy { @@ -519,6 +599,141 @@ pub fn group_handle_bankruptcy( Ok(()) } +#[cfg(feature = "admin")] +const BANKRUPTCY_CHUNKS: usize = 4; + +#[cfg(feature = "admin")] +pub fn handle_bankruptcy_for_accounts( + config: &Config, + profile: &Profile, + accounts: Vec, +) -> Result<()> { + let mut instructions = vec![]; + let rpc_client = config.mfi_program.rpc(); + + let banks = HashMap::from_iter(load_all_banks( + config, + Some(profile.marginfi_group.unwrap()), + )?); + + for account in accounts { + let marginfi_account = config + .mfi_program + .account::(account) + .unwrap(); + + marginfi_account + .lending_account + .balances + .iter() + .filter(|b| { + b.active + && banks + .get(&b.bank_pk) + .unwrap() + .get_liability_amount(b.liability_shares.into()) + .unwrap() + .is_positive_with_tolerance(ZERO_AMOUNT_THRESHOLD) + }) + .map(|b| b.bank_pk) + .collect::>() + .iter() + .for_each(|bank_pk| { + instructions.push( + make_bankruptcy_ix( + config, + profile, + &banks, + account, + &marginfi_account, + *bank_pk, + ) + .unwrap(), + ); + }); + } + + println!("Handling {} bankruptcies", instructions.len()); + + let chunks = instructions.chunks(BANKRUPTCY_CHUNKS); + + for chunk in chunks { + let recent_blockhash = rpc_client.get_latest_blockhash().unwrap(); + let signing_keypairs = if let CliSigner::Keypair(keypair) = &config.signer { + vec![keypair] + } else { + vec![] + }; + + let mut ixs = vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)]; + ixs.extend_from_slice(chunk); + + let message = Message::new(&ixs, Some(&config.signer.pubkey())); + + let mut transaction = Transaction::new_unsigned(message); + transaction.partial_sign(&signing_keypairs, recent_blockhash); + + match process_transaction(&transaction, &rpc_client, config.dry_run, &config.signer) { + Ok(sig) => println!("Bankruptcy handled (sig: {})", sig), + Err(err) => println!("Error during bankruptcy handling:\n{:#?}", err), + }; + } + + Ok(()) +} +#[cfg(feature = "admin")] +fn make_bankruptcy_ix( + config: &Config, + profile: &Profile, + banks: &HashMap, + marginfi_account_pk: Pubkey, + marginfi_account: &MarginfiAccount, + bank_pk: Pubkey, +) -> Result { + println!("Handling bankruptcy for bank {}", bank_pk); + let mut handle_bankruptcy_ix = Instruction { + program_id: config.program_id, + accounts: marginfi::accounts::LendingPoolHandleBankruptcy { + marginfi_group: profile.marginfi_group.unwrap(), + admin: config.signer.pubkey(), + bank: bank_pk, + marginfi_account: marginfi_account_pk, + liquidity_vault: find_bank_vault_pda( + &bank_pk, + BankVaultType::Liquidity, + &config.program_id, + ) + .0, + insurance_vault: find_bank_vault_pda( + &bank_pk, + BankVaultType::Insurance, + &config.program_id, + ) + .0, + insurance_vault_authority: find_bank_vault_authority_pda( + &bank_pk, + BankVaultType::Insurance, + &config.program_id, + ) + .0, + token_program: token::ID, + } + .to_account_metas(Some(true)), + data: marginfi::instruction::LendingPoolHandleBankruptcy {}.data(), + }; + + handle_bankruptcy_ix + .accounts + .extend(load_observation_account_metas( + &marginfi_account, + &banks, + vec![bank_pk], + vec![], + )); + + Ok(handle_bankruptcy_ix) +} + // -------------------------------------------------------------------------------------------------------------------- // bank // -------------------------------------------------------------------------------------------------------------------- @@ -719,7 +934,7 @@ pub fn bank_setup_emissions( match process_transaction(&transaction, &rpc_client, config.dry_run, &config.signer) { Ok(sig) => println!("Tx succeded (sig: {})", sig), - Err(err) => println!("Error during bankruptcy handling:\n{:#?}", err), + Err(err) => println!("Error :\n{:#?}", err), }; Ok(()) @@ -835,7 +1050,7 @@ pub fn bank_update_emissions( match process_transaction(&transaction, &rpc_client, config.dry_run, &config.signer) { Ok(sig) => println!("Tx succeded (sig: {})", sig), - Err(err) => println!("Error during bankruptcy handling:\n{:#?}", err), + Err(err) => println!("Error:\n{:#?}", err), }; Ok(())