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    // frozen: V0 consensus behavior
100    //
101    // `IdentityV0::to_object()` (from the `ValueConvertible` derive) serializes through
102    // platform_value's non-human-readable path and encodes `BinaryData` as `Value::Bytes`.
103    // But `platform_value::from_value(...)` produces inner deserializers that default to
104    // `is_human_readable() = true`, so `BinaryData::deserialize` dispatches to its string
105    // visitor and fails on `Value::Bytes`. The direct round-trip therefore does NOT work;
106    // consumers must go through the explicit conversion helpers (JSON path, etc.).
107    #[test]
108    fn to_object_then_try_from_fails_v0_frozen() {
109        let id = sample_with_disabled(Some(9));
110        let value = id.to_object().unwrap();
111        let result = IdentityV0::try_from(value);
112        assert!(
113            result.is_err(),
114            "V0 to_object -> TryFrom<Value> round-trip is not expected to succeed"
115        );
116    }
117
118    #[test]
119    fn try_from_ref_value_fails_on_to_object_output_v0_frozen() {
120        let id = sample_with_disabled(None);
121        let value = id.to_object().unwrap();
122        let result = IdentityV0::try_from(&value);
123        assert!(result.is_err());
124    }
125}