add pyth oracle and instruction to switch

This commit is contained in:
Maximilian Schneider 2022-07-18 13:41:21 +02:00
parent 4a88ae5045
commit 7f27d4508a
3 changed files with 117 additions and 13 deletions

View File

@ -67,6 +67,8 @@ pub enum MangoErrorCode {
FeeDiscountFunctionality,
#[error("MangoErrorCode::Deprecated")]
Deprecated,
#[error("MangoErrorCode::OracleOffline")]
OracleOffline,
#[error("MangoErrorCode::Default Check the source code for more info")]
Default = u32::MAX_VALUE,
@ -92,6 +94,14 @@ impl From<serum_dex::error::DexError> for MangoError {
}
}
impl From<pyth_client::PythError> for MangoError {
fn from(pyth_e: pyth_client::PythError) -> Self {
let prog_e: ProgramError = pyth_e.into();
prog_e.into()
}
}
#[inline]
pub fn check_assert(
cond: bool,

View File

@ -36,7 +36,7 @@ pub enum MangoInstruction {
/// 7+2*NUM_TOKENS..7+2*NUM_TOKENS+NUM_MARKETS `[]`
/// spot_market_accs - MarketState account from serum dex for each of the spot markets
/// 7+2*NUM_TOKENS+NUM_MARKETS..7+2*NUM_TOKENS+2*NUM_MARKETS `[]`
/// oracle_accs - Solana Flux Aggregator accounts corresponding to each trading pair
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
InitMangoGroup {
signer_nonce: u64,
maint_coll_ratio: U64F64,
@ -83,7 +83,7 @@ pub enum MangoInstruction {
/// 7. `[]` clock_acc - Clock sysvar account
/// 8..8+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
/// 8+NUM_MARKETS..8+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
Withdraw {
quantity: u64
},
@ -98,7 +98,7 @@ pub enum MangoInstruction {
/// 3. `[]` clock_acc - Clock sysvar account
/// 4..4+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
/// 4+NUM_MARKETS..4+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
Borrow {
token_index: usize,
quantity: u64
@ -128,7 +128,7 @@ pub enum MangoInstruction {
/// 4. `[]` clock_acc - Clock sysvar account
/// 5..5+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
/// 5+NUM_MARKETS..5+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
/// 5+2*NUM_MARKETS..5+2*NUM_MARKETS+NUM_TOKENS `[writable]`
/// vault_accs - MangoGroup vaults
/// 5+2*NUM_MARKETS+NUM_TOKENS..5+2*NUM_MARKETS+2*NUM_TOKENS `[writable]`
@ -197,7 +197,7 @@ pub enum MangoInstruction {
/// 16. `[writable]` srm_vault_acc - MangoGroup's srm_vault used for fee reduction
/// 17..17+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
/// 17+NUM_MARKETS..17+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
PlaceOrder {
order: serum_dex::instruction::NewOrderInstructionV3
},
@ -297,7 +297,7 @@ pub enum MangoInstruction {
/// 18. `[]` dex_signer_acc - signer for serum dex MarketState
/// 19..19+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
/// 19+NUM_MARKETS..19+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
PlaceAndSettle {
order: serum_dex::instruction::NewOrderInstructionV3
},
@ -324,7 +324,7 @@ pub enum MangoInstruction {
/// 15. `[]` clock_acc - Clock sysvar account
/// 16..16+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
/// 16+NUM_MARKETS..16+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
ForceCancelOrders {
/// Max orders to cancel -- could be useful to lower this if running into compute limits
/// Recommended: 5
@ -347,7 +347,7 @@ pub enum MangoInstruction {
/// 9. `[]` clock_acc - Clock sysvar account
/// 10..10+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
/// 10+NUM_MARKETS..10+2*NUM_MARKETS `[]`
/// oracle_accs - flux aggregator feed accounts
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
PartialLiquidate {
/// Quantity of the token being deposited to repay borrows
max_deposit: u64
@ -356,7 +356,13 @@ pub enum MangoInstruction {
AddMarginAccountInfo {
info: [u8; INFO_LEN]
}
},
/// Allows to switch the oracles to a new set
/// 0. `[writable]` mango_group_acc - the data account to store mango group state vars
/// 1. `[signer]` admin_acc - admin key that created the group
/// 2..2+NUM_MARKETS `[]` oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
SwitchOracles
}
@ -508,6 +514,9 @@ impl MangoInstruction {
info: *info
}
}
18 => {
MangoInstruction::SwitchOracles
}
_ => { return None; }
})
}
@ -1233,4 +1242,26 @@ pub fn add_margin_account_info(
accounts,
data
})
}
pub fn switch_oracles(
program_id: &Pubkey,
mango_group_pk: &Pubkey,
admin_pk: &Pubkey,
oracle_pks: &[Pubkey],
) -> Result<Instruction, ProgramError> {
let mut accounts = vec![
AccountMeta::new(*mango_group_pk, false),
AccountMeta::new_readonly(*admin_pk, true),
];
accounts.extend(oracle_pks.iter().map(
|pk| AccountMeta::new_readonly(*pk, false))
);
let instr = MangoInstruction::SwitchOracles {};
let data = instr.pack();
Ok(Instruction {
program_id: *program_id,
accounts,
data
})
}

View File

@ -1,11 +1,13 @@
use std::cmp;
use std::cmp::min;
use std::mem::size_of;
use std::ops::Neg;
use arrayref::{array_ref, array_refs};
use fixed::types::U64F64;
use fixed_macro::types::U64F64;
use flux_aggregator::borsh_state::InitBorshState;
use pyth_client::PriceStatus;
use serum_dex::matching::Side;
use serum_dex::state::ToAlignedBytes;
use solana_program::account_info::AccountInfo;
@ -1375,6 +1377,52 @@ impl Processor {
margin_account.info = info;
Ok(())
}
#[inline(never)]
fn switch_oracles(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> MangoResult<()> {
const NUM_FIXED: usize = 2;
let accounts = array_ref![accounts, 0, NUM_FIXED + NUM_MARKETS];
let (fixed_accs, oracle_accs) = array_refs![accounts, NUM_FIXED, NUM_MARKETS];
let [
mango_group_acc,
admin_acc,
] = fixed_accs;
let mut mango_group = MangoGroup::load_mut_checked(
mango_group_acc, program_id)?;
check_eq!(admin_acc.key, &mango_group.admin, MangoErrorCode::InvalidGroupOwner)?;
check!(admin_acc.is_signer, MangoErrorCode::SignerNecessary)?;
for i in 0..NUM_MARKETS {
mango_group.oracles[i] = *oracle_accs[i].key;
// determine oracle type
let borrowed = oracle_accs[i].data.borrow();
let magic = u32::from_le_bytes(*array_ref![borrowed, 0, 4]);
// read oracle decimals
let decimals = if magic == pyth_client::MAGIC {
// detected pyth oracle
let price_account = pyth_client::load_price(&borrowed)?;
// usually expo is -8, verify anyways that it's within bounds
check!(price_account.expo <= 0, MangoErrorCode::Default);
check!(price_account.expo >= -255, MangoErrorCode::Default);
price_account.expo.neg() as u8
} else {
// fall back to legacy flux aggregator
let oracle = flux_aggregator::state::Aggregator::load_initialized(&oracle_accs[i])?;
oracle.config.decimals
};
mango_group.oracle_decimals[i] = decimals;
}
Ok(())
}
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
@ -1492,6 +1540,10 @@ impl Processor {
msg!("Mango: AddMarginAccountInfo");
Self::add_margin_account_info(program_id, accounts, info)?;
}
MangoInstruction::SwitchOracles => {
msg!("Mango: SwitchOracles");
Self::switch_oracles(program_id, accounts)?;
}
}
Ok(())
}
@ -1671,16 +1723,27 @@ pub fn get_prices(
for i in 0..NUM_MARKETS {
check_eq_default!(&mango_group.oracles[i], oracle_accs[i].key)?;
// TODO store this info in MangoGroup, first make sure it cannot be changed by solink
let base_adj = U64F64::from_num(10u64.pow(mango_group.mint_decimals[i] as u32));
let quote_adj = U64F64::from_num(
10u64.pow(quote_decimals.checked_sub(mango_group.oracle_decimals[i]).unwrap() as u32)
);
let answer = flux_aggregator::read_median(&oracle_accs[i])?; // this is in USD cents
// determine oracle type
let borrowed = oracle_accs[i].data.borrow();
let magic = u32::from_le_bytes(*array_ref![borrowed, 0, 4]);
let value = U64F64::from_num(answer.median);
// read oracle value
let value = if magic == pyth_client::MAGIC {
// detected pyth oracle
let price_account = pyth_client::load_price(&borrowed)?;
check_eq!(price_account.get_current_price_status(), PriceStatus::Trading, MangoErrorCode::OracleOffline);
U64F64::from_num(price_account.agg.price)
} else {
// fall back to legacy flux aggregator
let answer = flux_aggregator::read_median(&oracle_accs[i])?; // this is in USD cents
U64F64::from_num(answer.median)
};
let base_adj = U64F64::from_num(10u64.pow(mango_group.mint_decimals[i] as u32));
prices[i] = quote_adj
.checked_div(base_adj).unwrap()
.checked_mul(value).unwrap();