Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Back to 64 byte blocks for the market #49

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions client/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.150.try_into().unwrap(),
price_mantissa: 150,
price_exponent: -3,
is_bid: true,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Limit,
Expand All @@ -310,7 +311,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.180.try_into().unwrap(),
price_mantissa: 180,
price_exponent: -3,
is_bid: false,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Limit,
Expand Down Expand Up @@ -456,7 +458,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.150.try_into().unwrap(),
price_mantissa: 150,
price_exponent: -3,
is_bid: true,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Limit,
Expand All @@ -471,7 +474,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.180.try_into().unwrap(),
price_mantissa: 180,
price_exponent: -3,
is_bid: false,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Limit,
Expand Down Expand Up @@ -714,7 +718,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.150.try_into().unwrap(),
price_mantissa: 150,
price_exponent: -3,
is_bid: true,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Global,
Expand All @@ -729,7 +734,8 @@ mod test {
market: market_key,
trader_index,
num_base_atoms: BaseAtoms::new(10_000),
price: 0.180.try_into().unwrap(),
price_mantissa: 180,
price_exponent: -3,
last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
order_type: OrderType::Limit,
global_trade_accounts_opts: &[None, None],
Expand Down
1 change: 0 additions & 1 deletion lib/src/red_black_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,6 @@ impl<'a, V: TreeValue> RedBlackTree<'a, V> {
self.rotate_left(grandparent_index);
self.set_color(index_to_fix, grandparent_color);
self.set_color(grandparent_index, index_to_fix_color);

}
}

Expand Down
2 changes: 2 additions & 0 deletions programs/manifest/src/program/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub enum ManifestError {
IncorrectAccount = 17,
#[error("Mint not allowed for market")]
InvalidMint = 18,
#[error("PriceConversionError")]
PriceConversionError = 19,
}

impl From<ManifestError> for ProgramError {
Expand Down
24 changes: 16 additions & 8 deletions programs/manifest/src/program/processor/batch_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::cell::RefMut;
use crate::{
logs::{emit_stack, CancelOrderLog, PlaceOrderLog},
program::{assert_with_msg, ManifestError},
quantities::{BaseAtoms, PriceConversionError, QuoteAtomsPerBaseAtom, WrapperU64},
quantities::{BaseAtoms, QuoteAtomsPerBaseAtom, WrapperU64},
state::{
claimed_seat::ClaimedSeat, AddOrderToMarketArgs, AddOrderToMarketResult, MarketRefMut,
OrderType, RestingOrder, MARKET_BLOCK_SIZE,
Expand All @@ -13,7 +13,8 @@ use crate::{
use borsh::{BorshDeserialize, BorshSerialize};
use hypertree::{get_helper, trace, DataIndex, PodBool, RBNode};
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program::set_return_data, pubkey::Pubkey,
account_info::AccountInfo, entrypoint::ProgramResult, program::set_return_data,
program_error::ProgramError, pubkey::Pubkey,
};

use super::{expand_market_if_needed, shared::get_mut_dynamic_account};
Expand Down Expand Up @@ -76,7 +77,7 @@ impl PlaceOrderParams {
pub fn base_atoms(&self) -> u64 {
self.base_atoms
}
pub fn try_price(&self) -> Result<QuoteAtomsPerBaseAtom, PriceConversionError> {
pub fn try_price(&self) -> Result<QuoteAtomsPerBaseAtom, ProgramError> {
QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
self.price_mantissa,
self.price_exponent,
Expand All @@ -91,6 +92,12 @@ impl PlaceOrderParams {
pub fn order_type(&self) -> OrderType {
self.order_type
}
pub fn price_mantissa(&self) -> u32 {
self.price_mantissa
}
pub fn price_exponent(&self) -> i8 {
self.price_exponent
}
}

#[derive(BorshDeserialize, BorshSerialize)]
Expand Down Expand Up @@ -204,24 +211,24 @@ pub(crate) fn process_batch_update(
assert_with_msg(
trader_index % (MARKET_BLOCK_SIZE as DataIndex) == 0,
ManifestError::WrongIndexHintParams,
&format!("Invalid cancel hint index {}", hinted_cancel_index),
&format!("Invalid cancel hint index due to alignment {}", hinted_cancel_index),
)?;
assert_with_msg(
get_helper::<RBNode<ClaimedSeat>>(
get_helper::<RBNode<RestingOrder>>(
&dynamic_account.dynamic,
hinted_cancel_index,
)
.get_payload_type()
== MarketDataTreeNodeType::RestingOrder as u8,
ManifestError::WrongIndexHintParams,
&format!("Invalid cancel hint index {}", hinted_cancel_index),
&format!("Invalid cancel hint index wrong type {}", hinted_cancel_index),
)?;
let order: &RestingOrder =
dynamic_account.get_order_by_index(hinted_cancel_index);
assert_with_msg(
trader_index == order.get_trader_index(),
ManifestError::WrongIndexHintParams,
&format!("Invalid cancel hint index {}", hinted_cancel_index),
&format!("Invalid cancel hint index wrong trader {}", hinted_cancel_index),
)?;
dynamic_account.cancel_order_by_index(
trader_index,
Expand Down Expand Up @@ -258,7 +265,8 @@ pub(crate) fn process_batch_update(
market: *market.key,
trader_index,
num_base_atoms: base_atoms,
price,
price_mantissa: place_order.price_mantissa,
price_exponent: place_order.price_exponent,
is_bid: place_order.is_bid(),
last_valid_slot,
order_type,
Expand Down
14 changes: 13 additions & 1 deletion programs/manifest/src/program/processor/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ pub(crate) fn process_swap(
} else {
QuoteAtomsPerBaseAtom::MAX
};
let (price_mantissa, price_exponent) = if is_base_in {
(1_u32, -20)
} else {
// This doesnt quite get to the max price, but close enough such that
// maker rounding on any ridiculous pricing will result in effectively
// infinite price.

// u128::MAX; = 340_282_366_920_938_463_463_374_607_431_768_211_455u128 ~= 3.4 * 10**35
// u32::MAX; = 4_294_967_295u32 ~= 4 * 10**9
(3_402_823_669, 6)
};
let last_valid_slot: u32 = NO_EXPIRATION_LAST_VALID_SLOT;
let order_type: OrderType = OrderType::ImmediateOrCancel;

Expand All @@ -126,7 +137,8 @@ pub(crate) fn process_swap(
market: *market.key,
trader_index,
num_base_atoms: base_atoms,
price,
price_mantissa,
price_exponent,
is_bid: !is_base_in,
last_valid_slot,
order_type,
Expand Down
117 changes: 79 additions & 38 deletions programs/manifest/src/quantities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use borsh::{BorshDeserialize as Deserialize, BorshSerialize as Serialize};
use bytemuck::{Pod, Zeroable};
use hypertree::trace;
use shank::ShankAccount;
use solana_program::{msg, program_error::ProgramError};
use solana_program::program_error::ProgramError;
use std::{
cmp::Ordering,
fmt::Display,
Expand Down Expand Up @@ -215,28 +215,34 @@ impl QuoteAtomsPerBaseAtom {
inner: [u64::MAX; 2],
};

pub fn to_effective_price(&self) -> EffectivePrice {
EffectivePrice { inner: self.inner }
}

pub fn try_from_mantissa_and_exponent(
mantissa: u32,
exponent: i8,
) -> Result<Self, PriceConversionError> {
if exponent > 10 {
msg!("invalid exponent {exponent} > 10 would truncate",);
return Err(PriceConversionError(0x0));
}
if exponent < -20 {
msg!("invalid exponent {exponent} < -20 would truncate",);
return Err(PriceConversionError(0x1));
}
) -> Result<Self, ProgramError> {
assert_with_msg(
exponent <= 10,
ManifestError::PriceConversionError,
"invalid exponent {exponent} > 10 would truncate",
)?;
assert_with_msg(
exponent >= -20,
ManifestError::PriceConversionError,
"invalid exponent {exponent} < -20 would truncate",
)?;
/* map exponent to array range
10 -> [0] -> D30
0 -> [10] -> D20
-10 -> [20] -> D10
-20 -> [30] -> D0
*/
let offset = -(exponent - 10) as usize;
let inner = DECIMAL_CONSTANTS[offset]
let offset: usize = -(exponent - 10) as usize;
let inner: u128 = DECIMAL_CONSTANTS[offset]
.checked_mul(mantissa as u128)
.ok_or(PriceConversionError(0x2))?;
.ok_or(ManifestError::PriceConversionError)?;
return Ok(QuoteAtomsPerBaseAtom {
inner: u128_to_u64_slice(inner),
});
Expand All @@ -250,9 +256,9 @@ impl QuoteAtomsPerBaseAtom {
if self == Self::ZERO {
return Ok(BaseAtoms::new(0));
}
let dividend = D20
let dividend: u128 = D20
.checked_mul(quote_atoms.inner as u128)
.ok_or(PriceConversionError(0x4))?;
.ok_or(ManifestError::PriceConversionError)?;
let inner: u128 = u64_slice_to_u128(self.inner);
let base_atoms = if round_up {
dividend.div_ceil(inner)
Expand All @@ -276,7 +282,7 @@ impl QuoteAtomsPerBaseAtom {
let inner: u128 = u64_slice_to_u128(self.inner);
let product: u128 = inner
.checked_mul(base_atoms.inner as u128)
.ok_or(PriceConversionError(0x8))?;
.ok_or(ManifestError::PriceConversionError)?;
let quote_atoms = if round_up {
product.div_ceil(D20)
} else {
Expand Down Expand Up @@ -312,7 +318,7 @@ impl QuoteAtomsPerBaseAtom {
let quote_matched_atoms = self.checked_quote_for_base_(num_base_atoms, !is_bid)?;
let quote_matched_d20 = quote_matched_atoms
.checked_mul(D20)
.ok_or(PriceConversionError(0x9))?;
.ok_or(ManifestError::PriceConversionError)?;
// no special case rounding needed because effective price is just a value used to compare for order
let inner = quote_matched_d20.div(num_base_atoms.inner as u128);
Ok(QuoteAtomsPerBaseAtom {
Expand Down Expand Up @@ -358,34 +364,69 @@ impl std::fmt::Debug for QuoteAtomsPerBaseAtom {
}
}

#[derive(Debug)]
pub struct PriceConversionError(u32);
// Sort key usage only. Does not implement math operations.
#[derive(Clone, Copy, Default, Zeroable, Pod, Deserialize, Serialize, ShankAccount)]
#[repr(C)]
pub struct EffectivePrice {
inner: [u64; 2],
}

impl Ord for EffectivePrice {
fn cmp(&self, other: &Self) -> Ordering {
(u64_slice_to_u128(self.inner)).cmp(&u64_slice_to_u128(other.inner))
}
}

impl PartialOrd for EffectivePrice {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl PartialEq for EffectivePrice {
fn eq(&self, other: &Self) -> bool {
(self.inner) == (other.inner)
}
}
impl Eq for EffectivePrice {}

const PRICE_CONVERSION_ERROR_BASE: u32 = 100;
impl std::fmt::Display for EffectivePrice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}",
&(u64_slice_to_u128(self.inner) as f64 / D20F)
))
}
}

impl From<PriceConversionError> for ProgramError {
fn from(value: PriceConversionError) -> Self {
ProgramError::Custom(value.0 + PRICE_CONVERSION_ERROR_BASE)
impl std::fmt::Debug for EffectivePrice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EffectivePrice")
.field("value", &(u64_slice_to_u128(self.inner) as f64 / D20F))
.finish()
}
}

impl TryFrom<f64> for QuoteAtomsPerBaseAtom {
type Error = PriceConversionError;
type Error = ProgramError;

fn try_from(value: f64) -> Result<Self, Self::Error> {
let mantissa = value * D20F;
if mantissa.is_infinite() {
msg!("infinite can not be expressed as fixed point decimal");
return Err(PriceConversionError(0xC));
}
if mantissa.is_nan() {
msg!("nan can not be expressed as fixed point decimal");
return Err(PriceConversionError(0xD));
}
if mantissa > u128::MAX as f64 {
msg!("price is too large");
return Err(PriceConversionError(0xE));
}
fn try_from(value: f64) -> Result<Self, ProgramError> {
let mantissa: f64 = value * D20F;
assert_with_msg(
!mantissa.is_infinite(),
ManifestError::PriceConversionError,
"infinite can not be expressed as fixed point decimal",
)?;
assert_with_msg(
!mantissa.is_nan(),
ManifestError::PriceConversionError,
"nan can not be expressed as fixed point decimal",
)?;
assert_with_msg(
mantissa < u128::MAX as f64,
ManifestError::PriceConversionError,
"price is too large",
)?;
Ok(QuoteAtomsPerBaseAtom {
inner: u128_to_u64_slice(mantissa as u128),
})
Expand Down
5 changes: 1 addition & 4 deletions programs/manifest/src/state/claimed_seat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ pub struct ClaimedSeat {
// When moving funds over to open orders, use the worst case rounding.
pub base_withdrawable_balance: BaseAtoms,
pub quote_withdrawable_balance: QuoteAtoms,
_padding: [u8; 16],
}
// 32 + // trader
// 8 + // base_balance
// 8 + // quote_balance
// 16 // padding
// = 64
// = 48
const_assert_eq!(size_of::<ClaimedSeat>(), CLAIMED_SEAT_SIZE);
const_assert_eq!(size_of::<ClaimedSeat>() % 8, 0);

Expand All @@ -32,7 +30,6 @@ impl ClaimedSeat {
trader,
base_withdrawable_balance: BaseAtoms::default(),
quote_withdrawable_balance: QuoteAtoms::default(),
_padding: [0; 16],
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion programs/manifest/src/state/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub const GLOBAL_FIXED_SIZE: usize = 88;
// Red black tree overhead is 16 bytes. If each block is 80 bytes, then we get
// 64 bytes for a RestingOrder or ClaimedSeat.
pub const GLOBAL_BLOCK_SIZE: usize = 64;
pub const MARKET_BLOCK_SIZE: usize = 80;
pub const MARKET_BLOCK_SIZE: usize = 64;
const MARKET_BLOCK_PAYLOAD_SIZE: usize = MARKET_BLOCK_SIZE - RBTREE_OVERHEAD_BYTES;
pub const RESTING_ORDER_SIZE: usize = MARKET_BLOCK_PAYLOAD_SIZE;
pub const CLAIMED_SEAT_SIZE: usize = MARKET_BLOCK_PAYLOAD_SIZE;
Expand Down
Loading
Loading