Skip to main content

dpp/address_funds/fee_strategy/
mod.rs

1pub mod deduct_fee_from_inputs_and_outputs;
2
3pub use deduct_fee_from_inputs_and_outputs::FeeDeductionResult;
4
5use bincode::{Decode, Encode};
6#[cfg(feature = "serde-conversion")]
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, Hash)]
10pub enum AddressFundsFeeStrategyStep {
11    /// Deduct fee from a specific input address by index.
12    /// The input must have remaining balance after its contribution to outputs.
13    DeductFromInput(u16),
14    /// Reduce a specific output by the fee amount.
15    /// The output amount will be reduced to cover the fee.
16    ReduceOutput(u16),
17}
18
19impl Default for AddressFundsFeeStrategyStep {
20    fn default() -> Self {
21        AddressFundsFeeStrategyStep::DeductFromInput(0)
22    }
23}
24
25pub type AddressFundsFeeStrategy = Vec<AddressFundsFeeStrategyStep>;
26
27// Custom serde impls so JSON / wasm Object output uses the standard
28// `{ "type": "...", "index": N }` discriminator shape used elsewhere in
29// the DPP wasm bindings. The bincode `Encode` / `Decode` derives above are
30// the consensus-critical binary format and are intentionally untouched.
31#[cfg(feature = "serde-conversion")]
32impl Serialize for AddressFundsFeeStrategyStep {
33    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34    where
35        S: serde::Serializer,
36    {
37        use serde::ser::SerializeStruct;
38
39        let mut state = serializer.serialize_struct("AddressFundsFeeStrategyStep", 2)?;
40        match self {
41            AddressFundsFeeStrategyStep::DeductFromInput(index) => {
42                state.serialize_field("type", "deductFromInput")?;
43                state.serialize_field("index", index)?;
44            }
45            AddressFundsFeeStrategyStep::ReduceOutput(index) => {
46                state.serialize_field("type", "reduceOutput")?;
47                state.serialize_field("index", index)?;
48            }
49        }
50        state.end()
51    }
52}
53
54#[cfg(feature = "serde-conversion")]
55impl<'de> Deserialize<'de> for AddressFundsFeeStrategyStep {
56    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57    where
58        D: serde::Deserializer<'de>,
59    {
60        use serde::de::{self, MapAccess, Visitor};
61        use std::fmt;
62
63        struct StepVisitor;
64
65        impl<'de> Visitor<'de> for StepVisitor {
66            type Value = AddressFundsFeeStrategyStep;
67
68            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
69                formatter.write_str("an AddressFundsFeeStrategyStep struct with type and index")
70            }
71
72            fn visit_map<V>(self, mut map: V) -> Result<AddressFundsFeeStrategyStep, V::Error>
73            where
74                V: MapAccess<'de>,
75            {
76                let mut step_type: Option<String> = None;
77                let mut index: Option<u16> = None;
78
79                while let Some(key) = map.next_key::<String>()? {
80                    match key.as_str() {
81                        "type" => {
82                            if step_type.is_some() {
83                                return Err(de::Error::duplicate_field("type"));
84                            }
85                            step_type = Some(map.next_value()?);
86                        }
87                        "index" => {
88                            if index.is_some() {
89                                return Err(de::Error::duplicate_field("index"));
90                            }
91                            index = Some(map.next_value()?);
92                        }
93                        _ => {
94                            let _: serde::de::IgnoredAny = map.next_value()?;
95                        }
96                    }
97                }
98
99                let step_type = step_type.ok_or_else(|| de::Error::missing_field("type"))?;
100                let index = index.ok_or_else(|| de::Error::missing_field("index"))?;
101
102                match step_type.as_str() {
103                    "deductFromInput" => Ok(AddressFundsFeeStrategyStep::DeductFromInput(index)),
104                    "reduceOutput" => Ok(AddressFundsFeeStrategyStep::ReduceOutput(index)),
105                    other => Err(de::Error::unknown_variant(
106                        other,
107                        &["deductFromInput", "reduceOutput"],
108                    )),
109                }
110            }
111        }
112
113        deserializer.deserialize_struct(
114            "AddressFundsFeeStrategyStep",
115            &["type", "index"],
116            StepVisitor,
117        )
118    }
119}
120
121#[cfg(all(test, feature = "serde-conversion"))]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn deduct_from_input_serializes_with_type_and_index() {
127        let step = AddressFundsFeeStrategyStep::DeductFromInput(7);
128        let json = serde_json::to_value(&step).unwrap();
129        assert_eq!(
130            json,
131            serde_json::json!({ "type": "deductFromInput", "index": 7 })
132        );
133    }
134
135    #[test]
136    fn reduce_output_serializes_with_type_and_index() {
137        let step = AddressFundsFeeStrategyStep::ReduceOutput(3);
138        let json = serde_json::to_value(&step).unwrap();
139        assert_eq!(
140            json,
141            serde_json::json!({ "type": "reduceOutput", "index": 3 })
142        );
143    }
144
145    #[test]
146    fn deserializes_from_type_and_index() {
147        let step: AddressFundsFeeStrategyStep =
148            serde_json::from_value(serde_json::json!({ "type": "deductFromInput", "index": 9 }))
149                .unwrap();
150        assert_eq!(step, AddressFundsFeeStrategyStep::DeductFromInput(9));
151
152        let step: AddressFundsFeeStrategyStep =
153            serde_json::from_value(serde_json::json!({ "type": "reduceOutput", "index": 2 }))
154                .unwrap();
155        assert_eq!(step, AddressFundsFeeStrategyStep::ReduceOutput(2));
156    }
157
158    #[test]
159    fn rejects_unknown_variant() {
160        let result: Result<AddressFundsFeeStrategyStep, _> =
161            serde_json::from_value(serde_json::json!({ "type": "burn", "index": 0 }));
162        assert!(result.is_err());
163    }
164
165    #[test]
166    fn round_trips_through_json() {
167        for original in [
168            AddressFundsFeeStrategyStep::DeductFromInput(0),
169            AddressFundsFeeStrategyStep::DeductFromInput(42),
170            AddressFundsFeeStrategyStep::ReduceOutput(0),
171            AddressFundsFeeStrategyStep::ReduceOutput(42),
172        ] {
173            let json = serde_json::to_string(&original).unwrap();
174            let restored: AddressFundsFeeStrategyStep = serde_json::from_str(&json).unwrap();
175            assert_eq!(original, restored);
176        }
177    }
178}