Deposit: Compute account health

Even if it's currently unused, it will become useful in the future.

Make a function to get the list of accounts needed for health checks in
tests, which is now shared between deposit and withdraw.
This commit is contained in:
Christian Kamm 2022-03-05 18:03:57 +01:00
parent 98cf8a7cff
commit 425e22a086
2 changed files with 97 additions and 58 deletions

View File

@ -3,6 +3,7 @@ use anchor_spl::token;
use anchor_spl::token::Token;
use anchor_spl::token::TokenAccount;
use crate::error::*;
use crate::state::*;
#[derive(Accounts)]
@ -59,12 +60,37 @@ pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
let (position, position_index) = account.indexed_positions.get_mut_or_create(token_index)?;
// Update the bank and position
let mut bank = ctx.accounts.bank.load_mut()?;
let position_is_active = bank.deposit(position, amount);
let position_is_active = {
let mut bank = ctx.accounts.bank.load_mut()?;
bank.deposit(position, amount)
};
// Transfer the actual tokens
token::transfer(ctx.accounts.transfer_ctx(), amount)?;
//
// Health computation
// TODO: This will be used to disable is_bankrupt or being_liquidated
// when health recovers sufficiently
//
let active_len = account.indexed_positions.iter_active().count();
require!(
ctx.remaining_accounts.len() == active_len * 2, // banks + oracles
MangoError::SomeError
);
let banks = &ctx.remaining_accounts[0..active_len];
let oracles = &ctx.remaining_accounts[active_len..active_len * 2];
let health = compute_health(&mut account, &banks, &oracles)?;
msg!("health: {}", health);
//
// Deactivate the position only after the health check because the user passed in
// remaining_accounts for all banks/oracles, including the account that will now be
// deactivated.
// Deposits can deactivate a position if they cancel out a previous borrow.
//
if !position_is_active {
account.indexed_positions.deactivate(position_index);
}

View File

@ -65,6 +65,66 @@ fn make_instruction(
}
}
async fn get_mint_info(
account_loader: &impl ClientAccountLoader,
account: &MangoAccount,
mint: Pubkey,
) -> MintInfo {
let mint_info_pk = Pubkey::find_program_address(
&[account.group.as_ref(), b"mintinfo".as_ref(), mint.as_ref()],
&mango_v4::id(),
)
.0;
account_loader.load(&mint_info_pk).await.unwrap()
}
// all the accounts that instructions like deposit/withdraw need to compute account health
async fn derive_health_check_remaining_account_metas(
account_loader: &impl ClientAccountLoader,
account: &MangoAccount,
affected_bank: Pubkey,
) -> Vec<AccountMeta> {
let group: MangoGroup = account_loader.load(&account.group).await.unwrap();
// figure out all the banks/oracles that need to be passed for the health check
let mut banks = vec![];
let mut oracles = vec![];
for position in account.indexed_positions.iter_active() {
let mint_pk = group.tokens.infos[position.token_index as usize].mint;
let mint_info = get_mint_info(account_loader, account, mint_pk).await;
let lookup_table = account_loader
.load_bytes(&mint_info.address_lookup_table)
.await
.unwrap();
let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
}
if banks.iter().find(|&&v| v == affected_bank).is_none() {
// If there is not yet an active position for the token, we need to pass
// the bank/oracle for health check anyway.
let new_position = account
.indexed_positions
.values
.iter()
.position(|p| !p.is_active())
.unwrap();
banks.insert(new_position, affected_bank);
let affected_bank: TokenBank = account_loader.load(&affected_bank).await.unwrap();
oracles.insert(new_position, affected_bank.oracle);
}
banks
.iter()
.chain(oracles.iter())
.map(|&pubkey| AccountMeta {
pubkey,
is_writable: false,
is_signer: false,
})
.collect()
}
//
// a struct for each instruction along with its
// ClientInstruction impl
@ -95,7 +155,6 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
// load accounts, find PDAs, find remainingAccounts
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let group: MangoGroup = account_loader.load(&account.group).await.unwrap();
let bank = Pubkey::find_program_address(
&[
@ -116,48 +175,8 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
)
.0;
let account_loader = &account_loader;
let get_mint_info = move |mint_pk: Pubkey| async move {
let mint_info_pk = Pubkey::find_program_address(
&[
account.group.as_ref(),
b"mintinfo".as_ref(),
mint_pk.as_ref(),
],
&program_id,
)
.0;
let mint_info: MintInfo = account_loader.load(&mint_info_pk).await.unwrap();
mint_info
};
// figure out all the banks/oracles that need to be passed for the health check
let mut banks = vec![];
let mut oracles = vec![];
for position in account.indexed_positions.iter_active() {
let mint_pk = group.tokens.infos[position.token_index as usize].mint;
let mint_info = get_mint_info(mint_pk).await;
let lookup_table = account_loader
.load_bytes(&mint_info.address_lookup_table)
.await
.unwrap();
let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
}
if banks.iter().find(|&&v| v == bank).is_none() {
// If there is not yet an active position for the token, we need to pass
// the bank/oracle for health check anyway.
let new_position = account
.indexed_positions
.values
.iter()
.position(|p| !p.is_active())
.unwrap();
let mint_info = get_mint_info(token_account.mint).await;
banks.insert(new_position, bank);
oracles.insert(new_position, mint_info.oracle);
}
let health_check_metas =
derive_health_check_remaining_account_metas(&account_loader, &account, bank).await;
let accounts = Self::Accounts {
group: account.group,
@ -170,18 +189,7 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
};
let mut instruction = make_instruction(program_id, &accounts, instruction);
instruction
.accounts
.extend(
banks
.iter()
.chain(oracles.iter())
.map(|&pubkey| AccountMeta {
pubkey,
is_writable: false,
is_signer: false,
}),
);
instruction.accounts.extend(health_check_metas.into_iter());
(accounts, instruction)
}
@ -234,6 +242,9 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
)
.0;
let health_check_metas =
derive_health_check_remaining_account_metas(&account_loader, &account, bank).await;
let accounts = Self::Accounts {
group: account.group,
account: self.account,
@ -244,7 +255,9 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
token_program: Token::id(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
let mut instruction = make_instruction(program_id, &accounts, instruction);
instruction.accounts.extend(health_check_metas.into_iter());
(accounts, instruction)
}