Skip to main content

dpp/identity/conversion/platform_value/
mod.rs

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