diff --git a/programs/mango-v4/src/instructions/liq_token_with_token.rs b/programs/mango-v4/src/instructions/liq_token_with_token.rs index 82ce70625..98f05292f 100644 --- a/programs/mango-v4/src/instructions/liq_token_with_token.rs +++ b/programs/mango-v4/src/instructions/liq_token_with_token.rs @@ -71,8 +71,8 @@ pub fn liq_token_with_token( account_retriever.bank_mut_and_oracle(asset_token_index)?; let (mut liab_bank, liab_oracle) = account_retriever.bank_mut_and_oracle(liab_token_index)?; - let asset_price = oracle_price(asset_oracle)?; - let liab_price = oracle_price(liab_oracle)?; + let asset_price = oracle_price(asset_oracle, asset_bank.mint_decimals)?; + let liab_price = oracle_price(liab_oracle, liab_bank.mint_decimals)?; let liqee_assets_native = liqee .tokens diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index 8ede1485c..6a739f07b 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -2,6 +2,7 @@ use anchor_lang::prelude::*; use fixed::types::I80F48; use crate::error::MangoError; + use crate::state::*; use crate::util::fill16_from_str; @@ -46,6 +47,7 @@ pub fn perp_create_market( perp_market_index: PerpMarketIndex, name: String, base_token_index_opt: Option, + base_token_decimals: u8, quote_token_index: TokenIndex, quote_lot_size: i64, base_lot_size: i64, @@ -79,17 +81,18 @@ pub fn perp_create_market( taker_fee: I80F48::from_num(taker_fee), min_funding: I80F48::from_num(min_funding), max_funding: I80F48::from_num(max_funding), + impact_quantity, long_funding: I80F48::ZERO, short_funding: I80F48::ZERO, funding_last_updated: Clock::get()?.unix_timestamp, - impact_quantity, open_interest: 0, seq_num: 0, fees_accrued: I80F48::ZERO, bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?, - reserved: Default::default(), - perp_market_index, + // Why optional - Perp could be based purely on an oracle base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(), + base_token_decimals, + perp_market_index, quote_token_index, }; diff --git a/programs/mango-v4/src/instructions/perp_place_order.rs b/programs/mango-v4/src/instructions/perp_place_order.rs index d63ab2041..3152084e7 100644 --- a/programs/mango-v4/src/instructions/perp_place_order.rs +++ b/programs/mango-v4/src/instructions/perp_place_order.rs @@ -89,7 +89,10 @@ pub fn perp_place_order( let mut event_queue = ctx.accounts.event_queue.load_mut()?; - let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?; + let oracle_price = oracle_price( + &ctx.accounts.oracle.to_account_info(), + perp_market.base_token_decimals, + )?; let now_ts = Clock::get()?.unix_timestamp as u64; let time_in_force = if expiry_timestamp != 0 { diff --git a/programs/mango-v4/src/instructions/perp_update_funding.rs b/programs/mango-v4/src/instructions/perp_update_funding.rs index ac42fc509..418ee6075 100644 --- a/programs/mango-v4/src/instructions/perp_update_funding.rs +++ b/programs/mango-v4/src/instructions/perp_update_funding.rs @@ -27,7 +27,10 @@ pub fn perp_update_funding(ctx: Context) -> Result<()> { let asks = &ctx.accounts.asks.to_account_info(); let book = Book::load_mut(bids, asks, &perp_market)?; - let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?; + let oracle_price = oracle_price( + &ctx.accounts.oracle.to_account_info(), + perp_market.base_token_decimals, + )?; perp_market.update_funding(&book, oracle_price, now_ts as u64)?; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index e6ba72f68..346c70347 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -196,6 +196,7 @@ pub mod mango_v4 { perp_market_index: PerpMarketIndex, name: String, base_token_index_opt: Option, + base_token_decimals: u8, quote_token_index: TokenIndex, quote_lot_size: i64, base_lot_size: i64, @@ -215,6 +216,7 @@ pub mod mango_v4 { perp_market_index, name, base_token_index_opt, + base_token_decimals, quote_token_index, quote_lot_size, base_lot_size, diff --git a/programs/mango-v4/src/state/health.rs b/programs/mango-v4/src/state/health.rs index 99317ef50..adc0c1de0 100644 --- a/programs/mango-v4/src/state/health.rs +++ b/programs/mango-v4/src/state/health.rs @@ -410,7 +410,7 @@ fn compute_health_detail<'a, 'b: 'a>( for (i, position) in account.tokens.iter_active().enumerate() { let (bank, oracle_ai) = retriever.bank_and_oracle(&account.group, i, position.token_index)?; - let oracle_price = oracle_price(oracle_ai)?; + let oracle_price = oracle_price(oracle_ai, bank.mint_decimals)?; // converts the token value to the basis token value for health computations // TODO: health basis token == USDC? diff --git a/programs/mango-v4/src/state/oracle.rs b/programs/mango-v4/src/state/oracle.rs index ba0f9d4cd..bd2eef038 100644 --- a/programs/mango-v4/src/state/oracle.rs +++ b/programs/mango-v4/src/state/oracle.rs @@ -5,9 +5,12 @@ use anchor_lang::Discriminator; use fixed::types::I80F48; use static_assertions::const_assert_eq; +use crate::checked_math as cm; use crate::error::MangoError; use crate::util::LoadZeroCopy; +pub const QUOTE_DECIMALS: u32 = 6; + #[derive(PartialEq)] pub enum OracleType { Stub, @@ -35,7 +38,7 @@ pub fn determine_oracle_type(data: &[u8]) -> Result { Err(MangoError::UnknownOracleType.into()) } -pub fn oracle_price(acc_info: &AccountInfo) -> Result { +pub fn oracle_price(acc_info: &AccountInfo, base_token_decimals: u8) -> Result { let data = &acc_info.try_borrow_data()?; let oracle_type = determine_oracle_type(data)?; @@ -43,7 +46,14 @@ pub fn oracle_price(acc_info: &AccountInfo) -> Result { OracleType::Stub => acc_info.load::()?.price, OracleType::Pyth => { let price_struct = pyth_sdk_solana::load_price(data).unwrap(); - I80F48::from_num(price_struct.price) + let price = I80F48::from_num(price_struct.price); + let decimals = (price_struct.expo as u32) + .checked_add(QUOTE_DECIMALS) + .unwrap() + .checked_sub(base_token_decimals as u32) + .unwrap(); + let decimal_adj = I80F48::from_num(10_u32.pow(decimals)); + cm!(price * decimal_adj) } }) } diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 71d7c1632..c8013e6fe 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -71,10 +71,12 @@ pub struct PerpMarket { /// PDA bump pub bump: u8, - pub reserved: [u8; 1], + + pub base_token_decimals: u8, /// Lookup indices pub perp_market_index: PerpMarketIndex, + pub base_token_index: TokenIndex, /// Cannot be chosen freely, must be the health-reference token, same for all PerpMarkets diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index a7751bd46..058843cca 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1343,6 +1343,7 @@ pub struct PerpCreateMarketInstruction<'keypair> { pub payer: &'keypair Keypair, pub perp_market_index: PerpMarketIndex, pub base_token_index: TokenIndex, + pub base_token_decimals: u8, pub quote_token_index: TokenIndex, pub quote_lot_size: i64, pub base_lot_size: i64, @@ -1380,6 +1381,7 @@ impl<'keypair> ClientInstruction for PerpCreateMarketInstruction<'keypair> { max_funding: 0.05, min_funding: 0.05, impact_quantity: 100, + base_token_decimals: self.base_token_decimals, }; let perp_market = Pubkey::find_program_address( @@ -1636,6 +1638,7 @@ pub struct PerpUpdateFundingInstruction { pub perp_market: Pubkey, pub bids: Pubkey, pub asks: Pubkey, + pub bank: Pubkey, pub oracle: Pubkey, } #[async_trait::async_trait(?Send)] diff --git a/programs/mango-v4/tests/test_health_compute.rs b/programs/mango-v4/tests/test_health_compute.rs index 5a6821e13..9edfd04f7 100644 --- a/programs/mango-v4/tests/test_health_compute.rs +++ b/programs/mango-v4/tests/test_health_compute.rs @@ -267,6 +267,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> { payer, perp_market_index: perp_market_index as PerpMarketIndex, base_token_index: quote_token.index, + base_token_decimals: quote_token.mint.decimals, quote_token_index: token.index, quote_lot_size: 10, base_lot_size: 100, diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index 508c4fb11..1668edeb2 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -149,6 +149,7 @@ async fn test_perp() -> Result<(), TransportError> { payer, perp_market_index: 0, base_token_index: tokens[0].index, + base_token_decimals: tokens[0].mint.decimals, quote_token_index: tokens[1].index, quote_lot_size: 10, base_lot_size: 100, diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index d9af59dc4..734436667 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -700,6 +700,7 @@ export class MangoClient { perpMarketIndex: number, name: string, baseTokenIndex: number, + baseTokenDecimals: number, quoteTokenIndex: number, quoteLotSize: number, baseLotSize: number, @@ -723,6 +724,7 @@ export class MangoClient { perpMarketIndex, name, baseTokenIndex, + baseTokenDecimals, quoteTokenIndex, new BN(quoteLotSize), new BN(baseLotSize), diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 36ccd3fb7..efb96abca 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1212,6 +1212,12 @@ export type MangoV4 = { "option": "u16" } }, + { + "name": "baseTokenDecimalsOpt", + "type": { + "option": "u8" + } + }, { "name": "quoteTokenIndex", "type": "u16" @@ -2149,13 +2155,8 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", - "type": { - "array": [ - "u8", - 1 - ] - } + "name": "baseTokenDecimals", + "type": "u8" }, { "name": "perpMarketIndex", @@ -4112,6 +4113,12 @@ export const IDL: MangoV4 = { "option": "u16" } }, + { + "name": "baseTokenDecimalsOpt", + "type": { + "option": "u8" + } + }, { "name": "quoteTokenIndex", "type": "u16" @@ -5049,13 +5056,8 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", - "type": { - "array": [ - "u8", - 1 - ] - } + "name": "baseTokenDecimals", + "type": "u8" }, { "name": "perpMarketIndex", diff --git a/ts/client/src/scripts/example1-admin.ts b/ts/client/src/scripts/example1-admin.ts index 4205c47dd..4efd5db06 100644 --- a/ts/client/src/scripts/example1-admin.ts +++ b/ts/client/src/scripts/example1-admin.ts @@ -204,6 +204,7 @@ async function main() { 0, 'BTC/USDC', 0, + 6, 1, 10, 100,