Skip to main content

dpp/identity/v0/conversion/
platform_value.rs

1use crate::identity::conversion::platform_value::IdentityPlatformValueConversionMethodsV0;
2use crate::identity::{property_names, IdentityV0};
3#[cfg(feature = "value-conversion")]
4use crate::serialization::ValueConvertible;
5use crate::ProtocolError;
6use platform_value::Value;
7
8impl IdentityPlatformValueConversionMethodsV0 for IdentityV0 {
9    fn to_cleaned_object(&self) -> Result<Value, ProtocolError> {
10        //same as object for Identities
11        let mut value = self.to_object()?;
12        if let Some(keys) = value.get_optional_array_mut_ref(property_names::PUBLIC_KEYS)? {
13            for key in keys.iter_mut() {
14                key.remove_optional_value_if_null("disabledAt")?;
15            }
16        }
17        Ok(value)
18    }
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24    use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
25    use crate::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel};
26    use platform_value::{BinaryData, Identifier};
27    use std::collections::BTreeMap;
28
29    fn sample_with_disabled(disabled_at: Option<u64>) -> IdentityV0 {
30        let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
31        keys.insert(
32            0,
33            IdentityPublicKey::V0(IdentityPublicKeyV0 {
34                id: 0,
35                purpose: Purpose::AUTHENTICATION,
36                security_level: SecurityLevel::MASTER,
37                contract_bounds: None,
38                key_type: KeyType::ECDSA_SECP256K1,
39                read_only: false,
40                data: BinaryData::new(vec![0x11; 33]),
41                disabled_at,
42            }),
43        );
44        IdentityV0 {
45            id: Identifier::from([0u8; 32]),
46            public_keys: keys,
47            balance: 0,
48            revision: 0,
49        }
50    }
51
52    fn key_map_at_index(value: &Value, index: usize) -> &Vec<(Value, Value)> {
53        let map = value.to_map_ref().expect("map");
54        let pks = map
55            .iter()
56            .find(|(k, _)| k.as_text() == Some("publicKeys"))
57            .map(|(_, v)| v)
58            .expect("publicKeys");
59        let arr = pks.to_array_ref().expect("array");
60        arr[index].to_map_ref().expect("key map")
61    }
62
63    #[test]
64    fn to_cleaned_object_strips_null_disabled_at_from_keys() {
65        let id = sample_with_disabled(None);
66        let cleaned = id.to_cleaned_object().expect("cleaned");
67        let key_map = key_map_at_index(&cleaned, 0);
68        assert!(
69            !key_map
70                .iter()
71                .any(|(k, _)| k.as_text() == Some("disabledAt")),
72            "disabledAt should have been stripped"
73        );
74    }
75
76    #[test]
77    fn to_cleaned_object_preserves_present_disabled_at() {
78        let id = sample_with_disabled(Some(123));
79        let cleaned = id.to_cleaned_object().expect("cleaned");
80        let key_map = key_map_at_index(&cleaned, 0);
81        assert!(key_map
82            .iter()
83            .any(|(k, _)| k.as_text() == Some("disabledAt")));
84    }
85
86    #[test]
87    fn to_object_and_cleaned_are_same_for_empty_keys() {
88        let id = IdentityV0 {
89            id: Identifier::from([1u8; 32]),
90            public_keys: BTreeMap::new(),
91            balance: 1,
92            revision: 2,
93        };
94        let object = id.to_object().expect("to_object");
95        let cleaned = id.to_cleaned_object().expect("cleaned");
96        assert_eq!(object, cleaned);
97    }
98
99    // V0 to_object -> TryFrom<Value> round-trip succeeds.
100    //
101    // Previously this failed because of an asymmetric `BinaryData::Deserialize`
102    // (string-only in human-readable mode, bytes-only in binary mode), while
103    // `platform_value`'s nested deserializers default to
104    // `is_human_readable() = true` even when the value carries `Value::Bytes`.
105    // The fix in PR #3235 made `BinaryData::Deserialize` symmetric (accepts
106    // both strings and bytes regardless of mode, mirroring `Identifier`).
107    // Bincode `Encode`/`Decode` derives are untouched, so consensus binary
108    // format is unchanged — only the serde platform_value path now round-trips.
109    #[test]
110    fn to_object_then_try_from_round_trips_v0() {
111        let id = sample_with_disabled(Some(9));
112        let value = id.to_object().unwrap();
113        let back = IdentityV0::try_from(value).expect("v0 round-trip should succeed");
114        assert_eq!(id, back);
115    }
116
117    #[test]
118    fn try_from_ref_value_round_trips_v0() {
119        let id = sample_with_disabled(None);
120        let value = id.to_object().unwrap();
121        let back = IdentityV0::try_from(&value).expect("v0 round-trip should succeed");
122        assert_eq!(id, back);
123    }
124}