Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4f572d4
[ENG-87] Implement price library
xbtmatt Nov 26, 2025
df427bb
Fix cargo.toml formatting
xbtmatt Nov 26, 2025
56fc0f9
Fix checks, remove type assertions, check significand final result range
xbtmatt Nov 26, 2025
81f13bf
Add todo/wip comment
xbtmatt Nov 26, 2025
97b6f35
Add price changes with some stuff WIP
xbtmatt Nov 27, 2025
5b87f04
Finish price implementation with reorganized files
xbtmatt Dec 5, 2025
698041a
Add pinocchio to price deps so it's possible to use the `hint::` module
xbtmatt Dec 5, 2025
36aeed1
Don't have doctests run the tests in macro docs
xbtmatt Dec 5, 2025
0560ee4
Fix doctests instead of disabling them
xbtmatt Dec 6, 2025
cda4dbb
Fix constraints typo
xbtmatt Dec 6, 2025
a1dd93e
Use fallible decoded price conversions that factor in the possibility…
xbtmatt Dec 6, 2025
da88fb9
Updated mantissa range and add tests for it
xbtmatt Dec 6, 2025
955c273
Add checks to the unbiased macro range checks
xbtmatt Dec 6, 2025
7430b72
Make inner value of encoded price private
xbtmatt Dec 6, 2025
f3ef73f
Remove unnecessary qualification on bias in UNBIASED_* consts
xbtmatt Dec 6, 2025
18f49cf
Make exponential 10^n const values in macro easier to read/evaluate f…
xbtmatt Dec 6, 2025
ce55456
Add const assertions and unit tests for the potential rebias overflow…
xbtmatt Dec 6, 2025
2ffcb85
Add `get` documentation
xbtmatt Dec 6, 2025
bf7d13d
Fix underflow error variant name
xbtmatt Dec 6, 2025
8465b6b
Add documentation for `as_exponent_and_mantissa`
xbtmatt Dec 6, 2025
3b47ede
Add documentation for max biased exponent value and rename it and rem…
xbtmatt Dec 6, 2025
ebf2d85
Add a test that ensures the order info construction fails if the quot…
xbtmatt Dec 6, 2025
2a8b15d
Update bitshift comment
xbtmatt Dec 9, 2025
5abfbcd
Update decoded price documentation
xbtmatt Dec 9, 2025
20e04fe
Remove `new_unchecked`
xbtmatt Dec 9, 2025
c2a87d7
Make mantissa constant bounds public
xbtmatt Dec 9, 2025
749808d
Add documentation for `pow10_u64`
xbtmatt Dec 9, 2025
534d218
Fix documentation being below attributes
xbtmatt Dec 9, 2025
fe7118f
Add `OrderInfo` documentation
xbtmatt Dec 9, 2025
5261c36
Update const assertion to use `<=`, update documentation to reflect t…
xbtmatt Dec 9, 2025
9a5680a
Update macro documentation to pass cargo test and be more descriptive
xbtmatt Dec 9, 2025
2df1084
Add test for price mantissa * base scalar results in arithmetic overf…
xbtmatt Dec 9, 2025
118c257
Add explicit exponent underflow unit test
xbtmatt Dec 9, 2025
d6c5f22
Use `matches` with the exact error type for `invalid_mantissas` unit …
xbtmatt Dec 9, 2025
6df2336
Fix comment variable names
xbtmatt Dec 9, 2025
5e881c4
Fix `price_bits` var naming, update to `mantissa_bits`
xbtmatt Dec 9, 2025
fa3308f
Clarify documentation on the exponent range; make formula for `UNBIAS…
xbtmatt Dec 9, 2025
756077b
Make safety expectation on unchecked add with unit test condition che…
xbtmatt Dec 10, 2025
24fcbad
Add `base_exponent_too_large` explicit unit test for `to_order_info`
xbtmatt Dec 10, 2025
8ec535d
Derive `Copy` for validated mantissa
xbtmatt Dec 10, 2025
44dd318
Fix `pow10_u64` macro formatting
xbtmatt Dec 10, 2025
3f1e5cc
Make `EncodedPrice` repr transparent
xbtmatt Dec 11, 2025
35f5b29
Remove trailing whitespace in macros.rs
xbtmatt Dec 11, 2025
5a9db21
Remove trailing whitespace in other files
xbtmatt Dec 11, 2025
c9eae72
Fix `exponent` typo
xbtmatt Dec 12, 2025
b49a7bc
Add unit test for overflowing quote atoms
xbtmatt Dec 12, 2025
1c059d1
Fix unclosed code blocks
xbtmatt Dec 12, 2025
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
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"instruction-macros/crates/test-fixtures",
"interface",
"grpc-stream",
"price",
"program",
"transaction-parser",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use crate::parse::{
/// // Use it to implement `TryFrom<u8>`:
/// impl TryFrom<u8> for MyInstruction {
/// type Error = ProgramError;
///
///
/// #[inline(always)]
/// fn try_from(tag: u8) -> Result<Self, Self::Error> {
/// MyInstruction_try_from_tag!(tag, ProgramError::InvalidInstructionData)
Expand Down
6 changes: 3 additions & 3 deletions instruction-macros/crates/test-fixtures/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod test {
#[account(9, name = "quote_token_program", desc = "The quote mint's token program.")]
#[args(sector_index_hint: u32, "A hint indicating which sector the user's seat resides in.")]
CloseSeat,

#[account(0, signer, name = "user", desc = "The user depositing or registering their seat.")]
#[account(1, writable, name = "market_account", desc = "The market account PDA.")]
#[account(2, writable, name = "user_ata", desc = "The user's associated token account.")]
Expand All @@ -29,10 +29,10 @@ mod test {
#[args(amount: u64, "The amount to deposit.")]
#[args(sector_index_hint: u32, "A hint indicating which sector the user's seat resides in (pass `NIL` when registering a new seat).")]
Deposit,

#[account(0, signer, name = "event_authority", desc = "Flush events.")]
FlushEvents,

Batch,
}
}
2 changes: 1 addition & 1 deletion interface/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum DropsetEventTag {
#[args(seat_sector_index: u32, "The user's (possibly newly registered) market seat sector index.")]
DepositEvent,
#[args(amount: u64, "The amount withdrawn.")]
#[args(is_base: bool, "Which token, i.e., `true` => base token, `false` => quote token.")]
#[args(is_base: bool, "Which token, i.e., `true` => base token, `false` => quote token.")]
WithdrawEvent,
#[args(market: [u8; 32], "The newly registered market.")]
RegisterMarketEvent,
Expand Down
15 changes: 15 additions & 0 deletions price/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "price"
version.workspace = true
edition.workspace = true

[dependencies]
static_assertions.workspace = true
pinocchio.workspace = true

[dev-dependencies]
strum.workspace = true
strum_macros.workspace = true

[lints]
workspace = true
79 changes: 79 additions & 0 deletions price/src/decoded_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::{
EncodedPrice,
OrderInfoError,
ValidatedPriceMantissa,
BIAS,
ENCODED_PRICE_INFINITY,
ENCODED_PRICE_ZERO,
PRICE_MANTISSA_BITS,
PRICE_MANTISSA_MASK,
};

/// An enum representing a decoded `EncodedPrice`.
#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum DecodedPrice {
Zero,
Infinity,
ExponentAndMantissa {
price_exponent_biased: u8,
price_mantissa: ValidatedPriceMantissa,
},
}

impl DecodedPrice {
/// Return the optional tuple of exponent and mantissa from a decoded price.
/// If the decoded price is not a [`DecodedPrice::ExponentAndMantissa`], this returns `None`.
pub fn as_exponent_and_mantissa(&self) -> Option<(&u8, &ValidatedPriceMantissa)> {
if let DecodedPrice::ExponentAndMantissa {
price_exponent_biased,
price_mantissa,
} = self
{
Some((price_exponent_biased, price_mantissa))
} else {
None
}
}
}

impl TryFrom<EncodedPrice> for DecodedPrice {
type Error = OrderInfoError;

fn try_from(encoded: EncodedPrice) -> Result<Self, Self::Error> {
let res = match encoded.get() {
ENCODED_PRICE_ZERO => Self::Zero,
ENCODED_PRICE_INFINITY => Self::Infinity,
value => {
let price_exponent_biased = (value >> PRICE_MANTISSA_BITS) as u8;
let validated_mantissa = value & PRICE_MANTISSA_MASK;

Self::ExponentAndMantissa {
price_exponent_biased,
price_mantissa: ValidatedPriceMantissa::try_from(validated_mantissa)?,
}
}
};

Ok(res)
}
}

impl TryFrom<DecodedPrice> for f64 {
type Error = OrderInfoError;

fn try_from(decoded: DecodedPrice) -> Result<Self, Self::Error> {
match decoded {
DecodedPrice::Zero => Ok(0f64),
DecodedPrice::Infinity => Err(OrderInfoError::InfinityIsNotAFloat),
DecodedPrice::ExponentAndMantissa {
price_exponent_biased,
price_mantissa,
} => {
let res = (price_mantissa.get() as f64)
* 10f64.powi(price_exponent_biased as i32 - BIAS as i32);
Ok(res)
}
}
}
}
102 changes: 102 additions & 0 deletions price/src/encoded_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::{
ValidatedPriceMantissa,
PRICE_MANTISSA_BITS,
};

/// The encoded price as a u32.
///
/// If `N` = the number of exponent bits and `M` = the number of price mantissa bits, the u32 bit
/// layout is:
///
/// ```text
/// N M
/// |-------------------|-------------------|
/// [ exponent_bits ] | [ mantissa_bits ]
/// |---------------------------------------|
/// 32
/// ```
#[repr(transparent)]
#[derive(Copy, Clone, Debug)]
pub struct EncodedPrice(u32);

pub const ENCODED_PRICE_INFINITY: u32 = u32::MAX;
pub const ENCODED_PRICE_ZERO: u32 = 0;

impl EncodedPrice {
/// Creates a new [`EncodedPrice`] from a biased price exponent and a validated price mantissa.
#[inline(always)]
pub fn new(price_exponent_biased: u8, price_mantissa: ValidatedPriceMantissa) -> Self {
// The biased price exponent doesn't need to be checked because a leftwards bitshift will
// always discard irrelevant bits.
let exponent_bits = (price_exponent_biased as u32) << PRICE_MANTISSA_BITS;

// No need to mask the price mantissa since it has already been range checked/validated.
// Thus it's guaranteed it will only occupy the lower M bits where M = PRICE_MANTISSA_BITS.
Self(exponent_bits | price_mantissa.get())
}

/// Returns the inner encoded price as a u32.
#[inline(always)]
pub fn get(&self) -> u32 {
self.0
}

/// The encoded price representation of a market buy/taker order with no constraints on the
/// maximum filled ask price.
#[inline(always)]
pub const fn infinity() -> Self {
Self(ENCODED_PRICE_INFINITY)
}

#[inline(always)]
pub fn is_infinity(&self) -> bool {
self.0 == ENCODED_PRICE_INFINITY
}

/// The encoded price representation of a market sell/taker order with no constraints on the
/// minimum filled bid price.
#[inline(always)]
pub const fn zero() -> Self {
Self(ENCODED_PRICE_ZERO)
}

#[inline(always)]
pub fn is_zero(&self) -> bool {
self.0 == ENCODED_PRICE_ZERO
}
}

#[cfg(test)]
mod tests {
use crate::{
to_biased_exponent,
EncodedPrice,
ValidatedPriceMantissa,
BIAS,
PRICE_MANTISSA_BITS,
PRICE_MANTISSA_MASK,
};

#[test]
fn encoded_price_mantissa_bits() {
let exponent = 0b0_1111;
let price_mantissa = 0b000_1111_0000_1111_0000_1111_0000;
let encoded_price = EncodedPrice::new(
to_biased_exponent!(exponent),
ValidatedPriceMantissa::try_from(price_mantissa).unwrap(),
);
assert_eq!(
encoded_price.0 >> PRICE_MANTISSA_BITS,
(exponent + BIAS) as u32
);
assert_eq!(encoded_price.0 & PRICE_MANTISSA_MASK, price_mantissa);
}

#[test]
fn test_infinity() {
assert_eq!(EncodedPrice::infinity().0, u32::MAX);
assert_eq!(EncodedPrice::zero().0, 0);
assert!(EncodedPrice::infinity().is_infinity());
assert!(EncodedPrice::zero().is_zero());
}
}
10 changes: 10 additions & 0 deletions price/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[repr(u8)]
#[derive(Debug)]
#[cfg_attr(test, derive(strum_macros::Display))]
pub enum OrderInfoError {
ExponentUnderflow,
ArithmeticOverflow,
InvalidPriceMantissa,
InvalidBiasedExponent,
InfinityIsNotAFloat,
}
Loading