Skip to main content

dpp/identity/conversion/platform_value/
mod.rs

1use crate::identity::{Identity, IdentityV0};
2use crate::version::PlatformVersion;
3use crate::ProtocolError;
4use platform_value::Value;
5use platform_version::TryFromPlatformVersioned;
6
7impl TryFromPlatformVersioned<Value> for Identity {
8    type Error = ProtocolError;
9
10    fn try_from_platform_versioned(
11        value: Value,
12        platform_version: &PlatformVersion,
13    ) -> Result<Self, Self::Error> {
14        match platform_version
15            .dpp
16            .identity_versions
17            .identity_structure_version
18        {
19            0 => {
20                let identity_v0: IdentityV0 =
21                    platform_value::from_value(value).map_err(ProtocolError::ValueError)?;
22                Ok(identity_v0.into())
23            }
24            version => Err(ProtocolError::UnknownVersionMismatch {
25                method: "Identity::try_from_owned_value".to_string(),
26                known_versions: vec![0],
27                received: version,
28            }),
29        }
30    }
31}
32
33impl TryFromPlatformVersioned<&Value> for Identity {
34    type Error = ProtocolError;
35
36    fn try_from_platform_versioned(
37        value: &Value,
38        platform_version: &PlatformVersion,
39    ) -> Result<Self, Self::Error> {
40        match platform_version
41            .dpp
42            .identity_versions
43            .identity_structure_version
44        {
45            0 => {
46                let identity_v0: IdentityV0 =
47                    platform_value::from_value(value.clone()).map_err(ProtocolError::ValueError)?;
48                Ok(identity_v0.into())
49            }
50            version => Err(ProtocolError::UnknownVersionMismatch {
51                method: "Identity::try_from_owned_value".to_string(),
52                known_versions: vec![0],
53                received: version,
54            }),
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::identity::accessors::IdentityGettersV0;
63    use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
64    use crate::identity::IdentityPublicKey;
65    use crate::identity::{KeyType, Purpose, SecurityLevel};
66    use crate::serialization::ValueConvertible;
67    use platform_value::{platform_value, BinaryData, Identifier};
68    use platform_version::version::LATEST_PLATFORM_VERSION;
69    use std::collections::BTreeMap;
70
71    fn sample_identity_v0() -> IdentityV0 {
72        let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
73        keys.insert(
74            0,
75            IdentityPublicKey::V0(IdentityPublicKeyV0 {
76                id: 0,
77                purpose: Purpose::AUTHENTICATION,
78                security_level: SecurityLevel::MASTER,
79                contract_bounds: None,
80                key_type: KeyType::ECDSA_SECP256K1,
81                read_only: false,
82                data: BinaryData::new(vec![0x01; 33]),
83                disabled_at: None,
84            }),
85        );
86        IdentityV0 {
87            id: Identifier::from([42u8; 32]),
88            public_keys: keys,
89            balance: 7,
90            revision: 2,
91        }
92    }
93
94    // A `platform_value::Value` that `try_from_platform_versioned` accepts and
95    // deserializes into an `IdentityV0` via `platform_value::from_value::<IdentityV0>`.
96    //
97    // NOTE: this is *not* a byte-for-byte mirror of `IdentityV0::to_object()`.
98    // `to_object()` produces `Value::Bytes` for `BinaryData` fields (e.g. the
99    // public-key `data`), while this fixture encodes `data` as a base64 STRING.
100    // Both shapes round-trip through the serde deserializer because the inner
101    // `platform_value` deserializer behaves as `is_human_readable() = true` for
102    // nested fields, which accepts the base64-string representation of
103    // `BinaryData`. This fixture deliberately exercises the human-readable path.
104    //
105    // Each inner public key carries the adjacency-tag `$formatVersion: "0"` that
106    // `IdentityPublicKey`'s serde enum representation requires.
107    //
108    // frozen: V0 consensus behavior.
109    fn tagged_raw_value() -> Value {
110        use platform_value::string_encoding::{encode, Encoding};
111        let data_b64 = encode(&[0x22u8; 33], Encoding::Base64);
112        platform_value!({
113            "id": Identifier::from([7u8; 32]),
114            "publicKeys": [
115                {
116                    "$formatVersion": "0",
117                    "id": 0u32,
118                    "type": 0u8,
119                    "purpose": 0u8,
120                    "securityLevel": 0u8,
121                    "contractBounds": Value::Null,
122                    "data": data_b64,
123                    "readOnly": false,
124                    "disabledAt": Value::Null,
125                }
126            ],
127            "balance": 100u64,
128            "revision": 1u64,
129        })
130    }
131
132    #[test]
133    fn try_from_platform_versioned_owned_value_parses_legacy_shape() {
134        let value = tagged_raw_value();
135        let identity = Identity::try_from_platform_versioned(value, LATEST_PLATFORM_VERSION)
136            .expect("should parse legacy raw object");
137        assert_eq!(identity.balance(), 100);
138        assert_eq!(identity.revision(), 1);
139        assert_eq!(identity.public_keys().len(), 1);
140    }
141
142    #[test]
143    fn try_from_platform_versioned_ref_value_parses_legacy_shape() {
144        let value = tagged_raw_value();
145        let identity = Identity::try_from_platform_versioned(&value, LATEST_PLATFORM_VERSION)
146            .expect("should parse legacy raw object from &Value");
147        assert_eq!(identity.balance(), 100);
148    }
149
150    #[test]
151    fn try_from_platform_versioned_errors_on_garbage_owned() {
152        let value = Value::Null;
153        let result = Identity::try_from_platform_versioned(value, LATEST_PLATFORM_VERSION);
154        assert!(matches!(result, Err(ProtocolError::ValueError(_))));
155    }
156
157    #[test]
158    fn try_from_platform_versioned_errors_on_garbage_ref() {
159        let value = Value::Text("not a map".to_string());
160        let result = Identity::try_from_platform_versioned(&value, LATEST_PLATFORM_VERSION);
161        assert!(matches!(result, Err(ProtocolError::ValueError(_))));
162    }
163
164    // After Phase D step 5, `IdentityPlatformValueConversionMethodsV0` has
165    // been deleted; the canonical `ValueConvertible::to_object` produces the
166    // tagged `$formatVersion: "0"` form for the Identity enum wrapper.
167    #[test]
168    fn identity_wrapper_to_object_includes_format_version_tag() {
169        let identity: Identity = sample_identity_v0().into();
170        let value = identity.to_object().expect("to_object");
171        let map = value.to_map_ref().expect("map");
172        assert!(
173            map.iter()
174                .any(|(k, _)| k.as_text() == Some("$formatVersion")),
175            "Identity enum wrapper must keep its format version tag"
176        );
177    }
178
179    #[test]
180    fn identity_wrapper_to_object_differs_from_v0_inner_shape() {
181        // Sanity check: the Identity wrapper's to_object includes `$formatVersion`,
182        // whereas IdentityV0's own to_object is a flat map.
183        let v0 = sample_identity_v0();
184        let wrapper: Identity = v0.clone().into();
185        let inner_value = v0.to_object().unwrap();
186        let outer_value = wrapper.to_object().unwrap();
187        assert_ne!(inner_value, outer_value);
188    }
189}