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] [patch.crates-io]
# for gzip encoded responses # 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, pub group: Pubkey,
// TODO: future: this may not scale if there's thousands of mints, probably some function // 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) // wrapping getMultipleAccounts is needed (or bettew: we provide this data as a service)
pub banks_cache: HashMap<String, (Pubkey, Bank)>, pub banks_cache: HashMap<String, Vec<(Pubkey, Bank)>>,
pub banks_cache_by_token_index: HashMap<TokenIndex, (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: HashMap<Pubkey, (Pubkey, MintInfo, Mint)>,
pub mint_infos_cache_by_token_index: HashMap<TokenIndex, (Pubkey, MintInfo, Mint)>, pub mint_infos_cache_by_token_index: HashMap<TokenIndex, (Pubkey, MintInfo, Mint)>,
pub serum3_markets_cache: HashMap<String, (Pubkey, Serum3Market)>, pub serum3_markets_cache: HashMap<String, (Pubkey, Serum3Market)>,
@ -160,8 +160,14 @@ impl MangoClient {
encoding: None, encoding: None,
})])?; })])?;
for (k, v) in bank_tuples { for (k, v) in bank_tuples {
banks_cache.insert(v.name().to_owned(), (k, v)); banks_cache
banks_cache_by_token_index.insert(v.token_index, (k, v)); .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 // 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 { pub fn client(&self) -> Client {
Client::new_with_options( Client::new_with_options(
self.cluster.clone(), self.cluster.clone(),
@ -302,7 +315,7 @@ impl MangoClient {
// let addresses = mango_v4::address_lookup_table::addresses(&lookup_table); // let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
// banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]); // banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
// oracles.push(addresses[mint_info.address_lookup_table_oracle_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); oracles.push(mint_info.oracle);
} }
if let Some(affected_bank) = affected_bank { if let Some(affected_bank) = affected_bank {
@ -387,7 +400,7 @@ impl MangoClient {
// let addresses = mango_v4::address_lookup_table::addresses(&lookup_table); // let addresses = mango_v4::address_lookup_table::addresses(&lookup_table);
// banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]); // banks.push(addresses[mint_info.address_lookup_table_bank_index as usize]);
// oracles.push(addresses[mint_info.address_lookup_table_oracle_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); oracles.push(mint_info.oracle);
} }
@ -431,7 +444,7 @@ impl MangoClient {
token_name: &str, token_name: &str,
amount: u64, amount: u64,
) -> Result<Signature, anchor_client::ClientError> { ) -> 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 mint_info: MintInfo = self.mint_infos_cache.get(&bank.1.mint).unwrap().1;
let health_check_metas = let health_check_metas =
@ -471,7 +484,7 @@ impl MangoClient {
&self, &self,
token_name: &str, token_name: &str,
) -> Result<pyth_sdk_solana::Price, anyhow::Error> { ) -> 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 let data = self
.program() .program()
@ -652,10 +665,10 @@ impl MangoClient {
group: self.group(), group: self.group(),
account: self.mango_account_cache.0, account: self.mango_account_cache.0,
open_orders, open_orders,
quote_bank: quote_info.bank, quote_bank: quote_info.first_bank(),
quote_vault: quote_info.vault, quote_vault: quote_info.first_vault(),
base_bank: base_info.bank, base_bank: base_info.first_bank(),
base_vault: base_info.vault, base_vault: base_info.first_vault(),
serum_market: serum3_market.0, serum_market: serum3_market.0,
serum_program: serum3_market.1.serum_program, serum_program: serum3_market.1.serum_program,
serum_market_external: serum3_market.1.serum_market_external, serum_market_external: serum3_market.1.serum_market_external,
@ -730,10 +743,10 @@ impl MangoClient {
group: self.group(), group: self.group(),
account: self.mango_account_cache.0, account: self.mango_account_cache.0,
open_orders, open_orders,
quote_bank: quote_info.bank, quote_bank: quote_info.first_bank(),
quote_vault: quote_info.vault, quote_vault: quote_info.first_vault(),
base_bank: base_info.bank, base_bank: base_info.first_bank(),
base_vault: base_info.vault, base_vault: base_info.first_vault(),
serum_market: serum3_market.0, serum_market: serum3_market.0,
serum_program: serum3_market.1.serum_program, serum_program: serum3_market.1.serum_program,
serum_market_external: serum3_market.1.serum_market_external, 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 anchor_lang::__private::bytemuck::cast_ref;
use futures::Future; 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::{ use solana_sdk::{
instruction::{AccountMeta, Instruction}, instruction::{AccountMeta, Instruction},
pubkey::Pubkey, pubkey::Pubkey,
@ -18,7 +18,12 @@ pub async fn runner(
let handles1 = mango_client let handles1 = mango_client
.banks_cache .banks_cache
.values() .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<_>>(); .collect::<Vec<_>>();
let handles2 = mango_client let handles2 = mango_client
@ -43,31 +48,54 @@ pub async fn runner(
Ok(()) 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)); let mut interval = time::interval(Duration::from_secs(5));
loop { loop {
interval.tick().await; interval.tick().await;
let client = mango_client.clone(); let client = mango_client.clone();
let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { 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 let sig_result = client
.program() .program()
.request() .request()
.instruction(Instruction { .instruction({
program_id: mango_v4::id(), let mut ix = Instruction {
accounts: anchor_lang::ToAccountMetas::to_account_metas( program_id: mango_v4::id(),
&mango_v4::accounts::UpdateIndex { bank: pk }, accounts: anchor_lang::ToAccountMetas::to_account_metas(
None, &mango_v4::accounts::UpdateIndex { mint_info },
), None,
data: anchor_lang::InstructionData::data( ),
&mango_v4::instruction::UpdateIndex {}, 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(); .send();
if let Err(e) = sig_result { if let Err(e) = sig_result {
log::error!("{:?}", e) log::error!("{:?}", e)
} else { } else {
log::info!("update_index {} {:?}", bank.name(), sig_result.unwrap()) log::info!("update_index {} {:?}", token_name, sig_result.unwrap())
} }
Ok(()) Ok(())

View File

@ -6,7 +6,10 @@ use std::{
use fixed::types::I80F48; use fixed::types::I80F48;
use futures::Future; use futures::Future;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; use mango_v4::{
instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side},
state::Bank,
};
use tokio::time; 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> { fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error> {
let mango_account = mango_client.get_account()?.1; 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 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)); 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 { let deposit_native = match token_account_opt {
Some(token_account) => { 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()); log::info!("Current balance {} {}", ui, bank.name());
if native < I80F48::ZERO { if native < I80F48::ZERO {

View File

@ -53,9 +53,9 @@ pub fn compute_health_(
.unwrap(); .unwrap();
banks.push(( banks.push((
mint_info.bank, mint_info.first_bank(),
chain_data chain_data
.account(&mint_info.bank) .account(&mint_info.first_bank())
.expect("chain data is missing bank"), .expect("chain data is missing bank"),
)); ));
oracles.push(( 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_pk = mint_infos.get(&token.token_index).expect("always Ok");
let mint_info = let mint_info =
load_mango_account_from_chain::<MintInfo>(chain_data, mint_info_pk)?; 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 oracle = chain_data.account(&mint_info.oracle)?;
let price = oracle_price( let price = oracle_price(
&KeyedAccountSharedData::new(mint_info.oracle, oracle.clone()), &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; am.is_signer = true;
// this is the data we'll need later to build the PDA account signer seeds // 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 group_key = ctx.accounts.group.key();
let signers = bank_signer_data let signers = bank_signer_data
.iter() .iter()
.map(|(token_index, bump)| { .map(|(token_index, bank_num, bump)| {
[ [
group_key.as_ref(), group_key.as_ref(),
b"Bank".as_ref(), b"Bank".as_ref(),
&token_index[..], &token_index[..],
&bank_num[..],
&bump[..], &bump[..],
] ]
}) })

View File

@ -27,6 +27,7 @@ pub use serum3_place_order::*;
pub use serum3_register_market::*; pub use serum3_register_market::*;
pub use serum3_settle_funds::*; pub use serum3_settle_funds::*;
pub use set_stub_oracle::*; pub use set_stub_oracle::*;
pub use token_add_bank::*;
pub use token_deposit::*; pub use token_deposit::*;
pub use token_deregister::*; pub use token_deregister::*;
pub use token_register::*; pub use token_register::*;
@ -62,6 +63,7 @@ mod serum3_place_order;
mod serum3_register_market; mod serum3_register_market;
mod serum3_settle_funds; mod serum3_settle_funds;
mod set_stub_oracle; mod set_stub_oracle;
mod token_add_bank;
mod token_deposit; mod token_deposit;
mod token_deregister; mod token_deregister;
mod token_register; 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_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)] #[derive(Accounts)]
#[instruction(token_index: TokenIndex)]
pub struct TokenDeregister<'info> { pub struct TokenDeregister<'info> {
#[account( #[account(
constraint = group.load()?.testing == 1, constraint = group.load()?.testing == 1,
@ -12,23 +14,11 @@ pub struct TokenDeregister<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>, 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 // match mint info to bank
#[account( #[account(
mut, mut,
has_one = bank, has_one = group,
constraint = mint_info.load()?.token_index == token_index,
close = sol_destination close = sol_destination
)] )]
pub mint_info: AccountLoader<'info, MintInfo>, pub mint_info: AccountLoader<'info, MintInfo>,
@ -41,21 +31,63 @@ pub struct TokenDeregister<'info> {
} }
#[allow(clippy::too_many_arguments)] #[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 = ctx.accounts.group.load()?;
let group_seeds = group_seeds!(group); let group_seeds = group_seeds!(group);
let cpi_accounts = CloseAccount {
account: ctx.accounts.vault.to_account_info(), // todo: use itertools::chunks(2)
destination: ctx.accounts.sol_destination.to_account_info(), for i in (0..ctx.remaining_accounts.len()).step_by(2) {
authority: ctx.accounts.group.to_account_info(), let vault_ai = &ctx.remaining_accounts[i + 1];
}; let bank_ai = &ctx.remaining_accounts[i];
let cpi_program = ctx.accounts.token_program.to_account_info();
token::close_account(CpiContext::new_with_signer( require_eq!(bank_ai.key(), mint_info.banks[i / 2]);
cpi_program, require_eq!(vault_ai.key(), mint_info.vaults[i / 2]);
cpi_accounts,
&[group_seeds], // todo: these checks might be superfluous, after above 2 checks
))?; {
ctx.accounts.vault.exit(ctx.program_id)?; 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(()) Ok(())
} }

View File

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

View File

@ -1,20 +1,88 @@
use anchor_lang::prelude::*; 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)] #[derive(Accounts)]
pub struct UpdateIndex<'info> { pub struct UpdateIndex<'info> {
// TODO: should we support arbitrary number of banks with remaining accounts? pub mint_info: AccountLoader<'info, MintInfo>,
// ix - consumed 17641 of 101000 compute units, so we have a lot of compute
#[account(mut)]
pub bank: AccountLoader<'info, Bank>,
} }
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()?; pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
bank.update_index(now_ts)?; 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(()) Ok(())
} }

View File

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

View File

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

View File

@ -4,17 +4,20 @@ use std::mem::size_of;
use super::TokenIndex; use super::TokenIndex;
pub const MAX_BANKS: usize = 6;
// This struct describes which address lookup table can be used to pass // 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 // 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 // can load this account to figure out which address maps to use when calling
// instructions that need banks/oracles for all active positions. // instructions that need banks/oracles for all active positions.
#[account(zero_copy)] #[account(zero_copy)]
#[derive(Debug)]
pub struct MintInfo { pub struct MintInfo {
// TODO: none of these pubkeys are needed, remove? // TODO: none of these pubkeys are needed, remove?
pub group: Pubkey, pub group: Pubkey,
pub mint: Pubkey, pub mint: Pubkey,
pub bank: Pubkey, pub banks: [Pubkey; MAX_BANKS],
pub vault: Pubkey, pub vaults: [Pubkey; MAX_BANKS],
pub oracle: Pubkey, pub oracle: Pubkey,
pub address_lookup_table: Pubkey, pub address_lookup_table: Pubkey,
@ -26,5 +29,19 @@ pub struct MintInfo {
pub reserved: [u8; 4], 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); 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] #[zero_copy]
#[derive(AnchorDeserialize, AnchorSerialize)] #[derive(AnchorDeserialize, AnchorSerialize, Debug)]
pub struct OracleConfig { pub struct OracleConfig {
pub conf_filter: I80F48, pub conf_filter: I80F48,
} }

View File

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

View File

@ -19,6 +19,7 @@ pub struct Token {
pub oracle: Pubkey, pub oracle: Pubkey,
pub bank: Pubkey, pub bank: Pubkey,
pub vault: Pubkey, pub vault: Pubkey,
pub mint_info: Pubkey,
} }
pub struct GroupWithTokens { pub struct GroupWithTokens {
@ -93,8 +94,23 @@ impl<'a> GroupWithTokensConfig<'a> {
) )
.await .await
.unwrap(); .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 bank = register_token_accounts.bank;
let vault = register_token_accounts.vault; let vault = register_token_accounts.vault;
let mint_info = register_token_accounts.mint_info;
tokens.push(Token { tokens.push(Token {
index: token_index, index: token_index,
@ -102,6 +118,7 @@ impl<'a> GroupWithTokensConfig<'a> {
oracle, oracle,
bank, bank,
vault, 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 // 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; let bank_data: Bank = solana.get_account(bank).await;
send_tx( send_tx(
solana, solana,
@ -173,7 +185,15 @@ async fn test_basic() -> Result<(), TransportError> {
admin, admin,
payer, payer,
group, 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, token_index: bank_data.token_index,
sol_destination: payer.pubkey(), sol_destination: payer.pubkey(),
}, },

View File

@ -1,6 +1,6 @@
#![cfg(feature = "test-bpf")] #![cfg(feature = "test-bpf")]
use mango_v4::state::Bank; use mango_v4::state::{Bank, MintInfo};
use solana_program_test::*; use solana_program_test::*;
use solana_sdk::{signature::Keypair, transport::TransportError}; use solana_sdk::{signature::Keypair, transport::TransportError};
@ -103,13 +103,19 @@ async fn test_update_index() -> Result<(), TransportError> {
send_tx( send_tx(
solana, solana,
UpdateIndexInstruction { 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 .await
.unwrap(); .unwrap();
let bank_after_update_index = solana.get_account::<Bank>(tokens[0].bank).await; 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.deposit_index < bank_after_update_index.deposit_index);
assert!(bank_before_update_index.borrow_index < bank_after_update_index.borrow_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, publicKey: PublicKey,
obj: { obj: {
mint: PublicKey; mint: PublicKey;
bank: PublicKey; banks: PublicKey[];
vault: PublicKey; vaults: PublicKey[];
oracle: PublicKey; oracle: PublicKey;
addressLookupTable: PublicKey; addressLookupTable: PublicKey;
tokenIndex: number; tokenIndex: number;
@ -230,8 +230,8 @@ export class MintInfo {
return new MintInfo( return new MintInfo(
publicKey, publicKey,
obj.mint, obj.mint,
obj.bank, obj.banks,
obj.vault, obj.vaults,
obj.oracle, obj.oracle,
obj.tokenIndex, obj.tokenIndex,
); );
@ -240,9 +240,16 @@ export class MintInfo {
constructor( constructor(
public publicKey: PublicKey, public publicKey: PublicKey,
public mint: PublicKey, public mint: PublicKey,
public bank: PublicKey, public banks: PublicKey[],
public vault: PublicKey, public vaults: PublicKey[],
public oracle: PublicKey, public oracle: PublicKey,
public tokenIndex: number, 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 return await this.program.methods
.tokenRegister( .tokenRegister(
tokenIndex, tokenIndex,
new BN(0),
name, name,
{ {
confFilter: { confFilter: {
@ -189,16 +190,20 @@ export class MangoClient {
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey; const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
return await this.program.methods return await this.program.methods
.tokenDeregister() .tokenDeregister(bank.tokenIndex)
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,
admin: adminPk, admin: adminPk,
bank: bank.publicKey,
vault: bank.vault,
mintInfo: group.mintInfosMap.get(bank.tokenIndex)?.publicKey, mintInfo: group.mintInfosMap.get(bank.tokenIndex)?.publicKey,
solDestination: (this.program.provider as AnchorProvider).wallet solDestination: (this.program.provider as AnchorProvider).wallet
.publicKey, .publicKey,
}) })
.remainingAccounts(
[bank.publicKey, bank.vault].map(
(pk) =>
({ pubkey: pk, isWritable: true, isSigner: false } as AccountMeta),
),
)
.rpc(); .rpc();
} }
@ -1425,7 +1430,9 @@ export class MangoClient {
const mintInfos = [...new Set(tokenIndices)].map( const mintInfos = [...new Set(tokenIndices)].map(
(tokenIndex) => group.mintInfosMap.get(tokenIndex)!, (tokenIndex) => group.mintInfosMap.get(tokenIndex)!,
); );
healthRemainingAccounts.push(...mintInfos.map((mintInfo) => mintInfo.bank)); healthRemainingAccounts.push(
...mintInfos.map((mintInfo) => mintInfo.firstBank()),
);
healthRemainingAccounts.push( healthRemainingAccounts.push(
...mintInfos.map((mintInfo) => mintInfo.oracle), ...mintInfos.map((mintInfo) => mintInfo.oracle),
); );

View File

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