remove base_token_index from perps and include oracles (#224)

Co-authored-by: Conj0iner <conj0iner@users.noreply.github.com>
This commit is contained in:
conj0iner 2022-09-21 15:42:45 +08:00 committed by GitHub
parent f96b9ded0d
commit 1c67b8ed5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 282 additions and 172 deletions

View File

@ -39,6 +39,7 @@ pub fn new_health_cache_(
let retriever = FixedOrderAccountRetriever {
ais: accounts,
n_banks: active_token_len,
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len,
};

View File

@ -49,8 +49,7 @@ pub fn perp_create_market(
perp_market_index: PerpMarketIndex,
name: String,
oracle_config: OracleConfig,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals: u8,
base_decimals: u8,
quote_lot_size: i64,
base_lot_size: i64,
maint_asset_weight: f32,
@ -94,10 +93,10 @@ pub fn perp_create_market(
fees_settled: I80F48::ZERO,
// Why optional - Perp could be based purely on an oracle
bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?,
base_token_decimals,
base_decimals,
perp_market_index,
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
registration_time: Clock::get()?.unix_timestamp,
padding0: Default::default(),
padding1: Default::default(),
padding2: Default::default(),
reserved: [0; 112],

View File

@ -22,8 +22,7 @@ pub fn perp_edit_market(
ctx: Context<PerpEditMarket>,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals_opt: Option<u8>,
base_decimals_opt: Option<u8>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
@ -100,17 +99,13 @@ pub fn perp_edit_market(
// fees_accrued
// bump
if let Some(base_token_decimals) = base_token_decimals_opt {
perp_market.base_token_decimals = base_token_decimals;
if let Some(base_decimals) = base_decimals_opt {
perp_market.base_decimals = base_decimals;
}
// unchanged -
// perp_market_index
if let Some(base_token_index) = base_token_index_opt {
perp_market.base_token_index = base_token_index;
}
// unchanged -
// quote_token_index

View File

@ -22,6 +22,9 @@ pub struct PerpLiqForceCancelOrders<'info> {
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub bids: AccountLoader<'info, BookSide>,
/// CHECK: Oracle can have different account types, constrained by address in perp_market
pub oracle: UncheckedAccount<'info>,
}
pub fn perp_liq_force_cancel_orders(

View File

@ -382,8 +382,7 @@ pub mod mango_v4 {
perp_market_index: PerpMarketIndex,
name: String,
oracle_config: OracleConfig,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals: u8,
base_decimals: u8,
quote_lot_size: i64,
base_lot_size: i64,
maint_asset_weight: f32,
@ -402,8 +401,7 @@ pub mod mango_v4 {
perp_market_index,
name,
oracle_config,
base_token_index_opt,
base_token_decimals,
base_decimals,
quote_lot_size,
base_lot_size,
maint_asset_weight,
@ -424,8 +422,7 @@ pub mod mango_v4 {
ctx: Context<PerpEditMarket>,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals_opt: Option<u8>,
base_decimals_opt: Option<u8>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
@ -441,8 +438,7 @@ pub mod mango_v4 {
ctx,
oracle_opt,
oracle_config_opt,
base_token_index_opt,
base_token_decimals_opt,
base_decimals_opt,
maint_asset_weight_opt,
init_asset_weight_opt,
maint_liab_weight_opt,

View File

@ -41,12 +41,12 @@ pub trait AccountRetriever {
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<&OpenOrders>;
fn perp_market(
fn perp_market_and_oracle_price(
&self,
group: &Pubkey,
account_index: usize,
perp_market_index: PerpMarketIndex,
) -> Result<&PerpMarket>;
) -> Result<(&PerpMarket, I80F48)>;
}
/// Assumes the account infos needed for the health computation follow a strict order.
@ -54,10 +54,12 @@ pub trait AccountRetriever {
/// 1. n_banks Bank account, in the order of account.token_iter_active()
/// 2. n_banks oracle accounts, one for each bank in the same order
/// 3. PerpMarket accounts, in the order of account.perps.iter_active_accounts()
/// 4. serum3 OpenOrders accounts, in the order of account.serum3.iter_active()
/// 4. PerpMarket oracle accounts, in the order of the perp market accounts
/// 5. serum3 OpenOrders accounts, in the order of account.serum3.iter_active()
pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub ais: Vec<T>,
pub n_banks: usize,
pub n_perps: usize,
pub begin_perp: usize,
pub begin_serum3: usize,
}
@ -70,15 +72,16 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
let active_serum3_len = account.active_serum3_orders().count();
let active_perp_len = account.active_perp_positions().count();
let expected_ais = cm!(active_token_len * 2 // banks + oracles
+ active_perp_len // PerpMarkets
+ active_perp_len * 2 // PerpMarkets + Oracles
+ active_serum3_len); // open_orders
require_eq!(ais.len(), expected_ais);
Ok(FixedOrderAccountRetriever {
ais: AccountInfoRef::borrow_slice(ais)?,
n_banks: active_token_len,
n_perps: active_perp_len,
begin_perp: cm!(active_token_len * 2),
begin_serum3: cm!(active_token_len * 2 + active_perp_len),
begin_serum3: cm!(active_token_len * 2 + active_perp_len * 2),
})
}
@ -90,10 +93,28 @@ impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
Ok(bank)
}
fn perp_market(
&self,
group: &Pubkey,
account_index: usize,
perp_market_index: PerpMarketIndex,
) -> Result<&PerpMarket> {
let market_ai = &self.ais[self.begin_perp + account_index];
let market = market_ai.load::<PerpMarket>()?;
require_keys_eq!(market.group, *group);
require_eq!(market.perp_market_index, perp_market_index);
Ok(market)
}
fn oracle_price(&self, account_index: usize, bank: &Bank) -> Result<I80F48> {
let oracle = &self.ais[cm!(self.n_banks + account_index)];
bank.oracle_price(oracle)
}
fn oracle_price_perp(&self, account_index: usize, perp_market: &PerpMarket) -> Result<I80F48> {
let oracle = &self.ais[self.begin_perp + self.n_perps + account_index];
perp_market.oracle_price(oracle)
}
}
impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
@ -126,27 +147,32 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
Ok((bank, oracle_price))
}
fn perp_market(
fn perp_market_and_oracle_price(
&self,
group: &Pubkey,
account_index: usize,
perp_market_index: PerpMarketIndex,
) -> Result<&PerpMarket> {
let ai = &self.ais[cm!(self.begin_perp + account_index)];
(|| {
let market = ai.load::<PerpMarket>()?;
require_keys_eq!(market.group, *group);
require_eq!(market.perp_market_index, perp_market_index);
Ok(market)
})()
.with_context(|| {
) -> Result<(&PerpMarket, I80F48)> {
let perp_market = self
.perp_market(group, account_index, perp_market_index)
.with_context(|| {
format!(
"loading perp market with health account index {} and perp market index {}, passed account {}",
account_index,
perp_market_index,
self.ais[self.begin_perp + account_index].key(),
)
})?;
let oracle_price = self.oracle_price_perp(account_index, perp_market).with_context(|| {
format!(
"loading perp market with health account index {} and perp market index {}, passed account {}",
"getting oracle for perp market with health account index {} and perp market index {}, passed account {}",
account_index,
perp_market_index,
ai.key(),
self.ais[self.begin_perp + self.n_perps + account_index].key(),
)
})
})?;
Ok((perp_market, oracle_price))
}
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
@ -169,6 +195,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
/// - an unknown number of Banks in any order, followed by
/// - the same number of oracles in the same order as the banks, followed by
/// - an unknown number of PerpMarket accounts
/// - the same number of oracles in the same order as the perp markets
/// - an unknown number of serum3 OpenOrders accounts
/// and retrieves accounts needed for the health computation by doing a linear
/// scan for each request.
@ -176,6 +203,7 @@ pub struct ScanningAccountRetriever<'a, 'info> {
banks: Vec<AccountInfoRefMut<'a, 'info>>,
oracles: Vec<AccountInfoRef<'a, 'info>>,
perp_markets: Vec<AccountInfoRef<'a, 'info>>,
perp_oracles: Vec<AccountInfoRef<'a, 'info>>,
serum3_oos: Vec<AccountInfoRef<'a, 'info>>,
token_index_map: HashMap<TokenIndex, usize>,
perp_index_map: HashMap<PerpMarketIndex, usize>,
@ -239,13 +267,16 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
})
})?;
let n_perps = perp_index_map.len();
let serum3_start = perps_start + n_perps;
let perp_oracles_start = perps_start + n_perps;
let serum3_start = perp_oracles_start + n_perps;
Ok(Self {
banks: AccountInfoRefMut::borrow_slice(&ais[..n_banks])?,
oracles: AccountInfoRef::borrow_slice(&ais[n_banks..2 * n_banks])?,
perp_markets: AccountInfoRef::borrow_slice(&ais[perps_start..perps_start + n_perps])?,
perp_oracles: AccountInfoRef::borrow_slice(
&ais[perp_oracles_start..perp_oracles_start + n_perps],
)?,
serum3_oos: AccountInfoRef::borrow_slice(&ais[serum3_start..])?,
token_index_map,
perp_index_map,
@ -312,9 +343,15 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
Ok((bank, bank.oracle_price(oracle)?))
}
pub fn scanned_perp_market(&self, perp_market_index: PerpMarketIndex) -> Result<&PerpMarket> {
pub fn scanned_perp_market_and_oracle(
&self,
perp_market_index: PerpMarketIndex,
) -> Result<(&PerpMarket, I80F48)> {
let index = self.perp_market_index(perp_market_index)?;
self.perp_markets[index].load_fully_unchecked::<PerpMarket>()
let perp_market = self.perp_markets[index].load_fully_unchecked::<PerpMarket>()?;
let oracle_acc = &self.perp_oracles[index];
let oracle_price = perp_market.oracle_price(oracle_acc)?;
Ok((perp_market, oracle_price))
}
pub fn scanned_serum_oo(&self, key: &Pubkey) -> Result<&OpenOrders> {
@ -337,13 +374,13 @@ impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
self.scanned_bank_and_oracle(token_index)
}
fn perp_market(
fn perp_market_and_oracle_price(
&self,
_group: &Pubkey,
_account_index: usize,
perp_market_index: PerpMarketIndex,
) -> Result<&PerpMarket> {
self.scanned_perp_market(perp_market_index)
) -> Result<(&PerpMarket, I80F48)> {
self.scanned_perp_market_and_oracle(perp_market_index)
}
fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
@ -484,19 +521,15 @@ pub struct PerpInfo {
pub base: I80F48,
// in health-reference-token native units, no asset/liab factor needed
pub quote: I80F48,
oracle_price: I80F48,
}
impl PerpInfo {
fn new(
perp_position: &PerpPosition,
perp_market: &PerpMarket,
token_infos: &[TokenInfo],
oracle_price: I80F48,
) -> Result<Self> {
// find the TokenInfos for the market's base and quote tokens
let base_index = find_token_info_index(token_infos, perp_market.base_token_index)?;
// TODO: base_index could be unset
let base_info = &token_infos[base_index];
let base_lot_size = I80F48::from(perp_market.base_lot_size);
let base_lots = cm!(perp_position.base_position_lots() + perp_position.taker_base_lots);
@ -550,7 +583,7 @@ impl PerpInfo {
let bids_net_lots = cm!(base_lots + perp_position.bids_base_lots);
let asks_net_lots = cm!(base_lots - perp_position.asks_base_lots);
let lots_to_quote = base_lot_size * base_info.oracle_price;
let lots_to_quote = base_lot_size * oracle_price;
let base;
let quote;
if cm!(bids_net_lots.abs()) > cm!(asks_net_lots.abs()) {
@ -573,6 +606,7 @@ impl PerpInfo {
maint_liab_weight: perp_market.maint_liab_weight,
base,
quote,
oracle_price,
})
}
@ -723,7 +757,7 @@ impl HealthCache {
.iter_mut()
.find(|m| m.perp_market_index == perp_market.perp_market_index)
.ok_or_else(|| error_msg!("perp market {} not found", perp_market.perp_market_index))?;
*perp_entry = PerpInfo::new(perp_position, perp_market, &self.token_infos)?;
*perp_entry = PerpInfo::new(perp_position, perp_market, perp_entry.oracle_price)?;
Ok(())
}
@ -1066,9 +1100,12 @@ pub fn new_health_cache(
// health contribution from perp accounts
let mut perp_infos = Vec::with_capacity(account.active_perp_positions().count());
for (i, perp_position) in account.active_perp_positions().enumerate() {
let perp_market =
retriever.perp_market(&account.fixed.group, i, perp_position.market_index)?;
perp_infos.push(PerpInfo::new(perp_position, perp_market, &token_infos)?);
let (perp_market, oracle_price) = retriever.perp_market_and_oracle_price(
&account.fixed.group,
i,
perp_position.market_index,
)?;
perp_infos.push(PerpInfo::new(perp_position, perp_market, oracle_price)?);
}
Ok(HealthCache {
@ -1201,15 +1238,15 @@ mod tests {
fn mock_perp_market(
group: Pubkey,
oracle: Pubkey,
market_index: PerpMarketIndex,
base_token: TokenIndex,
init_weights: f64,
maint_weights: f64,
) -> TestAccount<PerpMarket> {
let mut pm = TestAccount::<PerpMarket>::new_zeroed();
pm.data().group = group;
pm.data().oracle = oracle;
pm.data().perp_market_index = market_index;
pm.data().base_token_index = base_token;
pm.data().init_asset_weight = I80F48::from_num(1.0 - init_weights);
pm.data().init_liab_weight = I80F48::from_num(1.0 + init_weights);
pm.data().maint_asset_weight = I80F48::from_num(1.0 - maint_weights);
@ -1264,7 +1301,7 @@ mod tests {
oo1.data().native_coin_free = 3;
oo1.data().referrer_rebates_accrued = 2;
let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 9, 0.2, 0.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.change_base_and_quote_positions(perp1.data(), 3, -I80F48::from(310u16));
perpaccount.bids_base_lots = 7;
@ -1272,12 +1309,15 @@ mod tests {
perpaccount.taker_base_lots = 1;
perpaccount.taker_quote_lots = 2;
let oracle2_ai = oracle2.as_account_info();
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oracle2_ai.clone(),
perp1.as_account_info(),
oracle2_ai,
oo1.as_account_info(),
];
@ -1298,10 +1338,12 @@ mod tests {
#[test]
fn test_scanning_account_retriever() {
let oracle1_price = 1.0;
let oracle2_price = 5.0;
let group = Pubkey::new_unique();
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 4, 5.0, 0.5, 0.3);
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, oracle1_price, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 4, oracle2_price, 0.5, 0.3);
let (mut bank3, _) = mock_bank_and_oracle(group, 5, 1.0, 0.5, 0.3);
// bank3 reuses the bank2 oracle, to ensure the ScanningAccountRetriever doesn't choke on that
@ -1311,17 +1353,22 @@ mod tests {
let oo1key = oo1.pubkey;
oo1.data().native_pc_total = 20;
let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 9, 0.2, 0.1);
let mut perp2 = mock_perp_market(group, oracle1.pubkey, 8, 0.2, 0.1);
let oracle1_account_info = oracle1.as_account_info();
let oracle2_account_info = oracle2.as_account_info();
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle1_account_info.clone(),
oracle2_account_info.clone(),
oracle2_account_info.clone(),
oracle2_account_info,
perp1.as_account_info(),
perp2.as_account_info(),
oracle2_account_info,
oracle1_account_info,
oo1.as_account_info(),
];
@ -1330,8 +1377,9 @@ mod tests {
assert_eq!(retriever.banks.len(), 3);
assert_eq!(retriever.token_index_map.len(), 3);
assert_eq!(retriever.oracles.len(), 3);
assert_eq!(retriever.perp_markets.len(), 1);
assert_eq!(retriever.perp_index_map.len(), 1);
assert_eq!(retriever.perp_markets.len(), 2);
assert_eq!(retriever.perp_oracles.len(), 2);
assert_eq!(retriever.perp_index_map.len(), 2);
assert_eq!(retriever.serum3_oos.len(), 1);
{
@ -1372,10 +1420,21 @@ mod tests {
assert!(retriever.serum_oo(1, &Pubkey::default()).is_err());
let perp = retriever.perp_market(&group, 0, 9).unwrap();
let (perp, oracle_price) = retriever
.perp_market_and_oracle_price(&group, 0, 9)
.unwrap();
assert_eq!(identity(perp.perp_market_index), 9);
assert_eq!(oracle_price, oracle2_price);
assert!(retriever.perp_market(&group, 1, 5).is_err());
let (perp, oracle_price) = retriever
.perp_market_and_oracle_price(&group, 1, 8)
.unwrap();
assert_eq!(identity(perp.perp_market_index), 8);
assert_eq!(oracle_price, oracle1_price);
assert!(retriever
.perp_market_and_oracle_price(&group, 1, 5)
.is_err());
}
#[derive(Default)]
@ -1435,7 +1494,7 @@ mod tests {
oo2.data().native_pc_total = testcase.oo_1_3.0;
oo2.data().native_coin_total = testcase.oo_1_3.1;
let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1);
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 9, 0.2, 0.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.change_base_and_quote_positions(
perp1.data(),
@ -1445,14 +1504,16 @@ mod tests {
perpaccount.bids_base_lots = testcase.perp1.2;
perpaccount.asks_base_lots = testcase.perp1.3;
let oracle2_ai = oracle2.as_account_info();
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oracle2_ai.clone(),
oracle3.as_account_info(),
perp1.as_account_info(),
oracle2_ai,
oo1.as_account_info(),
oo2.as_account_info(),
];
@ -1785,16 +1846,18 @@ mod tests {
)
.unwrap();
let mut perp1 = mock_perp_market(group, 9, 1, 0.2, 0.1);
let mut perp1 = mock_perp_market(group, oracle1.pubkey, 9, 0.2, 0.1);
perp1.data().long_funding = I80F48::from_num(10.1);
let perpaccount = account.ensure_perp_position(9).unwrap().0;
perpaccount.change_base_and_quote_positions(perp1.data(), 10, I80F48::from(-110));
perpaccount.long_settled_funding = I80F48::from_num(10.0);
let oracle1_ai = oracle1.as_account_info();
let ais = vec![
bank1.as_account_info(),
oracle1.as_account_info(),
oracle1_ai.clone(),
perp1.as_account_info(),
oracle1_ai,
];
let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap();
@ -1811,4 +1874,35 @@ mod tests {
- 1.0
));
}
#[test]
fn test_scanning_retreiver_mismatched_oracle_for_perps_throws_error() {
let group = Pubkey::new_unique();
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 4, 5.0, 0.5, 0.3);
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
let mut perp1 = mock_perp_market(group, oracle1.pubkey, 9, 0.2, 0.1);
let mut perp2 = mock_perp_market(group, oracle2.pubkey, 8, 0.2, 0.1);
let oracle1_account_info = oracle1.as_account_info();
let oracle2_account_info = oracle2.as_account_info();
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
oracle1_account_info.clone(),
oracle2_account_info.clone(),
perp1.as_account_info(),
perp2.as_account_info(),
oracle2_account_info, // Oracles wrong way around
oracle1_account_info,
oo1.as_account_info(),
];
let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap();
let result = retriever.perp_market_and_oracle_price(&group, 0, 9);
assert!(result.is_err());
}
}

View File

@ -419,7 +419,6 @@ mod tests {
fn create_perp_market() -> PerpMarket {
return PerpMarket {
group: Pubkey::new_unique(),
base_token_index: 0,
perp_market_index: 0,
name: Default::default(),
oracle: Pubkey::new_unique(),
@ -449,8 +448,9 @@ mod tests {
fees_accrued: I80F48::ZERO,
fees_settled: I80F48::ZERO,
bump: 0,
base_token_decimals: 0,
base_decimals: 0,
reserved: [0; 112],
padding0: Default::default(),
padding1: Default::default(),
padding2: Default::default(),
registration_time: 0,

View File

@ -116,7 +116,7 @@ pub fn determine_oracle_type(acc_info: &impl KeyedAccountReader) -> Result<Oracl
pub fn oracle_price(
acc_info: &impl KeyedAccountReader,
oracle_conf_filter: I80F48,
base_token_decimals: u8,
base_decimals: u8,
) -> Result<I80F48> {
let data = &acc_info.data();
let oracle_type = determine_oracle_type(acc_info)?;
@ -142,8 +142,7 @@ pub fn oracle_price(
return Err(MangoError::SomeError.into());
}
let decimals =
cm!((price_account.expo as i8) + QUOTE_DECIMALS - (base_token_decimals as i8));
let decimals = cm!((price_account.expo as i8) + QUOTE_DECIMALS - (base_decimals as i8));
let decimal_adj = power_of_ten(decimals);
cm!(price * decimal_adj)
}
@ -173,7 +172,7 @@ pub fn oracle_price(
return Err(MangoError::SomeError.into());
}
let decimals = cm!(QUOTE_DECIMALS - (base_token_decimals as i8));
let decimals = cm!(QUOTE_DECIMALS - (base_decimals as i8));
let decimal_adj = power_of_ten(decimals);
cm!(price * decimal_adj)
}
@ -195,7 +194,7 @@ pub fn oracle_price(
return Err(MangoError::SomeError.into());
}
let decimals = cm!(QUOTE_DECIMALS - (base_token_decimals as i8));
let decimals = cm!(QUOTE_DECIMALS - (base_decimals as i8));
let decimal_adj = power_of_ten(decimals);
cm!(price * decimal_adj)
}

View File

@ -6,8 +6,8 @@ use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use crate::accounts_zerocopy::KeyedAccountReader;
use crate::state::oracle;
use crate::state::orderbook::order_type::Side;
use crate::state::{oracle, TokenIndex};
use crate::util::checked_math as cm;
use super::{Book, OracleConfig, DAY_I80F48};
@ -20,9 +20,8 @@ pub struct PerpMarket {
// ABI: Clients rely on this being at offset 8
pub group: Pubkey,
// TODO: Remove!
// ABI: Clients rely on this being at offset 40
pub base_token_index: TokenIndex,
pub padding0: [u8; 2],
/// Lookup indices
pub perp_market_index: PerpMarketIndex,
@ -85,7 +84,7 @@ pub struct PerpMarket {
/// PDA bump
pub bump: u8,
pub base_token_decimals: u8,
pub base_decimals: u8,
pub padding2: [u8; 6],
@ -125,7 +124,7 @@ impl PerpMarket {
oracle::oracle_price(
oracle_acc,
self.oracle_config.conf_filter,
self.base_token_decimals,
self.base_decimals,
)
}

View File

@ -166,6 +166,14 @@ fn get_perp_market_address_by_index(group: Pubkey, perp_market_index: PerpMarket
.0
}
async fn get_oracle_address_from_perp_market_address(
account_loader: &impl ClientAccountLoader,
perp_market_address: &Pubkey,
) -> Pubkey {
let perp_market: PerpMarket = account_loader.load(&perp_market_address).await.unwrap();
perp_market.oracle
}
// all the accounts that instructions like deposit/withdraw need to compute account health
async fn derive_health_check_remaining_account_metas(
account_loader: &impl ClientAccountLoader,
@ -201,6 +209,14 @@ async fn derive_health_check_remaining_account_metas(
.active_perp_positions()
.map(|perp| get_perp_market_address_by_index(account.fixed.group, perp.market_index));
let mut perp_oracles = vec![];
for perp in adjusted_account
.active_perp_positions()
.map(|perp| get_perp_market_address_by_index(account.fixed.group, perp.market_index))
{
perp_oracles.push(get_oracle_address_from_perp_market_address(account_loader, &perp).await)
}
let serum_oos = account.active_serum3_orders().map(|&s| s.open_orders);
let to_account_meta = |pubkey| AccountMeta {
@ -218,6 +234,7 @@ async fn derive_health_check_remaining_account_metas(
})
.chain(oracles.into_iter().map(to_account_meta))
.chain(perp_markets.map(to_account_meta))
.chain(perp_oracles.into_iter().map(to_account_meta))
.chain(serum_oos.map(to_account_meta))
.collect()
}
@ -251,11 +268,17 @@ async fn derive_liquidation_remaining_account_metas(
oracles.push(mint_info.oracle);
}
let perp_markets = liqee
let perp_markets: Vec<Pubkey> = liqee
.active_perp_positions()
.chain(liqee.active_perp_positions())
.map(|perp| get_perp_market_address_by_index(liqee.fixed.group, perp.market_index))
.unique();
.unique()
.collect();
let mut perp_oracles = vec![];
for &perp in &perp_markets {
perp_oracles.push(get_oracle_address_from_perp_market_address(account_loader, &perp).await)
}
let serum_oos = liqee
.active_serum3_orders()
@ -276,7 +299,8 @@ async fn derive_liquidation_remaining_account_metas(
is_signer: false,
})
.chain(oracles.into_iter().map(to_account_meta))
.chain(perp_markets.map(to_account_meta))
.chain(perp_markets.into_iter().map(to_account_meta))
.chain(perp_oracles.into_iter().map(to_account_meta))
.chain(serum_oos.map(to_account_meta))
.collect()
}
@ -2107,8 +2131,7 @@ pub struct PerpCreateMarketInstruction {
pub event_queue: Pubkey,
pub payer: TestKeypair,
pub perp_market_index: PerpMarketIndex,
pub base_token_index: TokenIndex,
pub base_token_decimals: u8,
pub base_decimals: u8,
pub quote_lot_size: i64,
pub base_lot_size: i64,
pub maint_asset_weight: f32,
@ -2135,8 +2158,7 @@ impl PerpCreateMarketInstruction {
.create_account_for_type::<EventQueue>(&mango_v4::id())
.await,
oracle: base.oracle,
base_token_index: base.index,
base_token_decimals: base.mint.decimals,
base_decimals: base.mint.decimals,
..PerpCreateMarketInstruction::default()
}
}
@ -2156,7 +2178,6 @@ impl ClientInstruction for PerpCreateMarketInstruction {
conf_filter: I80F48::from_num::<f32>(0.10),
},
perp_market_index: self.perp_market_index,
base_token_index_opt: Option::from(self.base_token_index),
quote_lot_size: self.quote_lot_size,
base_lot_size: self.base_lot_size,
maint_asset_weight: self.maint_asset_weight,
@ -2169,7 +2190,7 @@ impl ClientInstruction for PerpCreateMarketInstruction {
max_funding: 0.05,
min_funding: 0.05,
impact_quantity: 100,
base_token_decimals: self.base_token_decimals,
base_decimals: self.base_decimals,
};
let perp_market = Pubkey::find_program_address(
@ -2666,6 +2687,7 @@ impl ClientInstruction for PerpLiqForceCancelOrdersInstruction {
account: self.account,
bids: perp_market.bids,
asks: perp_market.asks,
oracle: perp_market.oracle,
};
let mut instruction = make_instruction(program_id, &accounts, instruction);
instruction.accounts.extend(health_check_metas);

View File

@ -221,10 +221,6 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
liquidation_fee: 0.012,
maker_fee: 0.0002,
taker_fee: 0.000,
// HACK: Currently the base_token_index token needs to be active on the account.
// Using token[0] for each market allows us to have multiple perp positions with
// just a single token position.
base_token_index: tokens[0].index,
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &token).await
},
)

View File

@ -31,7 +31,6 @@ export class PerpMarket {
publicKey: PublicKey,
obj: {
group: PublicKey;
baseTokenIndex: number;
quoteTokenIndex: number;
perpMarketIndex: number;
name: number[];
@ -59,14 +58,13 @@ export class PerpMarket {
seqNum: any; // TODO: ts complains that this is unknown for whatever reason
feesAccrued: I80F48Dto;
bump: number;
baseTokenDecimals: number;
baseDecimals: number;
registrationTime: BN;
},
): PerpMarket {
return new PerpMarket(
publicKey,
obj.group,
obj.baseTokenIndex,
obj.quoteTokenIndex,
obj.perpMarketIndex,
obj.name,
@ -94,7 +92,7 @@ export class PerpMarket {
obj.seqNum,
obj.feesAccrued,
obj.bump,
obj.baseTokenDecimals,
obj.baseDecimals,
obj.registrationTime,
);
}
@ -102,7 +100,6 @@ export class PerpMarket {
constructor(
public publicKey: PublicKey,
public group: PublicKey,
public baseTokenIndex: number,
public quoteTokenIndex: number,
public perpMarketIndex: number,
name: number[],
@ -130,7 +127,7 @@ export class PerpMarket {
seqNum: BN,
feesAccrued: I80F48Dto,
bump: number,
public baseTokenDecimals: number,
public baseDecimals: number,
public registrationTime: BN,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];

View File

@ -1273,8 +1273,7 @@ export class MangoClient {
perpMarketIndex: number,
name: string,
oracleConfFilter: number,
baseTokenIndex: number,
baseTokenDecimals: number,
baseDecimals: number,
quoteTokenIndex: number,
quoteLotSize: number,
baseLotSize: number,
@ -1302,8 +1301,7 @@ export class MangoClient {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
baseTokenIndex,
baseTokenDecimals,
baseDecimals,
new BN(quoteLotSize),
new BN(baseLotSize),
maintAssetWeight,
@ -1374,8 +1372,7 @@ export class MangoClient {
perpMarketName: string,
oracle: PublicKey,
oracleConfFilter: number,
baseTokenIndex: number,
baseTokenDecimals: number,
baseDecimals: number,
maintAssetWeight: number,
initAssetWeight: number,
maintLiabWeight: number,
@ -1397,8 +1394,7 @@ export class MangoClient {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
baseTokenIndex,
baseTokenDecimals,
baseDecimals,
maintAssetWeight,
initAssetWeight,
maintLiabWeight,
@ -1439,10 +1435,7 @@ export class MangoClient {
.rpc();
}
public async perpGetMarkets(
group: Group,
baseTokenIndex?: number,
): Promise<PerpMarket[]> {
public async perpGetMarkets(group: Group): Promise<PerpMarket[]> {
const bumpfbuf = Buffer.alloc(1);
bumpfbuf.writeUInt8(255);
@ -1455,17 +1448,6 @@ export class MangoClient {
},
];
if (baseTokenIndex) {
const bbuf = Buffer.alloc(2);
bbuf.writeUInt16LE(baseTokenIndex);
filters.push({
memcmp: {
bytes: bs58.encode(bbuf),
offset: 40,
},
});
}
return (await this.program.account.perpMarket.all(filters)).map((tuple) =>
PerpMarket.from(tuple.publicKey, tuple.account),
);
@ -1910,6 +1892,18 @@ export class MangoClient {
)[0].publicKey,
),
);
healthRemainingAccounts.push(
...mangoAccount.perps
.filter((perp) => perp.marketIndex !== 65535)
.map(
(perp) =>
Array.from(group.perpMarketsMap.values()).filter(
(perpMarket) => perpMarket.perpMarketIndex === perp.marketIndex,
)[0].oracle,
),
);
for (const perpMarket of perpMarkets) {
const alreadyAdded = mangoAccount.perps.find(
(p) => p.marketIndex === perpMarket.perpMarketIndex,
@ -1968,23 +1962,17 @@ export class MangoClient {
...mintInfos.map((mintInfo) => mintInfo.oracle),
);
for (const mangoAccount of mangoAccounts) {
healthRemainingAccounts.push(
...mangoAccount.serum3
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
.map((serum3Account) => serum3Account.openOrders),
);
}
const perpsToAdd: PerpMarket[] = [];
for (const mangoAccount of mangoAccounts) {
healthRemainingAccounts.push(
perpsToAdd.push(
...mangoAccount.perps
.filter((perp) => perp.marketIndex !== 65535)
.map(
(perp) =>
Array.from(group.perpMarketsMap.values()).filter(
(perpMarket) => perpMarket.perpMarketIndex === perp.marketIndex,
)[0].publicKey,
)[0],
),
);
}
@ -1994,15 +1982,28 @@ export class MangoClient {
(p) => p.marketIndex === perpMarket.perpMarketIndex,
);
if (!alreadyAdded) {
healthRemainingAccounts.push(
perpsToAdd.push(
Array.from(group.perpMarketsMap.values()).filter(
(p) => p.perpMarketIndex === perpMarket.perpMarketIndex,
)[0].publicKey,
)[0],
);
}
}
}
// Add perp accounts
healthRemainingAccounts.push(...perpsToAdd.map((p) => p.publicKey));
// Add oracle for each perp
healthRemainingAccounts.push(...perpsToAdd.map((p) => p.oracle));
for (const mangoAccount of mangoAccounts) {
healthRemainingAccounts.push(
...mangoAccount.serum3
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
.map((serum3Account) => serum3Account.openOrders),
);
}
return healthRemainingAccounts;
}
}

View File

@ -2257,13 +2257,7 @@ export type MangoV4 = {
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimals",
"name": "baseDecimals",
"type": "u8"
},
{
@ -2351,13 +2345,7 @@ export type MangoV4 = {
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimalsOpt",
"name": "baseDecimalsOpt",
"type": {
"option": "u8"
}
@ -2953,6 +2941,11 @@ export type MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "oracle",
"isMut": false,
"isSigner": false
}
],
"args": [
@ -3726,8 +3719,13 @@ export type MangoV4 = {
"type": "publicKey"
},
{
"name": "baseTokenIndex",
"type": "u16"
"name": "padding0",
"type": {
"array": [
"u8",
2
]
}
},
{
"name": "perpMarketIndex",
@ -3901,7 +3899,7 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "baseTokenDecimals",
"name": "baseDecimals",
"type": "u8"
},
{
@ -4293,6 +4291,12 @@ export type MangoV4 = {
"type": {
"defined": "I80F48"
}
},
{
"name": "oraclePrice",
"type": {
"defined": "I80F48"
}
}
]
}
@ -8256,13 +8260,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimals",
"name": "baseDecimals",
"type": "u8"
},
{
@ -8350,13 +8348,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimalsOpt",
"name": "baseDecimalsOpt",
"type": {
"option": "u8"
}
@ -8952,6 +8944,11 @@ export const IDL: MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "oracle",
"isMut": false,
"isSigner": false
}
],
"args": [
@ -9725,8 +9722,13 @@ export const IDL: MangoV4 = {
"type": "publicKey"
},
{
"name": "baseTokenIndex",
"type": "u16"
"name": "padding0",
"type": {
"array": [
"u8",
2
]
}
},
{
"name": "perpMarketIndex",
@ -9900,7 +9902,7 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "baseTokenDecimals",
"name": "baseDecimals",
"type": "u8"
},
{
@ -10292,6 +10294,12 @@ export const IDL: MangoV4 = {
"type": {
"defined": "I80F48"
}
},
{
"name": "oraclePrice",
"type": {
"defined": "I80F48"
}
}
]
}