Skip to content

Commit c11d8ab

Browse files
committed
fix(lazer): use non-inverted exponent values in Price and Rate
1 parent 5fb92aa commit c11d8ab

File tree

7 files changed

+139
-91
lines changed

7 files changed

+139
-91
lines changed

lazer/sdk/rust/protocol/src/lib.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub mod message;
1313
/// Types describing Lazer's message payload.
1414
pub mod payload;
1515
mod price;
16-
/// Legacy publisher API.
16+
/// Legacy Websocket API for publishers.
1717
pub mod publisher;
1818
mod rate;
1919
mod serde_price_as_i64;
@@ -69,6 +69,23 @@ pub enum PriceFeedProperty {
6969
// More fields may be added later.
7070
}
7171

72+
enum ExponentFactor {
73+
Mul(i64),
74+
Div(i64),
75+
}
76+
77+
impl ExponentFactor {
78+
fn get(exponent: i16) -> Option<Self> {
79+
if exponent >= 0 {
80+
let exponent: u32 = exponent.try_into().ok()?;
81+
Some(ExponentFactor::Div(10_i64.checked_pow(exponent)?))
82+
} else {
83+
let minus_exponent: u32 = exponent.checked_neg()?.try_into().ok()?;
84+
Some(ExponentFactor::Mul(10_i64.checked_pow(minus_exponent)?))
85+
}
86+
}
87+
}
88+
7289
#[test]
7390
fn magics_in_big_endian() {
7491
use crate::{

lazer/sdk/rust/protocol/src/payload.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//! Types representing binary encoding of signable payloads and signature envelopes.
2-
31
use crate::{
42
price::Price,
53
rate::Rate,

lazer/sdk/rust/protocol/src/price.rs

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod tests;
33

44
use {
5+
crate::ExponentFactor,
56
rust_decimal::{prelude::FromPrimitive, Decimal},
67
serde::{Deserialize, Serialize},
78
std::num::NonZeroI64,
@@ -25,18 +26,25 @@ pub enum PriceError {
2526
pub struct Price(NonZeroI64);
2627

2728
impl Price {
28-
pub fn from_integer(value: i64, exponent: u32) -> Result<Price, PriceError> {
29-
let coef = 10i64.checked_pow(exponent).ok_or(PriceError::Overflow)?;
30-
let value = value.checked_mul(coef).ok_or(PriceError::Overflow)?;
29+
pub fn from_integer(value: i64, exponent: i16) -> Result<Price, PriceError> {
30+
let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
31+
ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?,
32+
ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?,
33+
};
3134
let value = NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?;
3235
Ok(Self(value))
3336
}
3437

35-
pub fn parse_str(value: &str, exponent: u32) -> Result<Price, PriceError> {
38+
pub fn parse_str(value: &str, exponent: i16) -> Result<Price, PriceError> {
3639
let value: Decimal = value.parse()?;
37-
let coef = 10i64.checked_pow(exponent).ok_or(PriceError::Overflow)?;
38-
let coef = Decimal::from_i64(coef).ok_or(PriceError::Overflow)?;
39-
let value = value.checked_mul(coef).ok_or(PriceError::Overflow)?;
40+
let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
41+
ExponentFactor::Mul(coef) => value
42+
.checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
43+
.ok_or(PriceError::Overflow)?,
44+
ExponentFactor::Div(coef) => value
45+
.checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
46+
.ok_or(PriceError::Overflow)?,
47+
};
4048
if !value.is_integer() {
4149
return Err(PriceError::TooPrecise);
4250
}
@@ -65,15 +73,24 @@ impl Price {
6573
self.0.get()
6674
}
6775

68-
pub fn to_f64(self, exponent: u32) -> Result<f64, PriceError> {
69-
Ok(self.0.get() as f64 / 10i64.checked_pow(exponent).ok_or(PriceError::Overflow)? as f64)
76+
pub fn to_f64(self, exponent: i16) -> Result<f64, PriceError> {
77+
match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
78+
// Mul/div is reversed for this conversion
79+
ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64),
80+
ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64),
81+
}
7082
}
7183

72-
pub fn from_f64(value: f64, exponent: u32) -> Result<Self, PriceError> {
84+
pub fn from_f64(value: f64, exponent: i16) -> Result<Self, PriceError> {
7385
let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?;
74-
let coef = 10i64.checked_pow(exponent).ok_or(PriceError::Overflow)?;
75-
let coef = Decimal::from_i64(coef).ok_or(PriceError::Overflow)?;
76-
let value = value.checked_mul(coef).ok_or(PriceError::Overflow)?;
86+
let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
87+
ExponentFactor::Mul(coef) => value
88+
.checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
89+
.ok_or(PriceError::Overflow)?,
90+
ExponentFactor::Div(coef) => value
91+
.checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
92+
.ok_or(PriceError::Overflow)?,
93+
};
7794
let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?;
7895
Ok(Self(
7996
NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?,
@@ -116,19 +133,22 @@ impl Price {
116133
Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
117134
}
118135

119-
pub fn mul_decimal(self, mantissa: i64, rhs_exponent: u32) -> Result<Self, PriceError> {
136+
pub fn mul_decimal(self, mantissa: i64, rhs_exponent: i16) -> Result<Self, PriceError> {
120137
let left_value = i128::from(self.0.get());
121138
let right_value = i128::from(mantissa);
122139

123140
let value = left_value
124141
.checked_mul(right_value)
125-
.ok_or(PriceError::Overflow)?
126-
.checked_div(
127-
10i128
128-
.checked_pow(rhs_exponent)
129-
.ok_or(PriceError::Overflow)?,
130-
)
131142
.ok_or(PriceError::Overflow)?;
143+
144+
let value = match ExponentFactor::get(rhs_exponent).ok_or(PriceError::Overflow)? {
145+
ExponentFactor::Mul(coef) => {
146+
value.checked_div(coef.into()).ok_or(PriceError::Overflow)?
147+
}
148+
ExponentFactor::Div(coef) => {
149+
value.checked_mul(coef.into()).ok_or(PriceError::Overflow)?
150+
}
151+
};
132152
let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?;
133153
Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
134154
}

lazer/sdk/rust/protocol/src/price/tests.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,40 @@ use {super::Price, assert_float_eq::assert_float_absolute_eq};
22

33
#[test]
44
fn price_constructs() {
5-
let price = Price::parse_str("42.68", 8).unwrap();
5+
let price = Price::parse_str("42.68", -8).unwrap();
66
assert_eq!(price.0.get(), 4_268_000_000);
7-
assert_float_absolute_eq!(price.to_f64(8).unwrap(), 42.68);
7+
assert_float_absolute_eq!(price.to_f64(-8).unwrap(), 42.68);
88

9-
let price2 = Price::from_integer(2, 8).unwrap();
9+
let price2 = Price::from_integer(2, -8).unwrap();
1010
assert_eq!(price2.0.get(), 200_000_000);
11-
assert_float_absolute_eq!(price2.to_f64(8).unwrap(), 2.);
11+
assert_float_absolute_eq!(price2.to_f64(-8).unwrap(), 2.);
1212

1313
let price3 = Price::from_mantissa(123_456).unwrap();
1414
assert_eq!(price3.0.get(), 123_456);
15-
assert_float_absolute_eq!(price3.to_f64(8).unwrap(), 0.001_234_56);
15+
assert_float_absolute_eq!(price3.to_f64(-8).unwrap(), 0.001_234_56);
1616

17-
let price4 = Price::from_f64(42.68, 8).unwrap();
17+
let price4 = Price::from_f64(42.68, -8).unwrap();
1818
assert_eq!(price4.0.get(), 4_268_000_000);
19-
assert_float_absolute_eq!(price4.to_f64(8).unwrap(), 42.68);
19+
assert_float_absolute_eq!(price4.to_f64(-8).unwrap(), 42.68);
2020
}
2121

2222
#[test]
2323
fn price_constructs_negative_mantissa() {
24-
let price = Price::parse_str("-42.68", 8).unwrap();
24+
let price = Price::parse_str("-42.68", -8).unwrap();
2525
assert_eq!(price.0.get(), -4_268_000_000);
26-
assert_float_absolute_eq!(price.to_f64(8).unwrap(), -42.68);
26+
assert_float_absolute_eq!(price.to_f64(-8).unwrap(), -42.68);
2727

28-
let price2 = Price::from_integer(-2, 8).unwrap();
28+
let price2 = Price::from_integer(-2, -8).unwrap();
2929
assert_eq!(price2.0.get(), -200_000_000);
30-
assert_float_absolute_eq!(price2.to_f64(8).unwrap(), -2.);
30+
assert_float_absolute_eq!(price2.to_f64(-8).unwrap(), -2.);
3131

3232
let price3 = Price::from_mantissa(-123_456).unwrap();
3333
assert_eq!(price3.0.get(), -123_456);
34-
assert_float_absolute_eq!(price3.to_f64(8).unwrap(), -0.001_234_56);
34+
assert_float_absolute_eq!(price3.to_f64(-8).unwrap(), -0.001_234_56);
3535

36-
let price4 = Price::from_f64(-42.68, 8).unwrap();
36+
let price4 = Price::from_f64(-42.68, -8).unwrap();
3737
assert_eq!(price4.0.get(), -4_268_000_000);
38-
assert_float_absolute_eq!(price4.to_f64(8).unwrap(), -42.68);
38+
assert_float_absolute_eq!(price4.to_f64(-8).unwrap(), -42.68);
3939
}
4040

4141
#[test]
@@ -59,42 +59,42 @@ fn price_constructs_zero_exponent() {
5959

6060
#[test]
6161
fn price_rejects_zero_mantissa() {
62-
Price::parse_str("0.0", 8).unwrap_err();
63-
Price::from_integer(0, 8).unwrap_err();
62+
Price::parse_str("0.0", -8).unwrap_err();
63+
Price::from_integer(0, -8).unwrap_err();
6464
Price::from_mantissa(0).unwrap_err();
65-
Price::from_f64(-0.0, 8).unwrap_err();
65+
Price::from_f64(-0.0, -8).unwrap_err();
6666
}
6767

6868
#[test]
6969
fn price_rejects_too_precise() {
7070
Price::parse_str("42.68", 0).unwrap_err();
71-
Price::parse_str("42.68", 1).unwrap_err();
72-
Price::parse_str("42.68", 2).unwrap();
71+
Price::parse_str("42.68", -1).unwrap_err();
72+
Price::parse_str("42.68", -2).unwrap();
7373
}
7474

7575
#[test]
7676
fn price_ops() {
77-
let price1 = Price::parse_str("12.34", 8).unwrap();
78-
let price2 = Price::parse_str("23.45", 8).unwrap();
77+
let price1 = Price::parse_str("12.34", -8).unwrap();
78+
let price2 = Price::parse_str("23.45", -8).unwrap();
7979
assert_float_absolute_eq!(
8080
price1
8181
.add_with_same_mantissa(price2)
8282
.unwrap()
83-
.to_f64(8)
83+
.to_f64(-8)
8484
.unwrap(),
8585
12.34 + 23.45
8686
);
8787
assert_float_absolute_eq!(
88-
price1.mul_integer(2).unwrap().to_f64(8).unwrap(),
88+
price1.mul_integer(2).unwrap().to_f64(-8).unwrap(),
8989
12.34 * 2.
9090
);
9191
assert_float_absolute_eq!(
92-
price1.div_integer(2).unwrap().to_f64(8).unwrap(),
92+
price1.div_integer(2).unwrap().to_f64(-8).unwrap(),
9393
12.34 / 2.
9494
);
9595

9696
assert_float_absolute_eq!(
97-
price1.mul_decimal(3456, 2).unwrap().to_f64(8).unwrap(),
97+
price1.mul_decimal(3456, -2).unwrap().to_f64(-8).unwrap(),
9898
12.34 * 34.56
9999
);
100100
}

lazer/sdk/rust/protocol/src/publisher.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//! WebSocket JSON protocol types for API the publisher provides to the router.
2-
//! Publisher data sourcing may also be implemented in the router process,
3-
//! eliminating WebSocket overhead.
4-
51
use {
62
crate::{price::Price, rate::Rate, time::TimestampUs, PriceFeedId},
73
derive_more::derive::From,

lazer/sdk/rust/protocol/src/rate.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod tests;
33

44
use {
5+
crate::ExponentFactor,
56
rust_decimal::{prelude::FromPrimitive, Decimal},
67
serde::{Deserialize, Serialize},
78
thiserror::Error,
@@ -22,42 +23,58 @@ pub enum RateError {
2223
pub struct Rate(i64);
2324

2425
impl Rate {
25-
pub fn parse_str(value: &str, exponent: u32) -> Result<Self, RateError> {
26+
pub fn from_integer(value: i64, exponent: i16) -> Result<Self, RateError> {
27+
let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
28+
ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(RateError::Overflow)?,
29+
ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(RateError::Overflow)?,
30+
};
31+
Ok(Self(value))
32+
}
33+
34+
pub fn parse_str(value: &str, exponent: i16) -> Result<Self, RateError> {
2635
let value: Decimal = value.parse()?;
27-
let coef = 10i64.checked_pow(exponent).ok_or(RateError::Overflow)?;
28-
let coef = Decimal::from_i64(coef).ok_or(RateError::Overflow)?;
29-
let value = value.checked_mul(coef).ok_or(RateError::Overflow)?;
36+
let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
37+
ExponentFactor::Mul(coef) => value
38+
.checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
39+
.ok_or(RateError::Overflow)?,
40+
ExponentFactor::Div(coef) => value
41+
.checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
42+
.ok_or(RateError::Overflow)?,
43+
};
3044
if !value.is_integer() {
3145
return Err(RateError::TooPrecise);
3246
}
3347
let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?;
3448
Ok(Self(value))
3549
}
3650

37-
pub fn from_f64(value: f64, exponent: u32) -> Result<Self, RateError> {
38-
let value = Decimal::from_f64(value).ok_or(RateError::Overflow)?;
39-
let coef = 10i64.checked_pow(exponent).ok_or(RateError::Overflow)?;
40-
let coef = Decimal::from_i64(coef).ok_or(RateError::Overflow)?;
41-
let value = value.checked_mul(coef).ok_or(RateError::Overflow)?;
42-
let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?;
43-
Ok(Self(value))
51+
pub const fn from_mantissa(mantissa: i64) -> Self {
52+
Self(mantissa)
4453
}
4554

46-
pub fn from_integer(value: i64, exponent: u32) -> Result<Self, RateError> {
47-
let coef = 10i64.checked_pow(exponent).ok_or(RateError::Overflow)?;
48-
let value = value.checked_mul(coef).ok_or(RateError::Overflow)?;
55+
pub fn from_f64(value: f64, exponent: i16) -> Result<Self, RateError> {
56+
let value = Decimal::from_f64(value).ok_or(RateError::Overflow)?;
57+
let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
58+
ExponentFactor::Mul(coef) => value
59+
.checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
60+
.ok_or(RateError::Overflow)?,
61+
ExponentFactor::Div(coef) => value
62+
.checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
63+
.ok_or(RateError::Overflow)?,
64+
};
65+
let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?;
4966
Ok(Self(value))
5067
}
5168

52-
pub const fn from_mantissa(mantissa: i64) -> Self {
53-
Self(mantissa)
54-
}
55-
5669
pub fn mantissa(self) -> i64 {
5770
self.0
5871
}
5972

60-
pub fn to_f64(self, exponent: u32) -> Result<f64, RateError> {
61-
Ok(self.0 as f64 / 10i64.checked_pow(exponent).ok_or(RateError::Overflow)? as f64)
73+
pub fn to_f64(self, exponent: i16) -> Result<f64, RateError> {
74+
match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
75+
// Mul/div is reversed for this conversion
76+
ExponentFactor::Mul(coef) => Ok(self.0 as f64 / coef as f64),
77+
ExponentFactor::Div(coef) => Ok(self.0 as f64 * coef as f64),
78+
}
6279
}
6380
}

0 commit comments

Comments
 (0)