From 2cd536692637d9dd72e146d32641b33761669eb6 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 11 Aug 2025 16:14:20 -0700 Subject: [PATCH 1/2] feat: refactor pyth lazer protocol --- Cargo.lock | 6 +- lazer/publisher_sdk/rust/Cargo.toml | 2 +- lazer/sdk/rust/client/Cargo.toml | 2 +- lazer/sdk/rust/protocol/Cargo.toml | 2 +- lazer/sdk/rust/protocol/src/price.rs | 81 ++++++++++++---------- lazer/sdk/rust/protocol/src/price/tests.rs | 9 ++- lazer/sdk/rust/protocol/src/rate.rs | 20 +++--- 7 files changed, 66 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 039bc23e58..98de197f47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5688,7 +5688,7 @@ dependencies = [ "futures-util", "hex", "libsecp256k1 0.7.2", - "pyth-lazer-protocol 0.11.0", + "pyth-lazer-protocol 0.12.0", "serde", "serde_json", "tokio", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.11.0" +version = "0.12.0" dependencies = [ "alloy-primitives 0.8.25", "anyhow", @@ -5767,7 +5767,7 @@ dependencies = [ "fs-err", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.11.0", + "pyth-lazer-protocol 0.12.0", "serde_json", ] diff --git a/lazer/publisher_sdk/rust/Cargo.toml b/lazer/publisher_sdk/rust/Cargo.toml index be22223403..6695cbece3 100644 --- a/lazer/publisher_sdk/rust/Cargo.toml +++ b/lazer/publisher_sdk/rust/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" repository = "https://github.com/pyth-network/pyth-crosschain" [dependencies] -pyth-lazer-protocol = { version = "0.11.0", path = "../../sdk/rust/protocol" } +pyth-lazer-protocol = { version = "0.12.0", path = "../../sdk/rust/protocol" } anyhow = "1.0.98" protobuf = "3.7.2" serde_json = "1.0.140" diff --git a/lazer/sdk/rust/client/Cargo.toml b/lazer/sdk/rust/client/Cargo.toml index 44d595e0c5..bcb795704f 100644 --- a/lazer/sdk/rust/client/Cargo.toml +++ b/lazer/sdk/rust/client/Cargo.toml @@ -6,7 +6,7 @@ description = "A Rust client for Pyth Lazer" license = "Apache-2.0" [dependencies] -pyth-lazer-protocol = { path = "../protocol", version = "0.11.0" } +pyth-lazer-protocol = { path = "../protocol", version = "0.12.0" } tokio = { version = "1", features = ["full"] } tokio-tungstenite = { version = "0.20", features = ["native-tls"] } futures-util = "0.3" diff --git a/lazer/sdk/rust/protocol/Cargo.toml b/lazer/sdk/rust/protocol/Cargo.toml index e058344eea..54793bc69c 100644 --- a/lazer/sdk/rust/protocol/Cargo.toml +++ b/lazer/sdk/rust/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-protocol" -version = "0.11.0" +version = "0.12.0" edition = "2021" description = "Pyth Lazer SDK - protocol types." license = "Apache-2.0" diff --git a/lazer/sdk/rust/protocol/src/price.rs b/lazer/sdk/rust/protocol/src/price.rs index cbc5a8200e..9bd706dc90 100644 --- a/lazer/sdk/rust/protocol/src/price.rs +++ b/lazer/sdk/rust/protocol/src/price.rs @@ -27,17 +27,17 @@ pub struct Price(NonZeroI64); impl Price { pub fn from_integer(value: i64, exponent: i16) -> Result { - let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?, ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?, }; - let value = NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?; - Ok(Self(value)) + let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?; + Ok(Self(mantissa)) } pub fn parse_str(value: &str, exponent: i16) -> Result { let value: Decimal = value.parse()?; - let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { ExponentFactor::Mul(coef) => value .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?) .ok_or(PriceError::Overflow)?, @@ -45,12 +45,12 @@ impl Price { .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?) .ok_or(PriceError::Overflow)?, }; - if !value.is_integer() { + if !mantissa.is_integer() { return Err(PriceError::TooPrecise); } - let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?; - let value = NonZeroI64::new(value).ok_or(PriceError::Overflow)?; - Ok(Self(value)) + let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?; + let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::Overflow)?; + Ok(Self(mantissa)) } pub const fn from_nonzero_mantissa(mantissa: NonZeroI64) -> Self { @@ -58,8 +58,8 @@ impl Price { } pub const fn from_mantissa(mantissa: i64) -> Result { - if let Some(value) = NonZeroI64::new(mantissa) { - Ok(Self(value)) + if let Some(mantissa) = NonZeroI64::new(mantissa) { + Ok(Self(mantissa)) } else { Err(PriceError::ZeroPriceUnsupported) } @@ -75,7 +75,7 @@ impl Price { pub fn to_f64(self, exponent: i16) -> Result { match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { - // Mul/div is reversed for this conversion + // Mul/div is reversed for converting mantissa to value ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64), ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64), } @@ -83,7 +83,7 @@ impl Price { pub fn from_f64(value: f64, exponent: i16) -> Result { let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?; - let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { ExponentFactor::Mul(coef) => value .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?) .ok_or(PriceError::Overflow)?, @@ -91,65 +91,70 @@ impl Price { .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?) .ok_or(PriceError::Overflow)?, }; - let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?; + let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?; Ok(Self( - NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?, + NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?, )) } - pub fn add_with_same_mantissa(self, other: Price) -> Result { - let value = self + pub fn add_with_same_exponent(self, other: Price) -> Result { + let mantissa = self .0 .get() .checked_add(other.0.get()) .ok_or(PriceError::Overflow)?; - Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported) + Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported) } - pub fn sub_with_same_mantissa(self, other: Price) -> Result { - let value = self + pub fn sub_with_same_exponent(self, other: Price) -> Result { + let mantissa = self .0 .get() .checked_sub(other.0.get()) .ok_or(PriceError::Overflow)?; - Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported) + Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported) } pub fn mul_integer(self, factor: i64) -> Result { - let value = self + let mantissa = self .0 .get() .checked_mul(factor) .ok_or(PriceError::Overflow)?; - Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported) + Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported) } pub fn div_integer(self, factor: i64) -> Result { - let value = self + let mantissa = self .0 .get() .checked_div(factor) .ok_or(PriceError::Overflow)?; - Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported) + Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported) } - pub fn mul_decimal(self, mantissa: i64, rhs_exponent: i16) -> Result { - let left_value = i128::from(self.0.get()); - let right_value = i128::from(mantissa); + pub fn mul_decimal(self, mantissa: i64, exponent: i16) -> Result { + let left_mantissa = i128::from(self.0.get()); + let right_mantissa = i128::from(mantissa); - let value = left_value - .checked_mul(right_value) + // multiplied_mantissas = left_mantissa * right_mantissa + let multiplied_mantissas = left_mantissa + .checked_mul(right_mantissa) .ok_or(PriceError::Overflow)?; - let value = match ExponentFactor::get(rhs_exponent).ok_or(PriceError::Overflow)? { - ExponentFactor::Mul(coef) => { - value.checked_div(coef.into()).ok_or(PriceError::Overflow)? - } - ExponentFactor::Div(coef) => { - value.checked_mul(coef.into()).ok_or(PriceError::Overflow)? - } + // result_mantissa = left_mantissa * right_mantissa * 10^exponent + // Mul/div is reversed for multiplying 10^exponent + let result_mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? { + ExponentFactor::Mul(coef) => multiplied_mantissas + .checked_div(coef.into()) + .ok_or(PriceError::Overflow)?, + ExponentFactor::Div(coef) => multiplied_mantissas + .checked_mul(coef.into()) + .ok_or(PriceError::Overflow)?, }; - let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?; - Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported) + let result_mantissa: i64 = result_mantissa + .try_into() + .map_err(|_| PriceError::Overflow)?; + Self::from_mantissa(result_mantissa).map_err(|_| PriceError::ZeroPriceUnsupported) } } diff --git a/lazer/sdk/rust/protocol/src/price/tests.rs b/lazer/sdk/rust/protocol/src/price/tests.rs index 30105e2be6..c02fa7bc5c 100644 --- a/lazer/sdk/rust/protocol/src/price/tests.rs +++ b/lazer/sdk/rust/protocol/src/price/tests.rs @@ -105,7 +105,7 @@ fn price_ops() { let price2 = Price::parse_str("23.45", -8).unwrap(); assert_float_absolute_eq!( price1 - .add_with_same_mantissa(price2) + .add_with_same_exponent(price2) .unwrap() .to_f64(-8) .unwrap(), @@ -113,7 +113,7 @@ fn price_ops() { ); assert_float_absolute_eq!( price1 - .sub_with_same_mantissa(price2) + .sub_with_same_exponent(price2) .unwrap() .to_f64(-8) .unwrap(), @@ -138,6 +138,11 @@ fn price_ops() { 12.34 * 3400.0 ); + assert_eq!( + price1.mul_decimal(34, 2).unwrap().mantissa_i64(), + 1234000000 * 3400 + ); + assert_float_absolute_eq!( price1.mul_decimal(34, 0).unwrap().to_f64(-8).unwrap(), 12.34 * 34.0 diff --git a/lazer/sdk/rust/protocol/src/rate.rs b/lazer/sdk/rust/protocol/src/rate.rs index 43af27a1c7..7c76de26b4 100644 --- a/lazer/sdk/rust/protocol/src/rate.rs +++ b/lazer/sdk/rust/protocol/src/rate.rs @@ -24,16 +24,16 @@ pub struct Rate(i64); impl Rate { pub fn from_integer(value: i64, exponent: i16) -> Result { - let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(RateError::Overflow)?, ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(RateError::Overflow)?, }; - Ok(Self(value)) + Ok(Self(mantissa)) } pub fn parse_str(value: &str, exponent: i16) -> Result { let value: Decimal = value.parse()?; - let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { ExponentFactor::Mul(coef) => value .checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?) .ok_or(RateError::Overflow)?, @@ -41,11 +41,11 @@ impl Rate { .checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?) .ok_or(RateError::Overflow)?, }; - if !value.is_integer() { + if !mantissa.is_integer() { return Err(RateError::TooPrecise); } - let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?; - Ok(Self(value)) + let mantissa: i64 = mantissa.try_into().map_err(|_| RateError::Overflow)?; + Ok(Self(mantissa)) } pub const fn from_mantissa(mantissa: i64) -> Self { @@ -54,7 +54,7 @@ impl Rate { pub fn from_f64(value: f64, exponent: i16) -> Result { let value = Decimal::from_f64(value).ok_or(RateError::Overflow)?; - let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { + let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { ExponentFactor::Mul(coef) => value .checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?) .ok_or(RateError::Overflow)?, @@ -62,8 +62,8 @@ impl Rate { .checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?) .ok_or(RateError::Overflow)?, }; - let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?; - Ok(Self(value)) + let mantissa: i64 = mantissa.try_into().map_err(|_| RateError::Overflow)?; + Ok(Self(mantissa)) } pub fn mantissa(self) -> i64 { @@ -72,7 +72,7 @@ impl Rate { pub fn to_f64(self, exponent: i16) -> Result { match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? { - // Mul/div is reversed for this conversion + // Mul/div is reversed for converting mantissa to value ExponentFactor::Mul(coef) => Ok(self.0 as f64 / coef as f64), ExponentFactor::Div(coef) => Ok(self.0 as f64 * coef as f64), } From 09bcd40271b2b93817db79e5905e83f14d551f5d Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 12 Aug 2025 07:24:57 -0700 Subject: [PATCH 2/2] fix contract --- lazer/contracts/solana/Cargo.lock | 5 +++-- .../solana/programs/pyth-lazer-solana-contract/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lazer/contracts/solana/Cargo.lock b/lazer/contracts/solana/Cargo.lock index 89aa6a2162..3f94915ab8 100644 --- a/lazer/contracts/solana/Cargo.lock +++ b/lazer/contracts/solana/Cargo.lock @@ -3293,7 +3293,7 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.10.2" +version = "0.12.0" dependencies = [ "anyhow", "byteorder", @@ -3307,11 +3307,12 @@ dependencies = [ "rust_decimal", "serde", "serde_json", + "thiserror 2.0.12", ] [[package]] name = "pyth-lazer-solana-contract" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anchor-lang", "bytemuck", diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index cb2de74e6d..397b0d15d6 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -19,7 +19,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.11.0" } +pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.12.0" } anchor-lang = "0.31.1" bytemuck = { version = "1.20.0", features = ["derive"] }