1//! ZEC amount formatting.
2//!
3//! The `f64` values returned by this type should not be used in consensus-critical code.
4//! The values themselves are accurate, but any calculations using them could be lossy.
56use std::{
7 fmt,
8 hash::{Hash, Hasher},
9 ops,
10 str::FromStr,
11};
1213use zebra_chain::amount::{self, Amount, Constraint, COIN};
1415use zebra_node_services::BoxError;
1617// Doc links only
18#[allow(unused_imports)]
19use zebra_chain::amount::MAX_MONEY;
2021/// The maximum precision of a zatoshi in ZEC.
22/// Also used as the default decimal precision for ZEC formatting.
23///
24/// This is the same as the `getblocksubsidy` RPC in `zcashd`:
25/// <https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L134>
26pub const MAX_ZEC_FORMAT_PRECISION: usize = 8;
2728/// A wrapper type that formats [`Amount`]s as ZEC, using double-precision floating point.
29///
30/// This formatting is accurate to the nearest zatoshi, as long as the number of floating-point
31/// calculations is very small. This is because [`MAX_MONEY`] uses 51 bits, but [`f64`] has
32/// [53 bits of precision](f64::MANTISSA_DIGITS).
33///
34/// Rust uses [`roundTiesToEven`](f32), which can lose one bit of precision per calculation
35/// in the worst case. (Assuming the platform implements it correctly.)
36///
37/// Unlike `zcashd`, Zebra doesn't have control over its JSON number precision,
38/// because it uses `serde_json`'s formatter. But `zcashd` uses a fixed-point calculation:
39/// <https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L134>
40#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Default)]
41#[serde(try_from = "f64")]
42#[serde(into = "f64")]
43#[serde(bound = "C: Constraint + Clone")]
44pub struct Zec<C: Constraint>(Amount<C>);
4546impl<C: Constraint> Zec<C> {
47/// Returns the `f64` ZEC value for the inner amount.
48 ///
49 /// The returned value should not be used for consensus-critical calculations,
50 /// because it is lossy.
51pub fn lossy_zec(&self) -> f64 {
52let zats = self.zatoshis();
53// These conversions are exact, because f64 has 53 bits of precision,
54 // MAX_MONEY has <51, and COIN has <27, so we have 2 extra bits of precision.
55let zats = zats as f64;
56let coin = COIN as f64;
5758// After this calculation, we might have lost one bit of precision,
59 // leaving us with only 1 extra bit.
60zats / coin
61 }
6263/// Converts a `f64` ZEC value to a [`Zec`] amount.
64 ///
65 /// This method should not be used for consensus-critical calculations, because it is lossy.
66pub fn from_lossy_zec(lossy_zec: f64) -> Result<Self, BoxError> {
67// This conversion is exact, because f64 has 53 bits of precision, but COIN has <27
68let coin = COIN as f64;
6970// After this calculation, we might have lost one bit of precision
71let zats = lossy_zec * coin;
7273if zats != zats.trunc() {
74return Err(
75"loss of precision parsing ZEC value: floating point had fractional zatoshis"
76.into(),
77 );
78 }
7980// We know this conversion is exact, because we just checked.
81let zats = zats as i64;
82let zats = Amount::try_from(zats)?;
8384Ok(Self(zats))
85 }
86}
8788// These conversions are lossy, so they should not be used in consensus-critical code
89impl<C: Constraint> From<Zec<C>> for f64 {
90fn from(zec: Zec<C>) -> f64 {
91 zec.lossy_zec()
92 }
93}
9495impl<C: Constraint> TryFrom<f64> for Zec<C> {
96type Error = BoxError;
9798fn try_from(value: f64) -> Result<Self, Self::Error> {
99Self::from_lossy_zec(value)
100 }
101}
102103// This formatter should not be used for consensus-critical outputs.
104impl<C: Constraint> fmt::Display for Zec<C> {
105fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106let zec = self.lossy_zec();
107108// Try to format like `zcashd` by default
109let decimals = f.precision().unwrap_or(MAX_ZEC_FORMAT_PRECISION);
110let string = format!("{zec:.decimals$}");
111 f.pad_integral(zec >= 0.0, "", &string)
112 }
113}
114115// This parser should not be used for consensus-critical inputs.
116impl<C: Constraint> FromStr for Zec<C> {
117type Err = BoxError;
118119fn from_str(s: &str) -> Result<Self, Self::Err> {
120let lossy_zec: f64 = s.parse()?;
121122Self::from_lossy_zec(lossy_zec)
123 }
124}
125126impl<C: Constraint> std::fmt::Debug for Zec<C> {
127fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 f.debug_struct(&format!("Zec<{}>", std::any::type_name::<C>()))
129 .field("ZEC", &self.to_string())
130 .field("zat", &self.0)
131 .finish()
132 }
133}
134135impl<C: Constraint> ops::Deref for Zec<C> {
136type Target = Amount<C>;
137138fn deref(&self) -> &Self::Target {
139&self.0
140}
141}
142143impl<C: Constraint> ops::DerefMut for Zec<C> {
144fn deref_mut(&mut self) -> &mut Self::Target {
145&mut self.0
146}
147}
148149impl<C: Constraint> From<Amount<C>> for Zec<C> {
150fn from(amount: Amount<C>) -> Self {
151Self(amount)
152 }
153}
154155impl<C: Constraint> From<Zec<C>> for Amount<C> {
156fn from(zec: Zec<C>) -> Amount<C> {
157 zec.0
158}
159}
160161impl<C: Constraint> From<Zec<C>> for i64 {
162fn from(zec: Zec<C>) -> i64 {
163 zec.0.into()
164 }
165}
166167impl<C: Constraint> TryFrom<i64> for Zec<C> {
168type Error = amount::Error;
169170fn try_from(value: i64) -> Result<Self, Self::Error> {
171Ok(Self(Amount::try_from(value)?))
172 }
173}
174175impl<C: Constraint> Hash for Zec<C> {
176/// Zecs with the same value are equal, even if they have different constraints
177fn hash<H: Hasher>(&self, state: &mut H) {
178self.0.hash(state);
179 }
180}
181182impl<C1: Constraint, C2: Constraint> PartialEq<Zec<C2>> for Zec<C1> {
183fn eq(&self, other: &Zec<C2>) -> bool {
184self.0.eq(&other.0)
185 }
186}
187188impl<C: Constraint> PartialEq<i64> for Zec<C> {
189fn eq(&self, other: &i64) -> bool {
190self.0.eq(other)
191 }
192}
193194impl<C: Constraint> PartialEq<Zec<C>> for i64 {
195fn eq(&self, other: &Zec<C>) -> bool {
196self.eq(&other.0)
197 }
198}
199200impl<C1: Constraint, C2: Constraint> PartialEq<Amount<C2>> for Zec<C1> {
201fn eq(&self, other: &Amount<C2>) -> bool {
202self.0.eq(other)
203 }
204}
205206impl<C1: Constraint, C2: Constraint> PartialEq<Zec<C2>> for Amount<C1> {
207fn eq(&self, other: &Zec<C2>) -> bool {
208self.eq(&other.0)
209 }
210}
211212impl<C: Constraint> Eq for Zec<C> {}