Skip to main content

dpp/withdrawal/
mod.rs

1pub mod daily_withdrawal_limit;
2#[cfg(all(feature = "withdrawals-contract", feature = "system_contracts"))]
3mod document_try_into_asset_unlock_base_transaction_info;
4
5use bincode::{Decode, Encode};
6use serde_repr::{Deserialize_repr, Serialize_repr};
7
8#[repr(u8)]
9#[derive(
10    Serialize_repr, Deserialize_repr, PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, Default,
11)]
12pub enum Pooling {
13    #[default]
14    Never = 0,
15    IfAvailable = 1,
16    Standard = 2,
17}
18
19/// Transaction index type
20pub type WithdrawalTransactionIndex = u64;
21
22/// Simple type alias for withdrawal transaction with it's index
23pub type WithdrawalTransactionIndexAndBytes = (WithdrawalTransactionIndex, Vec<u8>);
24
25/// Serde helper for `Pooling` fields exposed through the JS surface.
26///
27/// `Pooling` is `#[repr(u8)]` with `Serialize_repr` / `Deserialize_repr`, so the
28/// default wire shape is the numeric discriminant (`0`/`1`/`2`). That number
29/// leaks into JSON / Object output and makes `XxxJSON.pooling: string`
30/// declarations false. The helper switches the **human-readable** path to a
31/// camelCase string (`"never"`/`"ifAvailable"`/`"standard"`) while keeping the
32/// non-HR path at the original `u8` so bincode (consensus binary format) is
33/// untouched.
34///
35/// Apply via `#[serde(with = "crate::withdrawal::pooling_serde")]` on the
36/// `pooling` field of any state transition that surfaces it to JS.
37#[cfg(feature = "serde-conversion")]
38pub mod pooling_serde {
39    use super::Pooling;
40    use serde::{Deserializer, Serialize, Serializer};
41
42    pub fn serialize<S: Serializer>(pooling: &Pooling, serializer: S) -> Result<S::Ok, S::Error> {
43        if serializer.is_human_readable() {
44            let name = match pooling {
45                Pooling::Never => "never",
46                Pooling::IfAvailable => "ifAvailable",
47                Pooling::Standard => "standard",
48            };
49            serializer.serialize_str(name)
50        } else {
51            (*pooling as u8).serialize(serializer)
52        }
53    }
54
55    /// Deserialize accepts both shapes regardless of the deserializer's
56    /// human-readable flag — mirrors the `BinaryData` / `Identifier` pattern.
57    /// Necessary because `platform_value::to_value` reports HR=false (emits the
58    /// numeric discriminant on the way to `JsValue`), but
59    /// `platform_value::from_value` reports HR=true on the way back. Without
60    /// dual acceptance, the `fromObject(toObject())` round-trip fails on the
61    /// `pooling` field.
62    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Pooling, D::Error> {
63        struct PoolingVisitor;
64
65        impl<'de> serde::de::Visitor<'de> for PoolingVisitor {
66            type Value = Pooling;
67
68            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
69                f.write_str("a Pooling variant: 'never'/'ifAvailable'/'standard' or 0/1/2")
70            }
71
72            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Pooling, E> {
73                match v {
74                    "never" | "Never" => Ok(Pooling::Never),
75                    "ifAvailable" | "IfAvailable" | "ifavailable" => Ok(Pooling::IfAvailable),
76                    "standard" | "Standard" => Ok(Pooling::Standard),
77                    other => Err(E::custom(format!(
78                        "unknown pooling variant '{}', expected 'never' | 'ifAvailable' | 'standard'",
79                        other
80                    ))),
81                }
82            }
83
84            fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Pooling, E> {
85                self.visit_str(&v)
86            }
87
88            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Pooling, E> {
89                match v {
90                    0 => Ok(Pooling::Never),
91                    1 => Ok(Pooling::IfAvailable),
92                    2 => Ok(Pooling::Standard),
93                    other => Err(E::custom(format!("unknown pooling discriminant {}", other))),
94                }
95            }
96
97            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Pooling, E> {
98                if v < 0 {
99                    return Err(E::custom(format!("negative pooling discriminant {}", v)));
100                }
101                self.visit_u64(v as u64)
102            }
103
104            fn visit_u8<E: serde::de::Error>(self, v: u8) -> Result<Pooling, E> {
105                self.visit_u64(v as u64)
106            }
107        }
108
109        if deserializer.is_human_readable() {
110            deserializer.deserialize_any(PoolingVisitor)
111        } else {
112            deserializer.deserialize_u8(PoolingVisitor)
113        }
114    }
115
116    #[cfg(test)]
117    mod tests {
118        use super::*;
119        use serde::{Deserialize, Serialize};
120
121        #[derive(Serialize, Deserialize, PartialEq, Debug)]
122        struct Wrap(#[serde(with = "super")] Pooling);
123
124        #[test]
125        fn json_emits_camelcase_string() {
126            for (variant, expected) in [
127                (Pooling::Never, "\"never\""),
128                (Pooling::IfAvailable, "\"ifAvailable\""),
129                (Pooling::Standard, "\"standard\""),
130            ] {
131                let json = serde_json::to_string(&Wrap(variant)).expect("serialize");
132                assert_eq!(json, expected);
133                let restored: Wrap = serde_json::from_str(expected).expect("deserialize");
134                assert_eq!(restored, Wrap(variant));
135            }
136        }
137
138        #[test]
139        fn bincode_keeps_u8_discriminant() {
140            for (variant, expected_u8) in [
141                (Pooling::Never, 0),
142                (Pooling::IfAvailable, 1),
143                (Pooling::Standard, 2),
144            ] {
145                let bytes =
146                    bincode::serde::encode_to_vec(Wrap(variant), bincode::config::standard())
147                        .expect("bincode encode");
148                assert_eq!(bytes.last(), Some(&expected_u8));
149                let (restored, _): (Wrap, usize) =
150                    bincode::serde::decode_from_slice(&bytes, bincode::config::standard())
151                        .expect("bincode decode");
152                assert_eq!(restored, Wrap(variant));
153            }
154        }
155    }
156}