liquidator: add a sequence check in rebalancing (#926)

liquidator: add a sequence check in rebalancing
This commit is contained in:
Serge Farny 2024-04-03 11:55:04 +02:00
parent e4002acf8f
commit 2d064e4fd1
4 changed files with 64 additions and 12 deletions

View File

@ -577,6 +577,7 @@ fn spawn_rebalance_job(
if let Err(err) = rebalancer.zero_all_non_quote().await {
error!("failed to rebalance liqor: {:?}", err);
// TODO FAS Are there other scenario where this sleep is useful ?
// Workaround: We really need a sequence enforcer in the liquidator since we don't want to
// accidentally send a similar tx again when we incorrectly believe an earlier one got forked
// off. For now, hard sleep on error to avoid the most frequent error cases.

View File

@ -141,6 +141,7 @@ impl Rebalancer {
/// Otherwise use the best of 4.
async fn token_swap_buy(
&self,
account: &MangoAccountValue,
output_mint: Pubkey,
in_amount_quote: u64,
) -> anyhow::Result<(Signature, swap::Quote)> {
@ -186,7 +187,7 @@ impl Rebalancer {
.determine_best_swap_tx(routes, quote_mint, output_mint)
.await;
let (tx_builder, route) = match best_route_res {
let (mut tx_builder, route) = match best_route_res {
Ok(x) => x,
Err(e) => {
warn!("could not use simple routes because of {}, trying with an alternative one (if configured)", e);
@ -211,6 +212,12 @@ impl Rebalancer {
}
};
let seq_check_ix = self
.mango_client
.sequence_check_instruction(&self.mango_account_address, account)
.await?;
tx_builder.append(seq_check_ix);
let sig = tx_builder
.send_and_confirm(&self.mango_client.client)
.await?;
@ -226,6 +233,7 @@ impl Rebalancer {
/// Otherwise use the best of 4.
async fn token_swap_sell(
&self,
account: &MangoAccountValue,
input_mint: Pubkey,
in_amount: u64,
) -> anyhow::Result<(Signature, swap::Quote)> {
@ -260,7 +268,7 @@ impl Rebalancer {
.determine_best_swap_tx(routes, input_mint, quote_mint)
.await;
let (tx_builder, route) = match best_route_res {
let (mut tx_builder, route) = match best_route_res {
Ok(x) => x,
Err(e) => {
warn!("could not use simple routes because of {}, trying with an alternative one (if configured)", e);
@ -283,6 +291,12 @@ impl Rebalancer {
}
};
let seq_check_ix = self
.mango_client
.sequence_check_instruction(&self.mango_account_address, account)
.await?;
tx_builder.append(seq_check_ix);
let sig = tx_builder
.send_and_confirm(&self.mango_client.client)
.await?;
@ -475,7 +489,7 @@ impl Rebalancer {
let input_amount =
buy_amount * token_price * I80F48::from_num(self.config.borrow_settle_excess);
let (txsig, route) = self
.token_swap_buy(token_mint, input_amount.to_num())
.token_swap_buy(&account, token_mint, input_amount.to_num())
.await?;
let in_token = self
.mango_client
@ -502,7 +516,7 @@ impl Rebalancer {
// To avoid creating a borrow when paying flash loan fees, sell only a fraction
let input_amount = amount * I80F48::from_num(0.99);
let (txsig, route) = self
.token_swap_sell(token_mint, input_amount.to_num::<u64>())
.token_swap_sell(&account, token_mint, input_amount.to_num::<u64>())
.await?;
let out_token = self
.mango_client
@ -556,13 +570,13 @@ impl Rebalancer {
}
#[instrument(
skip_all,
fields(
perp_market_name = perp.name,
base_lots = perp_position.base_position_lots(),
effective_lots = perp_position.effective_base_position_lots(),
quote_native = %perp_position.quote_position_native()
)
skip_all,
fields(
perp_market_name = perp.name,
base_lots = perp_position.base_position_lots(),
effective_lots = perp_position.effective_base_position_lots(),
quote_native = %perp_position.quote_position_native()
)
)]
async fn rebalance_perp(
&self,
@ -624,7 +638,7 @@ impl Rebalancer {
return Ok(true);
}
let ixs = self
let mut ixs = self
.mango_client
.perp_place_order_instruction(
account,
@ -642,6 +656,12 @@ impl Rebalancer {
)
.await?;
let seq_check_ix = self
.mango_client
.sequence_check_instruction(&self.mango_account_address, account)
.await?;
ixs.append(seq_check_ix);
let tx_builder = TransactionBuilder {
instructions: ixs.to_instructions(),
signers: vec![self.mango_client.authority.clone()],

View File

@ -623,6 +623,34 @@ impl MangoClient {
Ok(ixs)
}
/// Avoid executing same instruction multiple time
pub async fn sequence_check_instruction(
&self,
mango_account_address: &Pubkey,
mango_account: &MangoAccountValue,
) -> anyhow::Result<PreparedInstructions> {
let ixs = PreparedInstructions::from_vec(
vec![Instruction {
program_id: mango_v4::id(),
accounts: {
anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::SequenceCheck {
group: self.group(),
account: *mango_account_address,
owner: mango_account.fixed.owner,
},
None,
)
},
data: anchor_lang::InstructionData::data(&mango_v4::instruction::SequenceCheck {
expected_sequence_number: mango_account.fixed.sequence_number,
}),
}],
self.context.compute_estimates.cu_for_sequence_check,
);
Ok(ixs)
}
/// Creates token withdraw instructions for the MangoClient's account/owner.
/// The `account` state is passed in separately so changes during the tx can be
/// accounted for when deriving health accounts.

View File

@ -128,6 +128,7 @@ pub struct ComputeEstimates {
pub cu_perp_consume_events_base: u32,
pub cu_perp_consume_events_per_event: u32,
pub cu_token_update_index_and_rates: u32,
pub cu_for_sequence_check: u32,
}
impl Default for ComputeEstimates {
@ -156,6 +157,8 @@ impl Default for ComputeEstimates {
cu_perp_consume_events_base: 10_000,
cu_perp_consume_events_per_event: 18_000,
cu_token_update_index_and_rates: 90_000,
// measured around 8k, see test_basics
cu_for_sequence_check: 10_000,
}
}
}