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:
parent
0758125db8
commit
9fc8a5a56a
|
@ -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,
|
||||||
|
|
|
@ -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({
|
||||||
|
let mut ix = Instruction {
|
||||||
program_id: mango_v4::id(),
|
program_id: mango_v4::id(),
|
||||||
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
accounts: anchor_lang::ToAccountMetas::to_account_metas(
|
||||||
&mango_v4::accounts::UpdateIndex { bank: pk },
|
&mango_v4::accounts::UpdateIndex { mint_info },
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
data: anchor_lang::InstructionData::data(
|
data: anchor_lang::InstructionData::data(
|
||||||
&mango_v4::instruction::UpdateIndex {},
|
&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(())
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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[..],
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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,11 +31,44 @@ 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);
|
||||||
|
|
||||||
|
// 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 {
|
let cpi_accounts = CloseAccount {
|
||||||
account: ctx.accounts.vault.to_account_info(),
|
account: vault_ai.to_account_info(),
|
||||||
destination: ctx.accounts.sol_destination.to_account_info(),
|
destination: ctx.accounts.sol_destination.to_account_info(),
|
||||||
authority: ctx.accounts.group.to_account_info(),
|
authority: ctx.accounts.group.to_account_info(),
|
||||||
};
|
};
|
||||||
|
@ -55,7 +78,16 @@ pub fn token_deregister(ctx: Context<TokenDeregister>) -> Result<()> {
|
||||||
cpi_accounts,
|
cpi_accounts,
|
||||||
&[group_seeds],
|
&[group_seeds],
|
||||||
))?;
|
))?;
|
||||||
ctx.accounts.vault.exit(ctx.program_id)?;
|
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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue