diff --git a/bin/keeper/src/crank.rs b/bin/keeper/src/crank.rs index 705ea8b2b..10c12d412 100644 --- a/bin/keeper/src/crank.rs +++ b/bin/keeper/src/crank.rs @@ -26,6 +26,7 @@ use prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry}; use solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, + signature::Signature, }; use tokio::task::JoinHandle; use tracing::*; @@ -507,12 +508,13 @@ async fn charge_collateral_fees_inner( ix_to_send.push(ixs); } - send_batched_log_errors_no_confirm( + let txsigs = send_batched_log_errors_no_confirm( client.transaction_builder().await?, &client.client, &ix_to_send, ) .await; + info!("charge collateral fees: {:?}", txsigs); Ok(()) } @@ -522,7 +524,9 @@ async fn send_batched_log_errors_no_confirm( mut tx_builder: TransactionBuilder, client: &mango_v4_client::Client, ixs_list: &[PreparedInstructions], -) { +) -> Vec { + let mut txsigs = Vec::new(); + let mut current_batch = PreparedInstructions::new(); for ixs in ixs_list { let previous_batch = current_batch.clone(); @@ -533,7 +537,7 @@ async fn send_batched_log_errors_no_confirm( tx_builder.instructions = previous_batch.to_instructions(); match tx_builder.send(client).await { Err(err) => error!("could not send transaction: {err:?}"), - _ => {} + Ok(txsig) => txsigs.push(txsig), } current_batch = ixs.clone(); @@ -544,7 +548,9 @@ async fn send_batched_log_errors_no_confirm( tx_builder.instructions = current_batch.to_instructions(); match tx_builder.send(client).await { Err(err) => error!("could not send transaction: {err:?}"), - _ => {} + Ok(txsig) => txsigs.push(txsig), } } + + txsigs } diff --git a/programs/mango-v4/src/instructions/token_charge_collateral_fees.rs b/programs/mango-v4/src/instructions/token_charge_collateral_fees.rs index 028a287ff..fc145ba11 100644 --- a/programs/mango-v4/src/instructions/token_charge_collateral_fees.rs +++ b/programs/mango-v4/src/instructions/token_charge_collateral_fees.rs @@ -67,6 +67,11 @@ pub fn token_charge_collateral_fees(ctx: Context) -> } } + // If there's no assets or no liabs, we can't charge fees + if total_asset_health.is_zero() || total_liab_health.is_zero() { + return Ok(()); + } + // Users only pay for assets that are actively used to cover their liabilities. let asset_usage_scaling = (total_liab_health / total_asset_health) .max(I80F48::ZERO) @@ -77,7 +82,7 @@ pub fn token_charge_collateral_fees(ctx: Context) -> let token_position_count = account.active_token_positions().count(); for bank_ai in &ctx.remaining_accounts[0..token_position_count] { let mut bank = bank_ai.load_mut::()?; - if bank.collateral_fee_per_day <= 0.0 { + if bank.collateral_fee_per_day <= 0.0 || bank.maint_asset_weight.is_zero() { continue; } diff --git a/programs/mango-v4/tests/cases/test_collateral_fees.rs b/programs/mango-v4/tests/cases/test_collateral_fees.rs index b7fbb3d6c..5d069f023 100644 --- a/programs/mango-v4/tests/cases/test_collateral_fees.rs +++ b/programs/mango-v4/tests/cases/test_collateral_fees.rs @@ -1,3 +1,4 @@ +#![allow(unused_assignments)] use super::*; #[tokio::test] @@ -44,6 +45,18 @@ async fn test_collateral_fees() -> Result<(), TransportError> { ) .await; + let empty_account = create_funded_account( + &solana, + group, + owner, + 2, + &context.users[1], + &mints[0..0], + 0, + 0, + ) + .await; + let hour = 60 * 60; send_tx( @@ -92,6 +105,32 @@ async fn test_collateral_fees() -> Result<(), TransportError> { .await .unwrap(); + // + // TEST: It works on empty accounts + // + + send_tx( + solana, + TokenChargeCollateralFeesInstruction { + account: empty_account, + }, + ) + .await + .unwrap(); + let mut last_time = solana.clock_timestamp().await; + solana.set_clock_timestamp(last_time + 9 * hour).await; + + // send it twice, because the first time will never charge anything + send_tx( + solana, + TokenChargeCollateralFeesInstruction { + account: empty_account, + }, + ) + .await + .unwrap(); + last_time = solana.clock_timestamp().await; + // // TEST: Without borrows, charging collateral fees has no effect // @@ -99,7 +138,15 @@ async fn test_collateral_fees() -> Result<(), TransportError> { send_tx(solana, TokenChargeCollateralFeesInstruction { account }) .await .unwrap(); - let mut last_time = solana.clock_timestamp().await; + last_time = solana.clock_timestamp().await; + solana.set_clock_timestamp(last_time + 9 * hour).await; + + // send it twice, because the first time will never charge anything + send_tx(solana, TokenChargeCollateralFeesInstruction { account }) + .await + .unwrap(); + last_time = solana.clock_timestamp().await; + // no effect assert_eq!( account_position(solana, account, tokens[0].bank).await,