multiple banks (#82)

* multiple banks

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* test for closing multiple banks for a registered token

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fix deregister_token

* update idl

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Co-authored-by: Christian Kamm <mail@ckamm.de>
This commit is contained in:
microwavedcola1 2022-06-27 11:27:17 +02:00 committed by GitHub
parent 0758125db8
commit 9fc8a5a56a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1092 additions and 201 deletions

View File

@ -9,4 +9,4 @@ members = [
[patch.crates-io]
# for gzip encoded responses
jsonrpc-core-client = { git = "https://github.com/ckamm/jsonrpc.git", branch = "ckamm/http-with-gzip" }
jsonrpc-core-client = { git = "https://github.com/ckamm/jsonrpc.git", branch = "ckamm/http-with-gzip" }

View File

@ -36,8 +36,8 @@ pub struct MangoClient {
pub group: Pubkey,
// TODO: future: this may not scale if there's thousands of mints, probably some function
// wrapping getMultipleAccounts is needed (or bettew: we provide this data as a service)
pub banks_cache: HashMap<String, (Pubkey, Bank)>,
pub banks_cache_by_token_index: HashMap<TokenIndex, (Pubkey, Bank)>,
pub banks_cache: HashMap<String, Vec<(Pubkey, Bank)>>,
pub banks_cache_by_token_index: HashMap<TokenIndex, Vec<(Pubkey, Bank)>>,
pub mint_infos_cache: HashMap<Pubkey, (Pubkey, MintInfo, Mint)>,
pub mint_infos_cache_by_token_index: HashMap<TokenIndex, (Pubkey, MintInfo, Mint)>,
pub serum3_markets_cache: HashMap<String, (Pubkey, Serum3Market)>,
@ -160,8 +160,14 @@ impl MangoClient {
encoding: None,
})])?;
for (k, v) in bank_tuples {
banks_cache.insert(v.name().to_owned(), (k, v));
banks_cache_by_token_index.insert(v.token_index, (k, v));
banks_cache
.entry(v.name().to_owned())
.or_insert_with(|| Vec::new())
.push((k, v));
banks_cache_by_token_index
.entry(v.token_index)
.or_insert_with(|| Vec::new())
.push((k, v));
}
// mintinfo cache
@ -243,6 +249,13 @@ impl MangoClient {
})
}
pub fn get_mint_info(&self, token_index: &TokenIndex) -> Pubkey {
self.mint_infos_cache_by_token_index
.get(token_index)
.unwrap()
.0
}
pub fn client(&self) -> Client {
Client::new_with_options(
self.cluster.clone(),
@ -302,7 +315,7 @@ impl MangoClient {
// 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]);
banks.push(mint_info.bank);
banks.push(mint_info.first_bank());
oracles.push(mint_info.oracle);
}
if let Some(affected_bank) = affected_bank {
@ -387,7 +400,7 @@ impl MangoClient {
// 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]);
banks.push((mint_info.bank, writable_bank));
banks.push((mint_info.first_bank(), writable_bank));
oracles.push(mint_info.oracle);
}
@ -431,7 +444,7 @@ impl MangoClient {
token_name: &str,
amount: u64,
) -> Result<Signature, anchor_client::ClientError> {
let bank = self.banks_cache.get(token_name).unwrap();
let bank = self.banks_cache.get(token_name).unwrap().get(0).unwrap();
let mint_info: MintInfo = self.mint_infos_cache.get(&bank.1.mint).unwrap().1;
let health_check_metas =
@ -471,7 +484,7 @@ impl MangoClient {
&self,
token_name: &str,
) -> Result<pyth_sdk_solana::Price, anyhow::Error> {
let bank = self.banks_cache.get(token_name).unwrap().1;
let bank = self.banks_cache.get(token_name).unwrap().get(0).unwrap().1;
let data = self
.program()
@ -652,10 +665,10 @@ impl MangoClient {
group: self.group(),
account: self.mango_account_cache.0,
open_orders,
quote_bank: quote_info.bank,
quote_vault: quote_info.vault,
base_bank: base_info.bank,
base_vault: base_info.vault,
quote_bank: quote_info.first_bank(),
quote_vault: quote_info.first_vault(),
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: serum3_market.0,
serum_program: serum3_market.1.serum_program,
serum_market_external: serum3_market.1.serum_market_external,
@ -730,10 +743,10 @@ impl MangoClient {
group: self.group(),
account: self.mango_account_cache.0,
open_orders,
quote_bank: quote_info.bank,
quote_vault: quote_info.vault,
base_bank: base_info.bank,
base_vault: base_info.vault,
quote_bank: quote_info.first_bank(),
quote_vault: quote_info.first_vault(),
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: serum3_market.0,
serum_program: serum3_market.1.serum_program,
serum_market_external: serum3_market.1.serum_market_external,

View File

@ -4,7 +4,7 @@ use crate::MangoClient;
use anchor_lang::__private::bytemuck::cast_ref;
use futures::Future;
use mango_v4::state::{Bank, EventQueue, EventType, FillEvent, OutEvent, PerpMarket};
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
@ -18,7 +18,12 @@ pub async fn runner(
let handles1 = mango_client
.banks_cache
.values()
.map(|(pk, bank)| loop_update_index(mango_client.clone(), *pk, *bank))
.map(|banks_for_a_token| {
loop_update_index(
mango_client.clone(),
banks_for_a_token.get(0).unwrap().1.token_index,
)
})
.collect::<Vec<_>>();
let handles2 = mango_client
@ -43,31 +48,54 @@ pub async fn runner(
Ok(())
}
pub async fn loop_update_index(mango_client: Arc<MangoClient>, pk: Pubkey, bank: Bank) {
pub async fn loop_update_index(mango_client: Arc<MangoClient>, token_index: TokenIndex) {
let mut interval = time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
let client = mango_client.clone();
let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
let mint_info = client.get_mint_info(&token_index);
let banks_for_a_token = client.banks_cache_by_token_index.get(&token_index).unwrap();
let token_name = banks_for_a_token.get(0).unwrap().1.name();
let bank_pubkeys_for_a_token = banks_for_a_token
.into_iter()
.map(|bank| bank.0)
.collect::<Vec<Pubkey>>();
let sig_result = client
.program()
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::UpdateIndex { bank: pk },
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::UpdateIndex {},
),
.instruction({
let mut ix = Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::UpdateIndex { mint_info },
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::UpdateIndex {},
),
};
let mut foo = bank_pubkeys_for_a_token
.iter()
.map(|bank_pubkey| AccountMeta {
pubkey: *bank_pubkey,
is_signer: false,
is_writable: false,
})
.collect::<Vec<_>>();
ix.accounts.append(&mut foo);
ix
})
.send();
if let Err(e) = sig_result {
log::error!("{:?}", e)
} else {
log::info!("update_index {} {:?}", bank.name(), sig_result.unwrap())
log::info!("update_index {} {:?}", token_name, sig_result.unwrap())
}
Ok(())

View File

@ -6,7 +6,10 @@ use std::{
use fixed::types::I80F48;
use futures::Future;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::{
instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side},
state::Bank,
};
use tokio::time;
@ -86,7 +89,13 @@ fn ensure_oo(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error> {
fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error> {
let mango_account = mango_client.get_account()?.1;
for (_, bank) in mango_client.banks_cache.values() {
let banks: Vec<Bank> = mango_client
.banks_cache
.values()
.map(|vec| vec.get(0).unwrap().1)
.collect::<Vec<Bank>>();
for bank in banks {
let mint = &mango_client.mint_infos_cache.get(&bank.mint).unwrap().2;
let desired_balance = I80F48::from_num(10_000 * 10u64.pow(mint.decimals as u32));
@ -94,9 +103,9 @@ fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error>
let deposit_native = match token_account_opt {
Some(token_account) => {
let native = token_account.native(bank);
let native = token_account.native(&bank);
let ui = token_account.ui(bank, mint);
let ui = token_account.ui(&bank, mint);
log::info!("Current balance {} {}", ui, bank.name());
if native < I80F48::ZERO {

View File

@ -53,9 +53,9 @@ pub fn compute_health_(
.unwrap();
banks.push((
mint_info.bank,
mint_info.first_bank(),
chain_data
.account(&mint_info.bank)
.account(&mint_info.first_bank())
.expect("chain data is missing bank"),
));
oracles.push((
@ -150,7 +150,8 @@ pub fn process_accounts<'a>(
let mint_info_pk = mint_infos.get(&token.token_index).expect("always Ok");
let mint_info =
load_mango_account_from_chain::<MintInfo>(chain_data, mint_info_pk)?;
let bank = load_mango_account_from_chain::<Bank>(chain_data, &mint_info.bank)?;
let bank =
load_mango_account_from_chain::<Bank>(chain_data, &mint_info.first_bank())?;
let oracle = chain_data.account(&mint_info.oracle)?;
let price = oracle_price(
&KeyedAccountSharedData::new(mint_info.oracle, oracle.clone()),

View File

@ -209,7 +209,11 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
am.is_signer = true;
// this is the data we'll need later to build the PDA account signer seeds
bank_signer_data.push((bank.token_index.to_le_bytes(), [bank.bump]));
bank_signer_data.push((
bank.token_index.to_le_bytes(),
bank.bank_num.to_le_bytes(),
[bank.bump],
));
}
}
}
@ -247,11 +251,12 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
let group_key = ctx.accounts.group.key();
let signers = bank_signer_data
.iter()
.map(|(token_index, bump)| {
.map(|(token_index, bank_num, bump)| {
[
group_key.as_ref(),
b"Bank".as_ref(),
&token_index[..],
&bank_num[..],
&bump[..],
]
})

View File

@ -27,6 +27,7 @@ pub use serum3_place_order::*;
pub use serum3_register_market::*;
pub use serum3_settle_funds::*;
pub use set_stub_oracle::*;
pub use token_add_bank::*;
pub use token_deposit::*;
pub use token_deregister::*;
pub use token_register::*;
@ -62,6 +63,7 @@ mod serum3_place_order;
mod serum3_register_market;
mod serum3_settle_funds;
mod set_stub_oracle;
mod token_add_bank;
mod token_deposit;
mod token_deregister;
mod token_register;

View File

@ -0,0 +1,118 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};
// TODO: ALTs are unavailable
//use crate::address_lookup_table;
use crate::state::*;
#[derive(Accounts)]
#[instruction(token_index: TokenIndex, bank_num: u64)]
pub struct TokenAddBank<'info> {
#[account(
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
pub mint: Account<'info, Mint>,
#[account(
constraint = existing_bank.load()?.token_index == token_index,
has_one = group,
has_one = mint,
)]
pub existing_bank: AccountLoader<'info, Bank>,
#[account(
init,
// using the token_index in this seed guards against reusing it
seeds = [group.key().as_ref(), b"Bank".as_ref(), &token_index.to_le_bytes(), &bank_num.to_le_bytes()],
bump,
payer = payer,
space = 8 + std::mem::size_of::<Bank>(),
)]
pub bank: AccountLoader<'info, Bank>,
#[account(
init,
seeds = [group.key().as_ref(), b"Vault".as_ref(), &token_index.to_le_bytes(), &bank_num.to_le_bytes()],
bump,
token::authority = group,
token::mint = mint,
payer = payer
)]
pub vault: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [group.key().as_ref(), b"MintInfo".as_ref(), mint.key().as_ref()],
bump
)]
pub mint_info: AccountLoader<'info, MintInfo>,
// Creating an address lookup table needs a recent valid slot as an
// input argument. That makes creating ALTs from governance instructions
// impossible. Hence the ALT that this instruction uses must be created
// externally and the admin is responsible for placing banks/oracles into
// sensible address lookup tables.
// constraint: must be created, have the admin authority and have free space
// TODO: ALTs are unavailable
//#[account(mut)]
//pub address_lookup_table: UncheckedAccount<'info>, // TODO: wrapper?
#[account(mut)]
pub payer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
// TODO: ALTs are unavailable
//pub address_lookup_table_program: UncheckedAccount<'info>, // TODO: force address?
pub rent: Sysvar<'info, Rent>,
}
// TODO: should this be "configure_mint", we pass an explicit index, and allow
// overwriting config as long as the mint account stays the same?
#[allow(clippy::too_many_arguments)]
pub fn token_add_bank(
ctx: Context<TokenAddBank>,
_token_index: TokenIndex,
bank_num: u64,
) -> Result<()> {
// TODO: Error if mint is already configured (technically, init of vault will fail)
let existing_bank = ctx.accounts.existing_bank.load()?;
let mut bank = ctx.accounts.bank.load_init()?;
*bank = Bank::from_existing_bank(&existing_bank, ctx.accounts.vault.key(), bank_num);
// TODO: ALTs are unavailable
// let alt_previous_size =
// address_lookup_table::addresses(&ctx.accounts.address_lookup_table.try_borrow_data()?)
// .len();
// let address_lookup_table = Pubkey::default();
// let alt_previous_size = 0;
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
let free_slot = mint_info
.banks
.iter()
.position(|bank| bank == &Pubkey::default())
.unwrap();
require_eq!(bank_num as usize, free_slot);
mint_info.banks[free_slot] = ctx.accounts.bank.key();
mint_info.vaults[free_slot] = ctx.accounts.vault.key();
// TODO: ALTs are unavailable
/*
address_lookup_table::extend(
ctx.accounts.address_lookup_table.to_account_info(),
// TODO: is using the admin as ALT authority a good idea?
ctx.accounts.admin.to_account_info(),
ctx.accounts.payer.to_account_info(),
&[],
vec![ctx.accounts.bank.key(), ctx.accounts.oracle.key()],
)?;
*/
Ok(())
}

View File

@ -1,9 +1,11 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, CloseAccount, Token, TokenAccount};
use anchor_spl::token::{self, CloseAccount, Token};
use crate::state::*;
use crate::{accounts_zerocopy::LoadZeroCopyRef, state::*};
use anchor_lang::AccountsClose;
#[derive(Accounts)]
#[instruction(token_index: TokenIndex)]
pub struct TokenDeregister<'info> {
#[account(
constraint = group.load()?.testing == 1,
@ -12,23 +14,11 @@ pub struct TokenDeregister<'info> {
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
// match bank to group
// match bank to vault
#[account(
mut,
has_one = group,
has_one = vault,
close = sol_destination
)]
pub bank: AccountLoader<'info, Bank>,
#[account(mut)]
pub vault: Account<'info, TokenAccount>,
// match mint info to bank
#[account(
mut,
has_one = bank,
has_one = group,
constraint = mint_info.load()?.token_index == token_index,
close = sol_destination
)]
pub mint_info: AccountLoader<'info, MintInfo>,
@ -41,21 +31,63 @@ pub struct TokenDeregister<'info> {
}
#[allow(clippy::too_many_arguments)]
pub fn token_deregister(ctx: Context<TokenDeregister>) -> Result<()> {
pub fn token_deregister<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, TokenDeregister<'info>>,
token_index: TokenIndex,
) -> Result<()> {
let mint_info = ctx.accounts.mint_info.load()?;
{
let total_banks = mint_info
.banks
.iter()
.filter(|bank| *bank != &Pubkey::default())
.count();
require_eq!(total_banks * 2, ctx.remaining_accounts.len());
}
let group = ctx.accounts.group.load()?;
let group_seeds = group_seeds!(group);
let cpi_accounts = CloseAccount {
account: ctx.accounts.vault.to_account_info(),
destination: ctx.accounts.sol_destination.to_account_info(),
authority: ctx.accounts.group.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
token::close_account(CpiContext::new_with_signer(
cpi_program,
cpi_accounts,
&[group_seeds],
))?;
ctx.accounts.vault.exit(ctx.program_id)?;
// todo: use itertools::chunks(2)
for i in (0..ctx.remaining_accounts.len()).step_by(2) {
let vault_ai = &ctx.remaining_accounts[i + 1];
let bank_ai = &ctx.remaining_accounts[i];
require_eq!(bank_ai.key(), mint_info.banks[i / 2]);
require_eq!(vault_ai.key(), mint_info.vaults[i / 2]);
// todo: these checks might be superfluous, after above 2 checks
{
let bank = bank_ai.load::<Bank>()?;
require_keys_eq!(bank.group, ctx.accounts.group.key());
require_eq!(bank.token_index, token_index);
require_keys_eq!(bank.vault, vault_ai.key());
}
// note: vault seems to need closing before bank, weird solana oddity
// todo: add test to see if we can even close more than one bank in same ix
// close vault
let cpi_accounts = CloseAccount {
account: vault_ai.to_account_info(),
destination: ctx.accounts.sol_destination.to_account_info(),
authority: ctx.accounts.group.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
token::close_account(CpiContext::new_with_signer(
cpi_program,
cpi_accounts,
&[group_seeds],
))?;
vault_ai.exit(ctx.program_id)?;
}
// Close banks in a second step, because the cpi calls above don't like when someone
// else touches sol_destination.lamports in between.
for i in (0..ctx.remaining_accounts.len()).step_by(2) {
let bank_ai = &ctx.remaining_accounts[i];
let bank_al: AccountLoader<Bank> = AccountLoader::try_from(bank_ai)?;
bank_al.close(ctx.accounts.sol_destination.to_account_info())?;
}
Ok(())
}

View File

@ -9,10 +9,10 @@ use crate::error::*;
use crate::state::*;
use crate::util::fill16_from_str;
const INDEX_START: I80F48 = I80F48!(1_000_000);
pub const INDEX_START: I80F48 = I80F48!(1_000_000);
#[derive(Accounts)]
#[instruction(token_index: TokenIndex)]
#[instruction(token_index: TokenIndex, bank_num: u64)]
pub struct TokenRegister<'info> {
#[account(
has_one = admin,
@ -25,7 +25,7 @@ pub struct TokenRegister<'info> {
#[account(
init,
// using the token_index in this seed guards against reusing it
seeds = [group.key().as_ref(), b"Bank".as_ref(), &token_index.to_le_bytes()],
seeds = [group.key().as_ref(), b"Bank".as_ref(), &token_index.to_le_bytes(), &bank_num.to_le_bytes()],
bump,
payer = payer,
space = 8 + std::mem::size_of::<Bank>(),
@ -34,7 +34,7 @@ pub struct TokenRegister<'info> {
#[account(
init,
seeds = [group.key().as_ref(), b"Vault".as_ref(), &token_index.to_le_bytes()],
seeds = [group.key().as_ref(), b"Vault".as_ref(), &token_index.to_le_bytes(), &bank_num.to_le_bytes()],
bump,
token::authority = group,
token::mint = mint,
@ -90,6 +90,7 @@ pub struct InterestRateParams {
pub fn token_register(
ctx: Context<TokenRegister>,
token_index: TokenIndex,
bank_num: u64,
name: String,
oracle_config: OracleConfig,
interest_rate_params: InterestRateParams,
@ -103,6 +104,8 @@ pub fn token_register(
) -> Result<()> {
// TODO: Error if mint is already configured (technically, init of vault will fail)
require_eq!(bank_num, 0);
let mut bank = ctx.accounts.bank.load_init()?;
*bank = Bank {
name: fill16_from_str(name)?,
@ -115,6 +118,8 @@ pub fn token_register(
borrow_index: INDEX_START,
indexed_total_deposits: I80F48::ZERO,
indexed_total_borrows: I80F48::ZERO,
indexed_deposits: I80F48::ZERO,
indexed_borrows: I80F48::ZERO,
last_updated: Clock::get()?.unix_timestamp,
// TODO: add a require! verifying relation between the parameters
util0: I80F48::from_num(interest_rate_params.util0),
@ -133,8 +138,9 @@ pub fn token_register(
dust: I80F48::ZERO,
token_index,
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
reserved: Default::default(),
mint_decimals: ctx.accounts.mint.decimals,
bank_num: 0,
reserved: Default::default(),
};
// TODO: ALTs are unavailable
@ -147,8 +153,8 @@ pub fn token_register(
*mint_info = MintInfo {
group: ctx.accounts.group.key(),
mint: ctx.accounts.mint.key(),
bank: ctx.accounts.bank.key(),
vault: ctx.accounts.vault.key(),
banks: Default::default(),
vaults: Default::default(),
oracle: ctx.accounts.oracle.key(),
address_lookup_table,
token_index,
@ -157,6 +163,9 @@ pub fn token_register(
reserved: Default::default(),
};
mint_info.banks[0] = ctx.accounts.bank.key();
mint_info.vaults[0] = ctx.accounts.vault.key();
// TODO: ALTs are unavailable
/*
address_lookup_table::extend(

View File

@ -1,20 +1,88 @@
use anchor_lang::prelude::*;
use crate::state::Bank;
use crate::{
accounts_zerocopy::{LoadMutZeroCopyRef, LoadZeroCopyRef},
error::MangoError,
state::{Bank, MintInfo},
};
use checked_math as cm;
use fixed::types::I80F48;
#[derive(Accounts)]
pub struct UpdateIndex<'info> {
// TODO: should we support arbitrary number of banks with remaining accounts?
// ix - consumed 17641 of 101000 compute units, so we have a lot of compute
#[account(mut)]
pub bank: AccountLoader<'info, Bank>,
pub mint_info: AccountLoader<'info, MintInfo>,
}
pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
// TODO: should we enforce a minimum window between 2 update_index ix calls?
let now_ts = Clock::get()?.unix_timestamp;
let mut bank = ctx.accounts.bank.load_mut()?;
bank.update_index(now_ts)?;
pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
let mint_info = ctx.accounts.mint_info.load()?;
let total_banks = mint_info
.banks
.iter()
.filter(|bank| *bank != &Pubkey::default())
.count();
require_eq!(total_banks, ctx.remaining_accounts.len());
let all_banks = ctx.remaining_accounts;
check_banks(all_banks, &mint_info)?;
let mut indexed_total_deposits = I80F48::ZERO;
let mut indexed_total_borrows = I80F48::ZERO;
for ai in all_banks.iter() {
let bank = ai.load::<Bank>()?;
indexed_total_deposits = cm!(indexed_total_deposits + bank.indexed_deposits);
indexed_total_borrows = cm!(indexed_total_borrows + bank.indexed_borrows);
}
let now_ts = Clock::get()?.unix_timestamp;
let (diff_ts, deposit_index, borrow_index) = {
let mut some_bank = all_banks[0].load_mut::<Bank>()?;
// TODO: should we enforce a minimum window between 2 update_index ix calls?
let diff_ts = I80F48::from_num(now_ts - some_bank.last_updated);
let (deposit_index, borrow_index) =
some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?;
(diff_ts, deposit_index, borrow_index)
};
msg!("indexed_total_deposits {}", indexed_total_deposits);
msg!("indexed_total_borrows {}", indexed_total_borrows);
msg!("diff_ts {}", diff_ts);
msg!("deposit_index {}", deposit_index);
msg!("borrow_index {}", borrow_index);
for ai in all_banks.iter() {
let mut bank = ai.load_mut::<Bank>()?;
bank.indexed_total_deposits = indexed_total_deposits;
bank.indexed_total_borrows = indexed_total_borrows;
bank.last_updated = now_ts;
bank.charge_loan_fee(diff_ts);
bank.deposit_index = deposit_index;
bank.borrow_index = borrow_index;
}
Ok(())
}
fn check_banks(all_banks: &[AccountInfo], mint_info: &MintInfo) -> Result<()> {
for (idx, ai) in all_banks.iter().enumerate() {
match ai.load::<Bank>() {
Ok(bank) => {
if mint_info.token_index != bank.token_index
|| mint_info.group != bank.group
// todo: just below check should be enough, above 2 checks are superfluous and defensive
|| mint_info.banks[idx] != ai.key()
{
return Err(error!(MangoError::SomeError));
}
}
Err(error) => return Err(error),
}
}
Ok(())
}

View File

@ -42,6 +42,7 @@ pub mod mango_v4 {
pub fn token_register(
ctx: Context<TokenRegister>,
token_index: TokenIndex,
bank_num: u64,
name: String,
oracle_config: OracleConfig,
interest_rate_params: InterestRateParams,
@ -56,6 +57,7 @@ pub mod mango_v4 {
instructions::token_register(
ctx,
token_index,
bank_num,
name,
oracle_config,
interest_rate_params,
@ -69,8 +71,20 @@ pub mod mango_v4 {
)
}
pub fn token_deregister(ctx: Context<TokenDeregister>) -> Result<()> {
instructions::token_deregister(ctx)
#[allow(clippy::too_many_arguments)]
pub fn token_add_bank(
ctx: Context<TokenAddBank>,
token_index: TokenIndex,
bank_num: u64,
) -> Result<()> {
instructions::token_add_bank(ctx, token_index, bank_num)
}
pub fn token_deregister<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, TokenDeregister<'info>>,
token_index: TokenIndex,
) -> Result<()> {
instructions::token_deregister(ctx, token_index)
}
pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {

View File

@ -5,6 +5,7 @@ use anchor_lang::prelude::*;
use fixed::types::I80F48;
use fixed_macro::types::I80F48;
use static_assertions::const_assert_eq;
use std::mem::size_of;
pub const DAY: I80F48 = I80F48!(86400);
@ -30,6 +31,10 @@ pub struct Bank {
pub indexed_total_deposits: I80F48,
pub indexed_total_borrows: I80F48,
/// deposits/borrows for this bank
pub indexed_deposits: I80F48,
pub indexed_borrows: I80F48,
pub last_updated: i64,
pub util0: I80F48,
pub rate0: I80F48,
@ -66,10 +71,15 @@ pub struct Bank {
pub mint_decimals: u8,
pub reserved: [u8; 4],
pub bank_num: u64,
// TODO: add space for an oracle which services interest rate for the bank's mint
// interest rate tied to oracle might help reduce spreads between deposits and borrows
}
const_assert_eq!(size_of::<Bank>(), 16 + 32 * 4 + 8 + 16 * 19 + 2 + 1 + 1 + 4);
const_assert_eq!(
size_of::<Bank>(),
16 + 32 * 4 + 8 + 16 * 21 + 2 + 1 + 1 + 4 + 8
);
const_assert_eq!(size_of::<Bank>() % 8, 0);
impl std::fmt::Debug for Bank {
@ -80,10 +90,13 @@ impl std::fmt::Debug for Bank {
.field("mint", &self.mint)
.field("vault", &self.vault)
.field("oracle", &self.oracle)
.field("oracle_config", &self.oracle_config)
.field("deposit_index", &self.deposit_index)
.field("borrow_index", &self.borrow_index)
.field("indexed_total_deposits", &self.indexed_total_deposits)
.field("indexed_total_borrows", &self.indexed_total_borrows)
.field("indexed_deposits", &self.indexed_deposits)
.field("indexed_borrows", &self.indexed_borrows)
.field("last_updated", &self.last_updated)
.field("util0", &self.util0)
.field("rate0", &self.rate0)
@ -106,6 +119,43 @@ impl std::fmt::Debug for Bank {
}
impl Bank {
pub fn from_existing_bank(existing_bank: &Bank, vault: Pubkey, bank_num: u64) -> Self {
Self {
name: existing_bank.name,
group: existing_bank.group,
mint: existing_bank.mint,
vault: vault,
oracle: existing_bank.oracle,
oracle_config: existing_bank.oracle_config,
deposit_index: existing_bank.deposit_index,
borrow_index: existing_bank.borrow_index,
indexed_total_deposits: existing_bank.indexed_total_deposits,
indexed_total_borrows: existing_bank.indexed_total_borrows,
indexed_deposits: I80F48::ZERO,
indexed_borrows: I80F48::ZERO,
last_updated: existing_bank.last_updated,
util0: existing_bank.util0,
rate0: existing_bank.rate0,
util1: existing_bank.util1,
rate1: existing_bank.rate1,
max_rate: existing_bank.max_rate,
collected_fees_native: existing_bank.collected_fees_native,
loan_origination_fee_rate: existing_bank.loan_origination_fee_rate,
loan_fee_rate: existing_bank.loan_fee_rate,
maint_asset_weight: existing_bank.maint_asset_weight,
init_asset_weight: existing_bank.init_asset_weight,
maint_liab_weight: existing_bank.maint_liab_weight,
init_liab_weight: existing_bank.init_liab_weight,
liquidation_fee: existing_bank.liquidation_fee,
dust: I80F48::ZERO,
token_index: existing_bank.token_index,
bump: existing_bank.bump,
mint_decimals: existing_bank.mint_decimals,
reserved: Default::default(),
bank_num,
}
}
pub fn name(&self) -> &str {
std::str::from_utf8(&self.name)
.unwrap()
@ -120,6 +170,14 @@ impl Bank {
self.deposit_index * self.indexed_total_deposits
}
pub fn native_borrows(&self) -> I80F48 {
self.borrow_index * self.indexed_borrows
}
pub fn native_deposits(&self) -> I80F48 {
self.deposit_index * self.indexed_deposits
}
/// Returns whether the position is active
///
/// native_amount must be >= 0
@ -139,21 +197,20 @@ impl Bank {
let new_indexed_value = cm!(position.indexed_position + indexed_change);
if new_indexed_value.is_negative() {
// pay back borrows only, leaving a negative position
self.indexed_total_borrows = cm!(self.indexed_total_borrows - indexed_change);
position.indexed_position = new_indexed_value;
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
self.indexed_borrows = cm!(self.indexed_borrows - indexed_change);
position.indexed_position = cm!(position.indexed_position + indexed_change);
return Ok(true);
} else if new_native_position < I80F48::ONE && !position.is_in_use() {
// if there's less than one token deposited, zero the position
self.dust = cm!(self.dust + new_native_position);
self.indexed_total_borrows =
cm!(self.indexed_total_borrows + position.indexed_position);
self.indexed_borrows = cm!(self.indexed_borrows + position.indexed_position);
position.indexed_position = I80F48::ZERO;
return Ok(false);
}
// pay back all borrows
self.indexed_total_borrows =
cm!(self.indexed_total_borrows + position.indexed_position); // position.value is negative
self.indexed_borrows = cm!(self.indexed_borrows + position.indexed_position); // position.value is negative
position.indexed_position = I80F48::ZERO;
// deposit the rest
native_amount = cm!(native_amount + native_position);
@ -164,7 +221,7 @@ impl Bank {
// we want to ensure that users can withdraw the same amount they have deposited, so
// (amount/index + delta)*index >= amount is a better guarantee.
let indexed_change = cm!(native_amount / self.deposit_index + I80F48::DELTA);
self.indexed_total_deposits = cm!(self.indexed_total_deposits + indexed_change);
self.indexed_deposits = cm!(self.indexed_deposits + indexed_change);
position.indexed_position = cm!(position.indexed_position + indexed_change);
Ok(true)
@ -212,22 +269,20 @@ impl Bank {
if new_native_position < I80F48::ONE && !position.is_in_use() {
// zero the account collecting the leftovers in `dust`
self.dust = cm!(self.dust + new_native_position);
self.indexed_total_deposits =
cm!(self.indexed_total_deposits - position.indexed_position);
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
position.indexed_position = I80F48::ZERO;
return Ok(false);
} else {
// withdraw some deposits leaving a positive balance
let indexed_change = cm!(native_amount / self.deposit_index);
self.indexed_total_deposits = cm!(self.indexed_total_deposits - indexed_change);
self.indexed_deposits = cm!(self.indexed_deposits - indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
return Ok(true);
}
}
// withdraw all deposits
self.indexed_total_deposits =
cm!(self.indexed_total_deposits - position.indexed_position);
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
position.indexed_position = I80F48::ZERO;
// borrow the rest
native_amount = -new_native_position;
@ -239,7 +294,7 @@ impl Bank {
// add to borrows
let indexed_change = cm!(native_amount / self.borrow_index);
self.indexed_total_borrows = cm!(self.indexed_total_borrows + indexed_change);
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
Ok(true)
@ -256,7 +311,7 @@ impl Bank {
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
let indexed_change = cm!(loan_origination_fee / self.borrow_index);
self.indexed_total_borrows = cm!(self.indexed_total_borrows + indexed_change);
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
Ok(())
@ -290,26 +345,27 @@ impl Bank {
// Borrows continously expose insurance fund to risk, collect fees from borrowers
pub fn charge_loan_fee(&mut self, diff_ts: I80F48) {
let native_total_borrows_old = self.native_total_borrows();
self.indexed_total_borrows =
cm!((self.indexed_total_borrows
* (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR))));
self.collected_fees_native = cm!(
self.collected_fees_native + self.native_total_borrows() - native_total_borrows_old
);
let native_borrows_old = self.native_borrows();
self.indexed_borrows =
cm!((self.indexed_borrows * (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR))));
self.collected_fees_native =
cm!(self.collected_fees_native + self.native_borrows() - native_borrows_old);
}
pub fn update_index(&mut self, now_ts: i64) -> Result<()> {
let diff_ts = I80F48::from_num(now_ts - self.last_updated);
self.last_updated = now_ts;
pub fn compute_index(
&mut self,
indexed_total_deposits: I80F48,
indexed_total_borrows: I80F48,
diff_ts: I80F48,
) -> Result<(I80F48, I80F48)> {
// compute index based on utilization
let native_total_deposits = self.deposit_index * indexed_total_deposits;
let native_total_borrows = self.borrow_index * indexed_total_borrows;
self.charge_loan_fee(diff_ts);
// Update index based on utilization
let utilization = if self.native_total_deposits() == I80F48::ZERO {
let utilization = if native_total_deposits == I80F48::ZERO {
I80F48::ZERO
} else {
cm!(self.native_total_borrows() / self.native_total_deposits())
cm!(native_total_borrows / native_total_deposits)
};
let interest_rate = self.compute_interest_rate(utilization);
@ -317,15 +373,20 @@ impl Bank {
let borrow_interest: I80F48 = cm!(interest_rate * diff_ts);
let deposit_interest = cm!(borrow_interest * utilization);
// msg!("utilization {}", utilization);
// msg!("interest_rate {}", interest_rate);
// msg!("borrow_interest {}", borrow_interest);
// msg!("deposit_interest {}", deposit_interest);
if borrow_interest <= I80F48::ZERO || deposit_interest <= I80F48::ZERO {
return Ok(());
return Ok((self.deposit_index, self.borrow_index));
}
self.borrow_index = cm!((self.borrow_index * borrow_interest) / YEAR + self.borrow_index);
self.deposit_index =
let borrow_index = cm!((self.borrow_index * borrow_interest) / YEAR + self.borrow_index);
let deposit_index =
cm!((self.deposit_index * deposit_interest) / YEAR + self.deposit_index);
Ok(())
Ok((deposit_index, borrow_index))
}
/// returns the current interest rate in APR
@ -375,6 +436,7 @@ macro_rules! bank_seeds {
$bank.group.as_ref(),
b"Bank".as_ref(),
$bank.token_index.to_le_bytes(),
&bank.bank_num.to_le_bytes(),
&[$bank.bump],
]
};
@ -449,9 +511,9 @@ mod tests {
account.indexed_position = indexed(I80F48::from_num(start), &bank);
if start >= 0.0 {
bank.indexed_total_deposits = account.indexed_position;
bank.indexed_deposits = account.indexed_position;
} else {
bank.indexed_total_borrows = -account.indexed_position;
bank.indexed_borrows = -account.indexed_position;
}
// get the rounded start value
@ -483,11 +545,11 @@ mod tests {
assert!((account.indexed_position - expected_indexed).abs() <= epsilon);
if account.indexed_position.is_positive() {
assert_eq!(bank.indexed_total_deposits, account.indexed_position);
assert_eq!(bank.indexed_total_borrows, I80F48::ZERO);
assert_eq!(bank.indexed_deposits, account.indexed_position);
assert_eq!(bank.indexed_borrows, I80F48::ZERO);
} else {
assert_eq!(bank.indexed_total_deposits, I80F48::ZERO);
assert_eq!(bank.indexed_total_borrows, -account.indexed_position);
assert_eq!(bank.indexed_deposits, I80F48::ZERO);
assert_eq!(bank.indexed_borrows, -account.indexed_position);
}
}
}

View File

@ -4,17 +4,20 @@ use std::mem::size_of;
use super::TokenIndex;
pub const MAX_BANKS: usize = 6;
// This struct describes which address lookup table can be used to pass
// the accounts that are relevant for this mint. The idea is that clients
// can load this account to figure out which address maps to use when calling
// instructions that need banks/oracles for all active positions.
#[account(zero_copy)]
#[derive(Debug)]
pub struct MintInfo {
// TODO: none of these pubkeys are needed, remove?
pub group: Pubkey,
pub mint: Pubkey,
pub bank: Pubkey,
pub vault: Pubkey,
pub banks: [Pubkey; MAX_BANKS],
pub vaults: [Pubkey; MAX_BANKS],
pub oracle: Pubkey,
pub address_lookup_table: Pubkey,
@ -26,5 +29,19 @@ pub struct MintInfo {
pub reserved: [u8; 4],
}
const_assert_eq!(size_of::<MintInfo>(), 6 * 32 + 2 + 2 + 4);
const_assert_eq!(
size_of::<MintInfo>(),
MAX_BANKS * 2 * 32 + 4 * 32 + 2 + 2 + 4
);
const_assert_eq!(size_of::<MintInfo>() % 8, 0);
impl MintInfo {
// used for health purposes
pub fn first_bank(&self) -> Pubkey {
self.banks[0]
}
pub fn first_vault(&self) -> Pubkey {
self.vaults[0]
}
}

View File

@ -54,7 +54,7 @@ pub mod switchboard_v2_mainnet_oracle {
}
#[zero_copy]
#[derive(AnchorDeserialize, AnchorSerialize)]
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
pub struct OracleConfig {
pub conf_filter: I80F48,
}

View File

@ -96,6 +96,7 @@ async fn get_mint_info_by_token_index(
account.group.as_ref(),
b"Bank".as_ref(),
&token_index.to_le_bytes(),
&0u64.to_le_bytes(),
],
&mango_v4::id(),
)
@ -153,7 +154,7 @@ async fn derive_health_check_remaining_account_metas(
// 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]);
banks.push(mint_info.bank);
banks.push(mint_info.first_bank());
oracles.push(mint_info.oracle);
}
@ -212,7 +213,7 @@ async fn derive_liquidation_remaining_account_metas(
// writable_bank,
// ));
// oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
banks.push((mint_info.bank, writable_bank));
banks.push((mint_info.first_bank(), writable_bank));
oracles.push(mint_info.oracle);
}
@ -409,7 +410,7 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
&account,
Some(mint_info.bank),
Some(mint_info.first_bank()),
false,
None,
)
@ -419,8 +420,8 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
group: account.group,
account: self.account,
owner: self.owner.pubkey(),
bank: mint_info.bank,
vault: mint_info.vault,
bank: mint_info.first_bank(),
vault: mint_info.first_vault(),
token_account: self.token_account,
token_program: Token::id(),
};
@ -473,7 +474,7 @@ impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
&account,
Some(mint_info.bank),
Some(mint_info.first_bank()),
false,
None,
)
@ -482,8 +483,8 @@ impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
let accounts = Self::Accounts {
group: account.group,
account: self.account,
bank: mint_info.bank,
vault: mint_info.vault,
bank: mint_info.first_bank(),
vault: mint_info.first_vault(),
token_account: self.token_account,
token_authority: self.token_authority.pubkey(),
token_program: Token::id(),
@ -534,6 +535,7 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
let instruction = Self::Instruction {
name: "some_ticker".to_string(),
token_index: self.token_index,
bank_num: 0,
oracle_config: OracleConfig {
conf_filter: I80F48::from_num::<f32>(0.10),
},
@ -558,6 +560,7 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
self.group.as_ref(),
b"Bank".as_ref(),
&self.token_index.to_le_bytes(),
&0u64.to_le_bytes(),
],
&program_id,
)
@ -567,6 +570,7 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
self.group.as_ref(),
b"Vault".as_ref(),
&self.token_index.to_le_bytes(),
&0u64.to_le_bytes(),
],
&program_id,
)
@ -618,31 +622,46 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
}
}
pub struct TokenDeregisterInstruction<'keypair> {
pub admin: &'keypair Keypair,
pub payer: &'keypair Keypair,
pub group: Pubkey,
pub mint: Pubkey,
pub struct TokenAddBankInstruction<'keypair> {
pub token_index: TokenIndex,
pub sol_destination: Pubkey,
pub bank_num: u64,
pub group: Pubkey,
pub admin: &'keypair Keypair,
pub mint: Pubkey,
pub address_lookup_table: Pubkey,
pub payer: &'keypair Keypair,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for TokenDeregisterInstruction<'keypair> {
type Accounts = mango_v4::accounts::TokenDeregister;
type Instruction = mango_v4::instruction::TokenDeregister;
impl<'keypair> ClientInstruction for TokenAddBankInstruction<'keypair> {
type Accounts = mango_v4::accounts::TokenAddBank;
type Instruction = mango_v4::instruction::TokenAddBank;
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, Instruction) {
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let instruction = Self::Instruction {
token_index: self.token_index,
bank_num: self.bank_num,
};
let existing_bank = Pubkey::find_program_address(
&[
self.group.as_ref(),
b"Bank".as_ref(),
&self.token_index.to_le_bytes(),
&0u64.to_le_bytes(),
],
&program_id,
)
.0;
let bank = Pubkey::find_program_address(
&[
self.group.as_ref(),
b"Bank".as_ref(),
&self.token_index.to_le_bytes(),
&self.bank_num.to_le_bytes(),
],
&program_id,
)
@ -652,6 +671,7 @@ impl<'keypair> ClientInstruction for TokenDeregisterInstruction<'keypair> {
self.group.as_ref(),
b"Vault".as_ref(),
&self.token_index.to_le_bytes(),
&self.bank_num.to_le_bytes(),
],
&program_id,
)
@ -667,16 +687,90 @@ impl<'keypair> ClientInstruction for TokenDeregisterInstruction<'keypair> {
.0;
let accounts = Self::Accounts {
admin: self.admin.pubkey(),
group: self.group,
admin: self.admin.pubkey(),
mint: self.mint,
existing_bank,
bank,
vault,
mint_info,
// TODO: ALTs are unavailable
//address_lookup_table: self.address_lookup_table,
payer: self.payer.pubkey(),
token_program: Token::id(),
system_program: System::id(),
// TODO: ALTs are unavailable
//address_lookup_table_program: mango_v4::address_lookup_table::id(),
rent: sysvar::rent::Rent::id(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<&Keypair> {
vec![self.admin, self.payer]
}
}
pub struct TokenDeregisterInstruction<'keypair> {
pub admin: &'keypair Keypair,
pub payer: &'keypair Keypair,
pub group: Pubkey,
pub mint_info: Pubkey,
pub banks: Vec<Pubkey>,
pub vaults: Vec<Pubkey>,
pub token_index: TokenIndex,
pub sol_destination: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for TokenDeregisterInstruction<'keypair> {
type Accounts = mango_v4::accounts::TokenDeregister;
type Instruction = mango_v4::instruction::TokenDeregister;
async fn to_instruction(
&self,
_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
token_index: self.token_index,
};
let accounts = Self::Accounts {
admin: self.admin.pubkey(),
group: self.group,
mint_info: self.mint_info,
sol_destination: self.sol_destination,
token_program: Token::id(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
let mut instruction = make_instruction(program_id, &accounts, instruction);
let mut ams = self
.banks
.iter()
.zip(self.vaults.iter())
.filter(|(bank, _)| **bank != Pubkey::default())
.map(|(bank, vault)| {
vec![
AccountMeta {
pubkey: *bank,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: *vault,
is_signer: false,
is_writable: true,
},
]
})
.flat_map(|vec| vec.into_iter())
.collect::<Vec<_>>();
dbg!(ams.clone());
instruction.accounts.append(&mut ams);
(accounts, instruction)
}
@ -1267,10 +1361,10 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
group: account.group,
account: self.account,
open_orders,
quote_bank: quote_info.bank,
quote_vault: quote_info.vault,
base_bank: base_info.bank,
base_vault: base_info.vault,
quote_bank: quote_info.first_bank(),
quote_vault: quote_info.first_vault(),
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: self.serum_market,
serum_program: serum_market.serum_program,
serum_market_external: serum_market.serum_market_external,
@ -1472,10 +1566,10 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
group: account.group,
account: self.account,
open_orders,
quote_bank: quote_info.bank,
quote_vault: quote_info.vault,
base_bank: base_info.bank,
base_vault: base_info.vault,
quote_bank: quote_info.first_bank(),
quote_vault: quote_info.first_vault(),
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: self.serum_market,
serum_program: serum_market.serum_program,
serum_market_external: serum_market.serum_market_external,
@ -1558,10 +1652,10 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
group: account.group,
account: self.account,
open_orders,
quote_bank: quote_info.bank,
quote_vault: quote_info.vault,
base_bank: base_info.bank,
base_vault: base_info.vault,
quote_bank: quote_info.first_bank(),
quote_vault: quote_info.first_vault(),
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: self.serum_market,
serum_program: serum_market.serum_program,
serum_market_external: serum_market.serum_market_external,
@ -2037,7 +2131,8 @@ impl ClientInstruction for BenchmarkInstruction {
}
}
pub struct UpdateIndexInstruction {
pub bank: Pubkey,
pub mint_info: Pubkey,
pub banks: Vec<Pubkey>,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for UpdateIndexInstruction {
@ -2049,9 +2144,23 @@ impl ClientInstruction for UpdateIndexInstruction {
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let accounts = Self::Accounts { bank: self.bank };
let accounts = Self::Accounts {
mint_info: self.mint_info,
};
let mut instruction = make_instruction(program_id, &accounts, instruction);
let mut bank_ams = self
.banks
.iter()
.filter(|bank| **bank != Pubkey::default())
.map(|bank| AccountMeta {
pubkey: *bank,
is_signer: false,
is_writable: true,
})
.collect::<Vec<_>>();
instruction.accounts.append(&mut bank_ams);
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}

View File

@ -19,6 +19,7 @@ pub struct Token {
pub oracle: Pubkey,
pub bank: Pubkey,
pub vault: Pubkey,
pub mint_info: Pubkey,
}
pub struct GroupWithTokens {
@ -93,8 +94,23 @@ impl<'a> GroupWithTokensConfig<'a> {
)
.await
.unwrap();
let _ = send_tx(
solana,
TokenAddBankInstruction {
token_index,
bank_num: 1,
group,
admin,
mint: mint.pubkey,
address_lookup_table,
payer,
},
)
.await
.unwrap();
let bank = register_token_accounts.bank;
let vault = register_token_accounts.vault;
let mint_info = register_token_accounts.mint_info;
tokens.push(Token {
index: token_index,
@ -102,6 +118,7 @@ impl<'a> GroupWithTokensConfig<'a> {
oracle,
bank,
vault,
mint_info,
});
}

View File

@ -139,6 +139,18 @@ async fn test_basic() -> Result<(), TransportError> {
//
// withdraw whatever is remaining, can't close bank vault without this
send_tx(
solana,
UpdateIndexInstruction {
mint_info: tokens[0].mint_info,
banks: {
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
mint_info.banks.to_vec()
},
},
)
.await
.unwrap();
let bank_data: Bank = solana.get_account(bank).await;
send_tx(
solana,
@ -173,7 +185,15 @@ async fn test_basic() -> Result<(), TransportError> {
admin,
payer,
group,
mint: bank_data.mint,
mint_info: tokens[0].mint_info,
banks: {
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
mint_info.banks.to_vec()
},
vaults: {
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
mint_info.vaults.to_vec()
},
token_index: bank_data.token_index,
sol_destination: payer.pubkey(),
},

View File

@ -1,6 +1,6 @@
#![cfg(feature = "test-bpf")]
use mango_v4::state::Bank;
use mango_v4::state::{Bank, MintInfo};
use solana_program_test::*;
use solana_sdk::{signature::Keypair, transport::TransportError};
@ -103,13 +103,19 @@ async fn test_update_index() -> Result<(), TransportError> {
send_tx(
solana,
UpdateIndexInstruction {
bank: tokens[0].bank,
mint_info: tokens[0].mint_info,
banks: {
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
mint_info.banks.to_vec()
},
},
)
.await
.unwrap();
let bank_after_update_index = solana.get_account::<Bank>(tokens[0].bank).await;
dbg!(bank_after_update_index);
dbg!(bank_after_update_index);
assert!(bank_before_update_index.deposit_index < bank_after_update_index.deposit_index);
assert!(bank_before_update_index.borrow_index < bank_after_update_index.borrow_index);

View File

@ -217,8 +217,8 @@ export class MintInfo {
publicKey: PublicKey,
obj: {
mint: PublicKey;
bank: PublicKey;
vault: PublicKey;
banks: PublicKey[];
vaults: PublicKey[];
oracle: PublicKey;
addressLookupTable: PublicKey;
tokenIndex: number;
@ -230,8 +230,8 @@ export class MintInfo {
return new MintInfo(
publicKey,
obj.mint,
obj.bank,
obj.vault,
obj.banks,
obj.vaults,
obj.oracle,
obj.tokenIndex,
);
@ -240,9 +240,16 @@ export class MintInfo {
constructor(
public publicKey: PublicKey,
public mint: PublicKey,
public bank: PublicKey,
public vault: PublicKey,
public banks: PublicKey[],
public vaults: PublicKey[],
public oracle: PublicKey,
public tokenIndex: number,
) {}
public firstBank() {
return this.banks[0];
}
public firstVault() {
return this.vaults[0];
}
}

View File

@ -155,6 +155,7 @@ export class MangoClient {
return await this.program.methods
.tokenRegister(
tokenIndex,
new BN(0),
name,
{
confFilter: {
@ -189,16 +190,20 @@ export class MangoClient {
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
return await this.program.methods
.tokenDeregister()
.tokenDeregister(bank.tokenIndex)
.accounts({
group: group.publicKey,
admin: adminPk,
bank: bank.publicKey,
vault: bank.vault,
mintInfo: group.mintInfosMap.get(bank.tokenIndex)?.publicKey,
solDestination: (this.program.provider as AnchorProvider).wallet
.publicKey,
})
.remainingAccounts(
[bank.publicKey, bank.vault].map(
(pk) =>
({ pubkey: pk, isWritable: true, isSigner: false } as AccountMeta),
),
)
.rpc();
}
@ -1425,7 +1430,9 @@ export class MangoClient {
const mintInfos = [...new Set(tokenIndices)].map(
(tokenIndex) => group.mintInfosMap.get(tokenIndex)!,
);
healthRemainingAccounts.push(...mintInfos.map((mintInfo) => mintInfo.bank));
healthRemainingAccounts.push(
...mintInfos.map((mintInfo) => mintInfo.firstBank()),
);
healthRemainingAccounts.push(
...mintInfos.map((mintInfo) => mintInfo.oracle),
);

View File

@ -120,6 +120,11 @@ export type MangoV4 = {
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
@ -144,6 +149,11 @@ export type MangoV4 = {
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
@ -204,6 +214,10 @@ export type MangoV4 = {
"name": "tokenIndex",
"type": "u16"
},
{
"name": "bankNum",
"type": "u64"
},
{
"name": "name",
"type": "string"
@ -251,7 +265,7 @@ export type MangoV4 = {
]
},
{
"name": "tokenDeregister",
"name": "tokenAddBank",
"accounts": [
{
"name": "group",
@ -263,16 +277,144 @@ export type MangoV4 = {
"isMut": false,
"isSigner": true
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "existingBank",
"isMut": false,
"isSigner": false
},
{
"name": "bank",
"isMut": true,
"isSigner": false
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "Bank"
},
{
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
},
{
"name": "vault",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "Vault"
},
{
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
},
{
"name": "mintInfo",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "MintInfo"
},
{
"kind": "account",
"type": "publicKey",
"account": "Mint",
"path": "mint"
}
]
}
},
{
"name": "payer",
"isMut": true,
"isSigner": true
},
{
"name": "tokenProgram",
"isMut": false,
"isSigner": false
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "tokenIndex",
"type": "u16"
},
{
"name": "bankNum",
"type": "u64"
}
]
},
{
"name": "tokenDeregister",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "mintInfo",
"isMut": true,
@ -289,14 +431,19 @@ export type MangoV4 = {
"isSigner": false
}
],
"args": []
"args": [
{
"name": "tokenIndex",
"type": "u16"
}
]
},
{
"name": "updateIndex",
"accounts": [
{
"name": "bank",
"isMut": true,
"name": "mintInfo",
"isMut": false,
"isSigner": false
}
],
@ -1968,6 +2115,18 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "indexedDeposits",
"type": {
"defined": "I80F48"
}
},
{
"name": "indexedBorrows",
"type": {
"defined": "I80F48"
}
},
{
"name": "lastUpdated",
"type": "i64"
@ -2076,6 +2235,10 @@ export type MangoV4 = {
4
]
}
},
{
"name": "bankNum",
"type": "u64"
}
]
}
@ -2208,12 +2371,22 @@ export type MangoV4 = {
"type": "publicKey"
},
{
"name": "bank",
"type": "publicKey"
"name": "banks",
"type": {
"array": [
"publicKey",
6
]
}
},
{
"name": "vault",
"type": "publicKey"
"name": "vaults",
"type": {
"array": [
"publicKey",
6
]
}
},
{
"name": "oracle",
@ -3412,6 +3585,11 @@ export const IDL: MangoV4 = {
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
@ -3436,6 +3614,11 @@ export const IDL: MangoV4 = {
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
@ -3496,6 +3679,10 @@ export const IDL: MangoV4 = {
"name": "tokenIndex",
"type": "u16"
},
{
"name": "bankNum",
"type": "u64"
},
{
"name": "name",
"type": "string"
@ -3543,7 +3730,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "tokenDeregister",
"name": "tokenAddBank",
"accounts": [
{
"name": "group",
@ -3555,16 +3742,144 @@ export const IDL: MangoV4 = {
"isMut": false,
"isSigner": true
},
{
"name": "mint",
"isMut": false,
"isSigner": false
},
{
"name": "existingBank",
"isMut": false,
"isSigner": false
},
{
"name": "bank",
"isMut": true,
"isSigner": false
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "Bank"
},
{
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
},
{
"name": "vault",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "Vault"
},
{
"kind": "arg",
"type": "u16",
"path": "token_index"
},
{
"kind": "arg",
"type": "u64",
"path": "bank_num"
}
]
}
},
{
"name": "mintInfo",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "account",
"type": "publicKey",
"path": "group"
},
{
"kind": "const",
"type": "string",
"value": "MintInfo"
},
{
"kind": "account",
"type": "publicKey",
"account": "Mint",
"path": "mint"
}
]
}
},
{
"name": "payer",
"isMut": true,
"isSigner": true
},
{
"name": "tokenProgram",
"isMut": false,
"isSigner": false
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "tokenIndex",
"type": "u16"
},
{
"name": "bankNum",
"type": "u64"
}
]
},
{
"name": "tokenDeregister",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "mintInfo",
"isMut": true,
@ -3581,14 +3896,19 @@ export const IDL: MangoV4 = {
"isSigner": false
}
],
"args": []
"args": [
{
"name": "tokenIndex",
"type": "u16"
}
]
},
{
"name": "updateIndex",
"accounts": [
{
"name": "bank",
"isMut": true,
"name": "mintInfo",
"isMut": false,
"isSigner": false
}
],
@ -5260,6 +5580,18 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "indexedDeposits",
"type": {
"defined": "I80F48"
}
},
{
"name": "indexedBorrows",
"type": {
"defined": "I80F48"
}
},
{
"name": "lastUpdated",
"type": "i64"
@ -5368,6 +5700,10 @@ export const IDL: MangoV4 = {
4
]
}
},
{
"name": "bankNum",
"type": "u64"
}
]
}
@ -5500,12 +5836,22 @@ export const IDL: MangoV4 = {
"type": "publicKey"
},
{
"name": "bank",
"type": "publicKey"
"name": "banks",
"type": {
"array": [
"publicKey",
6
]
}
},
{
"name": "vault",
"type": "publicKey"
"name": "vaults",
"type": {
"array": [
"publicKey",
6
]
}
},
{
"name": "oracle",