Skip to main content

dpp/state_transition/
proof_result.rs

1use crate::address_funds::PlatformAddress;
2use crate::asset_lock::StoredAssetLockInfo;
3use crate::balances::credits::TokenAmount;
4use crate::data_contract::group::GroupSumPower;
5use crate::data_contract::DataContract;
6use crate::document::Document;
7use crate::fee::Credits;
8use crate::group::group_action_status::GroupActionStatus;
9use crate::identity::{Identity, PartialIdentity};
10use crate::prelude::AddressNonce;
11use crate::tokens::info::IdentityTokenInfo;
12use crate::tokens::status::TokenStatus;
13use crate::tokens::token_pricing_schedule::TokenPricingSchedule;
14use crate::voting::votes::Vote;
15use platform_value::Identifier;
16use std::collections::BTreeMap;
17
18#[derive(Debug, PartialEq, strum::Display, derive_more::TryInto)]
19#[cfg_attr(
20    feature = "serde-conversion",
21    derive(serde::Serialize, serde::Deserialize)
22)]
23pub enum StateTransitionProofResult {
24    VerifiedDataContract(DataContract),
25    VerifiedIdentity(Identity),
26    VerifiedTokenBalanceAbsence(Identifier),
27    // `TokenAmount`/`Credits` (u64) live in tuple variants / nested containers
28    // that `#[json_safe_fields]` can't reach; apply the JS-safe helpers directly
29    // so values above `MAX_SAFE_INTEGER` serialize as strings in human-readable
30    // JSON. (`AddressNonce`/`GroupSumPower` are `u32` → already JS-safe; the
31    // `Vec<u8>` nullifiers are arrays of bytes < 256 → no precision concern.)
32    VerifiedTokenBalance(
33        Identifier,
34        #[cfg_attr(
35            feature = "json-conversion",
36            serde(with = "crate::serialization::json_safe_u64")
37        )]
38        TokenAmount,
39    ),
40    VerifiedTokenIdentityInfo(Identifier, IdentityTokenInfo),
41    VerifiedTokenPricingSchedule(Identifier, Option<TokenPricingSchedule>),
42    VerifiedTokenStatus(TokenStatus),
43    VerifiedTokenIdentitiesBalances(
44        #[cfg_attr(
45            feature = "json-conversion",
46            serde(
47                with = "crate::serialization::json::safe_integer_map::json_safe_identifier_u64_map"
48            )
49        )]
50        BTreeMap<Identifier, TokenAmount>,
51    ),
52    VerifiedPartialIdentity(PartialIdentity),
53    VerifiedBalanceTransfer(PartialIdentity, PartialIdentity), //from/to
54    VerifiedDocuments(BTreeMap<Identifier, Option<Document>>),
55    VerifiedTokenActionWithDocument(Document),
56    VerifiedTokenGroupActionWithDocument(GroupSumPower, Option<Document>),
57    VerifiedTokenGroupActionWithTokenBalance(
58        GroupSumPower,
59        GroupActionStatus,
60        #[cfg_attr(
61            feature = "json-conversion",
62            serde(with = "crate::serialization::json_safe_option_u64")
63        )]
64        Option<TokenAmount>,
65    ),
66    VerifiedTokenGroupActionWithTokenIdentityInfo(
67        GroupSumPower,
68        GroupActionStatus,
69        Option<IdentityTokenInfo>,
70    ),
71    VerifiedTokenGroupActionWithTokenPricingSchedule(
72        GroupSumPower,
73        GroupActionStatus,
74        Option<TokenPricingSchedule>,
75    ),
76    VerifiedMasternodeVote(Vote),
77    VerifiedNextDistribution(Vote),
78    VerifiedAddressInfos(
79        #[cfg_attr(
80            feature = "json-conversion",
81            serde(with = "json_safe_address_info_map")
82        )]
83        BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
84    ),
85    VerifiedIdentityFullWithAddressInfos(
86        Identity,
87        #[cfg_attr(
88            feature = "json-conversion",
89            serde(with = "json_safe_address_info_map")
90        )]
91        BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
92    ),
93    VerifiedIdentityWithAddressInfos(
94        PartialIdentity,
95        #[cfg_attr(
96            feature = "json-conversion",
97            serde(with = "json_safe_address_info_map")
98        )]
99        BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
100    ),
101    VerifiedAssetLockConsumed(StoredAssetLockInfo),
102    VerifiedShieldedNullifiers(Vec<(Vec<u8>, bool)>),
103    VerifiedShieldedNullifiersWithAddressInfos(
104        Vec<(Vec<u8>, bool)>,
105        #[cfg_attr(
106            feature = "json-conversion",
107            serde(with = "json_safe_address_info_map")
108        )]
109        BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
110    ),
111    VerifiedShieldedNullifiersWithWithdrawalDocument(
112        Vec<(Vec<u8>, bool)>,
113        BTreeMap<Identifier, Option<Document>>,
114    ),
115    /// Returned by `ShieldFromAssetLock` when a `surplus_output` is set. Carries the consumed
116    /// asset-lock info AND the proven balance of the surplus-output address, so a light/SDK
117    /// client can cryptographically confirm the asset-lock surplus credit landed at the signed
118    /// `surplus_output` address. The plain [`VerifiedAssetLockConsumed`] is still returned when
119    /// no `surplus_output` is set.
120    ///
121    /// [`VerifiedAssetLockConsumed`]: StateTransitionProofResult::VerifiedAssetLockConsumed
122    VerifiedAssetLockConsumedWithAddressInfos(
123        StoredAssetLockInfo,
124        BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
125    ),
126    /// Returned by `IdentityCreateFromShieldedPool`. Carries the newly-created [`Identity`] AND the
127    /// presence of each spent nullifier (`(nullifier_bytes, present)`), proven together in a single
128    /// STRICT merged multi-root GroveDB proof. A light/SDK client can cryptographically confirm both
129    /// that the identity was created and that the funding nullifiers were consumed.
130    VerifiedIdentityWithShieldedNullifiers(Identity, Vec<(Vec<u8>, bool)>),
131}
132
133/// Serde `with` module for `BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>`.
134///
135/// `AddressNonce` is `u32` (JS-safe); `Credits` is `u64` and must serialize as a
136/// string in human-readable JSON above `MAX_SAFE_INTEGER`. A small wrapper tuple
137/// carries `#[serde(with = "json_safe_u64")]` on the credits, preserving the
138/// `[nonce, credits]` array wire-shape while making the value JS-safe. Binary /
139/// `Value` paths stay native (the helper checks `is_human_readable`).
140#[cfg(feature = "json-conversion")]
141mod json_safe_address_info_map {
142    use super::{AddressNonce, Credits, PlatformAddress};
143    use serde::de::Deserializer;
144    use serde::ser::{SerializeMap, Serializer};
145    use serde::{Deserialize, Serialize};
146    use std::collections::BTreeMap;
147
148    /// The address-info map shape shared by several `StateTransitionProofResult`
149    /// variants. Aliased to keep the helper signatures below under clippy's
150    /// `type_complexity` threshold.
151    type AddressInfoMap = BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>;
152
153    #[derive(Serialize, Deserialize)]
154    struct Entry(
155        AddressNonce,
156        #[serde(with = "crate::serialization::json_safe_u64")] Credits,
157    );
158
159    pub fn serialize<S: Serializer>(
160        map: &AddressInfoMap,
161        serializer: S,
162    ) -> Result<S::Ok, S::Error> {
163        let mut s = serializer.serialize_map(Some(map.len()))?;
164        for (k, v) in map {
165            let wrapped = v.map(|(nonce, credits)| Entry(nonce, credits));
166            s.serialize_entry(k, &wrapped)?;
167        }
168        s.end()
169    }
170
171    pub fn deserialize<'de, D: Deserializer<'de>>(
172        deserializer: D,
173    ) -> Result<AddressInfoMap, D::Error> {
174        let raw: BTreeMap<PlatformAddress, Option<Entry>> = BTreeMap::deserialize(deserializer)?;
175        Ok(raw
176            .into_iter()
177            .map(|(k, v)| (k, v.map(|Entry(nonce, credits)| (nonce, credits))))
178            .collect())
179    }
180}
181
182// --- canonical conversion trait impls (unification pass 1) ---
183#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
184impl crate::serialization::JsonConvertible for StateTransitionProofResult {}
185
186#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
187impl crate::serialization::ValueConvertible for StateTransitionProofResult {}
188
189#[cfg(all(
190    test,
191    feature = "json-conversion",
192    feature = "value-conversion",
193    feature = "serde-conversion"
194))]
195mod json_convertible_tests {
196    use super::*;
197    use platform_value::{Identifier, Value};
198    use serde_json::json;
199
200    /// Non-default variant `VerifiedTokenBalance(id, amount)` with both
201    /// tuple fields set so the wire-shape assertion catches silent variant
202    /// flip / inner-zero on round-trip.
203    fn fixture() -> StateTransitionProofResult {
204        StateTransitionProofResult::VerifiedTokenBalance(
205            Identifier::new([0xab; 32]),
206            123_456_789u64,
207        )
208    }
209
210    #[test]
211    fn json_round_trip_with_full_wire_shape() {
212        use crate::serialization::JsonConvertible;
213        let original = fixture();
214        let json = original.to_json().expect("to_json");
215        // `StateTransitionProofResult` uses serde external tagging (default,
216        // no `#[serde(tag = ...)]`). Tuple variants serialize as
217        // `{ "VariantName": [field0, field1, ...] }`. `Identifier` -> base58
218        // string in JSON; `TokenAmount` is `u64` and JSON erases the size —
219        // see the value-path assertion which uses `123_456_789u64`.
220        assert_eq!(
221            json,
222            json!({
223                "VerifiedTokenBalance": [
224                    "CZ8YUVdk7znjrUmnb5n7kgySk9yRAsQDYmyCxzfSky9t",
225                    123_456_789u64,
226                ],
227            })
228        );
229        let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
230        assert_eq!(original, recovered);
231    }
232
233    #[test]
234    fn value_round_trip_with_full_wire_shape() {
235        use crate::serialization::ValueConvertible;
236        let original = fixture();
237        let value = original.to_object().expect("to_object");
238        // platform_value preserves typed `Identifier` and `U64` variants. We
239        // construct the expected `Value::Map` by hand: `platform_value!{...}`
240        // would convert the `Identifier` interpolation through Serialize
241        // (correct) but the outer shape has only one (Text-keyed) entry whose
242        // value is an Array of mixed-typed Values, so it's clearer to write
243        // the literal Map.
244        let expected = Value::Map(vec![(
245            Value::Text("VerifiedTokenBalance".to_string()),
246            Value::Array(vec![Value::Identifier([0xab; 32]), Value::U64(123_456_789)]),
247        )]);
248        assert_eq!(value, expected);
249        let recovered = StateTransitionProofResult::from_object(value).expect("from_object");
250        assert_eq!(original, recovered);
251    }
252
253    #[test]
254    fn verified_token_balance_large_amount_serializes_as_string() {
255        use crate::serialization::JsonConvertible;
256        // `TokenAmount` above `Number.MAX_SAFE_INTEGER` must serialize as a JSON
257        // string (it sits in a tuple variant the macro can't reach).
258        let original = StateTransitionProofResult::VerifiedTokenBalance(
259            Identifier::new([0xab; 32]),
260            9_007_199_254_740_993, // 2^53 + 1
261        );
262        let json = original.to_json().expect("to_json");
263        assert_eq!(json["VerifiedTokenBalance"][1], json!("9007199254740993"));
264        let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
265        assert_eq!(original, recovered);
266    }
267
268    #[test]
269    fn verified_address_infos_large_credits_serialize_as_string() {
270        use crate::serialization::{JsonConvertible, ValueConvertible};
271        use std::collections::BTreeMap;
272        // `Credits` (u64) nested in `BTreeMap<PlatformAddress, Option<(AddressNonce,
273        // Credits)>>` must be JS-safe via the bespoke `json_safe_address_info_map`
274        // helper, preserving the `[nonce, credits]` array wire-shape.
275        let big_credits: Credits = 9_007_199_254_740_993; // 2^53 + 1
276        let mut infos: BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>> = BTreeMap::new();
277        infos.insert(PlatformAddress::P2pkh([0x11; 20]), Some((7, big_credits)));
278        infos.insert(PlatformAddress::P2sh([0x22; 20]), None);
279        let original = StateTransitionProofResult::VerifiedAddressInfos(infos);
280
281        // Human-readable JSON: credits string, nonce number, null preserved.
282        let json = original.to_json().expect("to_json");
283        let map = json["VerifiedAddressInfos"].as_object().expect("object");
284        let non_null = map
285            .values()
286            .find(|v| !v.is_null())
287            .expect("one populated entry");
288        assert_eq!(non_null[0], json!(7));
289        assert_eq!(non_null[1], json!("9007199254740993"));
290        assert!(
291            map.values().any(|v| v.is_null()),
292            "the None entry must survive"
293        );
294        let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
295        assert_eq!(original, recovered);
296
297        // Non-human-readable (platform_value): native u64, round-trips intact.
298        let value = original.to_object().expect("to_object");
299        let recovered = StateTransitionProofResult::from_object(value).expect("from_object");
300        assert_eq!(original, recovered);
301    }
302}