1use crate::serialization::PlatformDeserializable;
2use crate::state_transition::StateTransition;
3use crate::ProtocolError;
4
5impl StateTransition {
6 pub fn deserialize_many(raw_state_transitions: &[Vec<u8>]) -> Result<Vec<Self>, ProtocolError> {
7 raw_state_transitions
8 .iter()
9 .map(|raw_state_transition| Self::deserialize_from_bytes(raw_state_transition))
10 .collect()
11 }
12}
13
14#[cfg(test)]
15mod tests {
16 use hex::ToHex;
17 use base64::engine::general_purpose::STANDARD;
18 use base64::Engine;
19 use platform_value::string_encoding::Encoding;
20 use crate::bls::native_bls::NativeBlsModule;
21 use crate::data_contract::accessors::v0::DataContractV0Getters;
22 use crate::identity::state_transition::AssetLockProved;
23 use crate::identity::accessors::IdentityGettersV0;
24 use crate::identity::core_script::CoreScript;
25 use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
26 use crate::identity::Identity;
27 use crate::prelude::AssetLockProof;
28 use crate::serialization::PlatformMessageSignable;
29 use crate::serialization::Signable;
30 use crate::serialization::{PlatformDeserializable, PlatformSerializable};
31 use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
32 use crate::state_transition::data_contract_update_transition::{
33 DataContractUpdateTransition, DataContractUpdateTransitionV0,
34 };
35 use crate::state_transition::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType;
36 use crate::state_transition::batch_transition::{
37 BatchTransition, BatchTransitionV1,
38 };
39 use crate::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0;
40 use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
41 use crate::state_transition::identity_create_transition::IdentityCreateTransition;
42 use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
43 use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
44 use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
45 use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Setters;
46 use crate::state_transition::StateTransition;
47 use crate::tests::fixtures::{
48 get_data_contract_fixture, get_batched_transitions_fixture,
49 get_extended_documents_fixture_with_owner_id_from_contract,
50 raw_instant_asset_lock_proof_fixture,
51 };
52 use crate::version::PlatformVersion;
53 use crate::withdrawal::Pooling;
54 use crate::ProtocolError;
55 use platform_version::version::LATEST_PLATFORM_VERSION;
56 use platform_version::TryIntoPlatformVersioned;
57 use rand::rngs::StdRng;
58 use rand::SeedableRng;
59 use std::collections::BTreeMap;
60
61 #[test]
62 fn should_build_identity_create_transition_from_mainnet_asset_lock_transaction() {
65 const EXPECTED_STATE_TRANSITION_HASH: &str =
66 "6CDCC15AC4EC68DBB414EE0DA692DFE363A996A0F285423BEFC3A29F87948A0D";
67 const RAW_TRANSACTION_BASE64: &str = "AwADAAAAAAAAACEDeLqSkwVyfHvYThgegiZUvPu0+dU4kyd3PJKigGLC1spBH+wrzjjA/ZGZdQmUzpQyOiC3GyP2eBp8ga9cNlnIOkptMzAtfXPA2daH3xTqt25JQ+fZ6UKB3ypzTK3fOXaAATgAAQAAAgAAIQPoVeBC6iyS0jFV0Dly5WV0SEl6uDciQqqi4EATeUJutEEfAd6+/HbUM4FLS6+lNc6AH8vaD9lViiYny4GPsl/AlBxdr0WjJxxU/B0cNVH8kRMo+W6a+1iSN+NZS7MTyzmTHwACAAEDAAAhA6S0TKbm1a/xyrYMG+Y2odspJ1roL1TcoK9h552yE1VCQSA+KpHiQ8lDBseXI/1ZCMxEvu0qopdjDojaQ4FzaZMgUGfPBeXSfMbQGksLMNseKRBLob/g0DHJWqZAxSDOuAwZAfwAIQxGIDIHY9cjWxS0tJupeJuKMZwzFKmLxkU3NmqFTcFscilVAABBH9R3vwbfA3q5XJG4m4z87OAA1uG8wup915wGGKAxdEObXPSqIvPBWrHlGTf/Uymanc2cDH1uKdsniJyoORwauPBIqlz61/Kf9HDnubX4GoHRYdnb4WzE+Tdh+L39a2dN2A==";
68 const EXPECTED_IDENTITY_ADDRESS: &str = "5tf2QotaJw8kRNpQEa8TXtRQ6FLxwUrY4Mtee2JF2nco";
69 const EXPECTED_CORE_TRANSACTION_HASH: &str =
70 "5529726cc14d856a363745c68ba914339c318a9b78a99bb4b4145b23d7630732";
71 let raw_transaction = STANDARD
72 .decode(RAW_TRANSACTION_BASE64)
73 .expect("base64 transaction should decode");
74 let state_transition = StateTransition::deserialize_from_bytes(&raw_transaction)
75 .expect("State transition deserializes correctly");
76
77 assert_eq!(
78 &state_transition
79 .transaction_id()
80 .expect("expected transaction id")
81 .encode_hex_upper::<String>(),
82 EXPECTED_STATE_TRANSITION_HASH
83 );
84
85 let StateTransition::IdentityCreate(identity_create_transition) = state_transition else {
86 panic!("expected identity create transition");
87 };
88
89 let asset_lock_proof = identity_create_transition.asset_lock_proof();
92 let AssetLockProof::Chain(chain_proof) = asset_lock_proof else {
93 panic!("expected chain asset lock proof for this mainnet transaction");
94 };
95
96 assert_eq!(
98 &chain_proof.out_point.txid.to_string().to_lowercase(),
99 EXPECTED_CORE_TRANSACTION_HASH
100 );
101
102 let identity_address = identity_create_transition
103 .identity_id()
104 .to_string(Encoding::Base58);
105
106 assert_eq!(identity_address, EXPECTED_IDENTITY_ADDRESS);
107 }
108
109 #[test]
110 #[cfg(feature = "random-identities")]
111 fn identity_create_transition_ser_de() {
112 let platform_version = LATEST_PLATFORM_VERSION;
113 let identity = Identity::random_identity(5, Some(5), platform_version)
114 .expect("expected a random identity");
115 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
116
117 let identity_create_transition = IdentityCreateTransition::V0(
118 IdentityCreateTransitionV0::try_from_identity(
119 &identity,
120 AssetLockProof::Instant(asset_lock_proof),
121 platform_version,
122 )
123 .expect("expected to make an identity create transition"),
124 );
125
126 let state_transition: StateTransition = identity_create_transition.into();
127 let bytes = state_transition
128 .serialize_to_bytes()
129 .expect("expected to serialize");
130 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
131 .expect("expected to deserialize state transition");
132 assert_eq!(state_transition, recovered_state_transition);
133 }
134
135 #[test]
136 #[cfg(feature = "random-identities")]
137 fn identity_topup_transition_ser_de() {
138 let platform_version = PlatformVersion::latest();
139 let identity = Identity::random_identity(5, Some(5), platform_version)
140 .expect("expected a random identity");
141 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
142
143 let identity_topup_transition = IdentityTopUpTransitionV0 {
144 asset_lock_proof: AssetLockProof::Instant(asset_lock_proof),
145 identity_id: identity.id(),
146 user_fee_increase: 0,
147 signature: [1u8; 65].to_vec().into(),
148 };
149 let state_transition: StateTransition = identity_topup_transition.into();
150 let bytes = state_transition
151 .serialize_to_bytes()
152 .expect("expected to serialize");
153 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
154 .expect("expected to deserialize state transition");
155 assert_eq!(state_transition, recovered_state_transition);
156 }
157
158 #[test]
159 #[cfg(feature = "random-identities")]
160 fn identity_update_transition_add_keys_ser_de() {
161 let mut rng = StdRng::seed_from_u64(5);
162 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
163 Identity::random_identity_with_main_keys_with_private_key(
164 5,
165 &mut rng,
166 LATEST_PLATFORM_VERSION,
167 )
168 .expect("expected to get identity");
169 let bls = NativeBlsModule;
170 let add_public_keys_in_creation = identity
171 .public_keys()
172 .values()
173 .map(|public_key| public_key.into())
174 .collect();
175 let mut identity_update_transition = IdentityUpdateTransitionV0 {
176 signature: Default::default(),
177 signature_public_key_id: 0,
178 identity_id: identity.id(),
179 revision: 1,
180 nonce: 1,
181 add_public_keys: add_public_keys_in_creation,
182 disable_public_keys: vec![],
183 user_fee_increase: 0,
184 };
185
186 let key_signable_bytes = identity_update_transition
187 .signable_bytes()
188 .expect("expected to get signable bytes");
189
190 identity_update_transition
191 .add_public_keys
192 .iter_mut()
193 .zip(identity.public_keys().clone().into_values())
194 .try_for_each(|(public_key_with_witness, public_key)| {
195 if public_key.key_type().is_unique_key_type() {
196 let private_key = keys
197 .get(&public_key)
198 .expect("expected to have the private key");
199 let signature = key_signable_bytes
200 .as_slice()
201 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
202 .into();
203 public_key_with_witness.set_signature(signature);
204 }
205
206 Ok::<(), ProtocolError>(())
207 })
208 .expect("expected to update keys");
209
210 let (public_key, private_key) = keys.pop_first().unwrap();
211
212 let mut state_transition: StateTransition = identity_update_transition.into();
213
214 state_transition
215 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
216 .expect("expected to sign IdentityUpdateTransition");
217 let bytes = state_transition
218 .serialize_to_bytes()
219 .expect("expected to serialize");
220 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
221 .expect("expected to deserialize state transition");
222 assert_eq!(state_transition, recovered_state_transition);
223 }
224
225 #[test]
226 #[cfg(feature = "state-transition-signing")]
227 fn identity_update_transition_disable_keys_ser_de() {
228 let mut rng = StdRng::seed_from_u64(5);
229 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
230 Identity::random_identity_with_main_keys_with_private_key(
231 5,
232 &mut rng,
233 LATEST_PLATFORM_VERSION,
234 )
235 .expect("expected to get identity");
236 let bls = NativeBlsModule;
237 let add_public_keys_in_creation = identity
238 .public_keys()
239 .values()
240 .map(|public_key| public_key.into())
241 .collect();
242 let mut identity_update_transition = IdentityUpdateTransitionV0 {
243 signature: Default::default(),
244 signature_public_key_id: 0,
245 identity_id: identity.id(),
246 revision: 1,
247 nonce: 1,
248 add_public_keys: add_public_keys_in_creation,
249 disable_public_keys: vec![3, 4, 5],
250 user_fee_increase: 0,
251 };
252
253 let key_signable_bytes = identity_update_transition
254 .signable_bytes()
255 .expect("expected to get signable bytes");
256
257 identity_update_transition
258 .add_public_keys
259 .iter_mut()
260 .zip(identity.public_keys().clone().into_values())
261 .try_for_each(|(public_key_with_witness, public_key)| {
262 if public_key.key_type().is_unique_key_type() {
263 let private_key = keys
264 .get(&public_key)
265 .expect("expected to have the private key");
266 let signature = key_signable_bytes
267 .as_slice()
268 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
269 .into();
270 public_key_with_witness.set_signature(signature);
271 }
272
273 Ok::<(), ProtocolError>(())
274 })
275 .expect("expected to update keys");
276
277 let (public_key, private_key) = keys.pop_first().unwrap();
278
279 let mut state_transition: StateTransition = identity_update_transition.into();
280
281 state_transition
282 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
283 .expect("expected to sign IdentityUpdateTransition");
284 let bytes = state_transition
285 .serialize_to_bytes()
286 .expect("expected to serialize");
287 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
288 .expect("expected to deserialize state transition");
289 assert_eq!(state_transition, recovered_state_transition);
290 }
291
292 #[test]
293 #[cfg(feature = "random-identities")]
294 fn identity_credit_withdrawal_transition_ser_de() {
295 let platform_version = PlatformVersion::latest();
296 let identity = Identity::random_identity(5, Some(5), platform_version)
297 .expect("expected a random identity");
298 let identity_credit_withdrawal_transition = IdentityCreditWithdrawalTransitionV0 {
299 identity_id: identity.id(),
300 amount: 5000000,
301 core_fee_per_byte: 34,
302 pooling: Pooling::Standard,
303 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
304 nonce: 1,
305 user_fee_increase: 0,
306 signature_public_key_id: 0,
307 signature: [1u8; 65].to_vec().into(),
308 };
309 let state_transition: StateTransition = identity_credit_withdrawal_transition.into();
310 let bytes = state_transition
311 .serialize_to_bytes()
312 .expect("expected to serialize");
313 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
314 .expect("expected to deserialize state transition");
315 assert_eq!(state_transition, recovered_state_transition);
316 }
317
318 #[test]
319 #[cfg(feature = "random-identities")]
320 fn data_contract_create_ser_de() {
321 let platform_version = LATEST_PLATFORM_VERSION;
322 let identity = Identity::random_identity(5, Some(5), platform_version)
323 .expect("expected a random identity");
324 let created_data_contract = get_data_contract_fixture(
325 Some(identity.id()),
326 0,
327 LATEST_PLATFORM_VERSION.protocol_version,
328 );
329 let data_contract_create_transition: DataContractCreateTransition = created_data_contract
330 .try_into_platform_versioned(platform_version)
331 .expect("expected to transform into a DataContractCreateTransition");
332 let state_transition: StateTransition = data_contract_create_transition.into();
333 let bytes = state_transition
334 .serialize_to_bytes()
335 .expect("expected to serialize");
336 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
337 .expect("expected to deserialize state transition");
338 assert_eq!(state_transition, recovered_state_transition);
339 }
340
341 #[test]
342 #[cfg(feature = "random-identities")]
343 fn data_contract_update_ser_de() {
344 let platform_version = PlatformVersion::latest();
345 let identity = Identity::random_identity(5, Some(5), platform_version)
346 .expect("expected a random identity");
347 let created_data_contract =
348 get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version);
349 let data_contract_update_transition =
350 DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
351 identity_contract_nonce: 1,
352 data_contract: created_data_contract
353 .data_contract_owned()
354 .try_into_platform_versioned(platform_version)
355 .expect("expected a data contract"),
356 user_fee_increase: 0,
357 signature_public_key_id: 0,
358 signature: [1u8; 65].to_vec().into(),
359 });
360 let state_transition: StateTransition = data_contract_update_transition.into();
361 let bytes = state_transition
362 .serialize_to_bytes()
363 .expect("expected to serialize");
364 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
365 .expect("expected to deserialize state transition");
366 assert_eq!(state_transition, recovered_state_transition);
367 }
368
369 #[test]
370 fn document_batch_transition_10_created_documents_ser_de() {
371 let platform_version = PlatformVersion::latest();
372
373 let mut nonces = BTreeMap::new();
374 let data_contract = get_data_contract_fixture(None, 0, platform_version.protocol_version)
375 .data_contract_owned();
376 let documents = get_extended_documents_fixture_with_owner_id_from_contract(
377 &data_contract,
378 platform_version.protocol_version,
379 )
380 .unwrap();
381 let documents = documents
382 .iter()
383 .map(|extended_document| {
384 let document = extended_document.document().clone();
385 let data_contract = extended_document.data_contract();
386 (
387 document,
388 data_contract
389 .document_type_for_name(extended_document.document_type_name())
390 .unwrap(),
391 *extended_document.entropy(),
392 None,
393 )
394 })
395 .collect::<Vec<_>>();
396 let transitions = get_batched_transitions_fixture(
397 [(DocumentTransitionActionType::Create, documents)],
398 &mut nonces,
399 );
400 let documents_batch_transition: BatchTransition = BatchTransitionV1 {
401 owner_id: data_contract.owner_id(),
402 transitions,
403 ..Default::default()
404 }
405 .into();
406 let state_transition: StateTransition = documents_batch_transition.into();
407 let bytes = state_transition
408 .serialize_to_bytes()
409 .expect("expected to serialize");
410 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
411 .expect("expected to deserialize state transition");
412 assert_eq!(state_transition, recovered_state_transition);
413 }
414
415 #[test]
416 fn deserialize_empty_bytes_should_fail() {
417 let result = StateTransition::deserialize_from_bytes(&[]);
418 assert!(
419 result.is_err(),
420 "deserialization of empty bytes should fail"
421 );
422 }
423
424 #[test]
425 fn deserialize_single_byte_should_fail() {
426 let result = StateTransition::deserialize_from_bytes(&[0xFF]);
427 assert!(
428 result.is_err(),
429 "deserialization of a single 0xFF byte should fail"
430 );
431 }
432
433 #[test]
434 #[cfg(feature = "random-identities")]
435 fn deserialize_truncated_bytes_should_fail() {
436 let platform_version = PlatformVersion::latest();
437 let identity = Identity::random_identity(5, Some(5), platform_version)
438 .expect("expected a random identity");
439 let transition = IdentityCreditWithdrawalTransitionV0 {
440 identity_id: identity.id(),
441 amount: 5000000,
442 core_fee_per_byte: 34,
443 pooling: Pooling::Standard,
444 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
445 nonce: 1,
446 user_fee_increase: 0,
447 signature_public_key_id: 0,
448 signature: [1u8; 65].to_vec().into(),
449 };
450 let state_transition: StateTransition = transition.into();
451 let bytes = state_transition
452 .serialize_to_bytes()
453 .expect("expected to serialize");
454
455 let half = &bytes[..bytes.len() / 2];
457 assert!(
458 StateTransition::deserialize_from_bytes(half).is_err(),
459 "deserialization of truncated-to-half bytes should fail"
460 );
461
462 let minus_one = &bytes[..bytes.len() - 1];
464 assert!(
465 StateTransition::deserialize_from_bytes(minus_one).is_err(),
466 "deserialization of bytes missing last byte should fail"
467 );
468
469 let first_only = &bytes[..1];
471 assert!(
472 StateTransition::deserialize_from_bytes(first_only).is_err(),
473 "deserialization of only the first byte should fail"
474 );
475 }
476
477 #[test]
478 #[cfg(feature = "random-identities")]
479 fn deserialize_corrupted_bytes_should_not_panic() {
480 let platform_version = PlatformVersion::latest();
481 let identity = Identity::random_identity(5, Some(5), platform_version)
482 .expect("expected a random identity");
483 let transition = IdentityCreditWithdrawalTransitionV0 {
484 identity_id: identity.id(),
485 amount: 5000000,
486 core_fee_per_byte: 34,
487 pooling: Pooling::Standard,
488 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
489 nonce: 1,
490 user_fee_increase: 0,
491 signature_public_key_id: 0,
492 signature: [1u8; 65].to_vec().into(),
493 };
494 let state_transition: StateTransition = transition.into();
495 let mut bytes = state_transition
496 .serialize_to_bytes()
497 .expect("expected to serialize");
498
499 let mid = bytes.len() / 2;
501 bytes[mid] ^= 0xFF;
502
503 let result = StateTransition::deserialize_from_bytes(&bytes);
505 if let Ok(recovered) = result {
506 assert_ne!(
507 state_transition, recovered,
508 "corrupted bytes should not deserialize to the original value"
509 );
510 }
511 }
512
513 fn craft_oversized_vec_payload(fake_len: u64) -> Vec<u8> {
519 let config = bincode::config::standard()
520 .with_big_endian()
521 .with_no_limit();
522 let mut buf = Vec::new();
524 buf.extend_from_slice(&bincode::encode_to_vec(5u32, config).unwrap());
526 buf.push(0);
528 buf.extend_from_slice(&[0u8; 32]);
530 buf.extend_from_slice(&bincode::encode_to_vec(1000u64, config).unwrap());
532 buf.extend_from_slice(&bincode::encode_to_vec(1u32, config).unwrap());
534 buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
536 buf.extend_from_slice(&bincode::encode_to_vec(fake_len, config).unwrap());
538 buf
540 }
541
542 #[test]
543 fn deserialize_crafted_huge_vec_length_does_not_oom() {
544 let payload = craft_oversized_vec_payload(8_000_000_000);
547 let result = StateTransition::deserialize_from_bytes(&payload);
548 assert!(
550 result.is_err(),
551 "crafted payload with 8GB vec length must be rejected, not cause OOM"
552 );
553 }
554
555 #[test]
556 fn deserialize_crafted_vec_exceeding_limit_is_rejected() {
557 let payload = craft_oversized_vec_payload(200_000);
563 let result = StateTransition::deserialize_from_bytes(&payload);
564 assert!(
565 result.is_err(),
566 "Vec length exceeding byte budget must be rejected"
567 );
568 }
569
570 #[test]
571 fn deserialize_no_limit_does_not_enforce_byte_budget() {
572 let payload = craft_oversized_vec_payload(200_000);
576 let result = StateTransition::deserialize_from_bytes_no_limit(&payload);
577 assert!(result.is_err());
578 match result.unwrap_err() {
579 ProtocolError::MaxEncodedBytesReachedError { .. } => {
580 panic!("deserialize_from_bytes_no_limit should NOT enforce byte budget");
581 }
582 _ => {} }
584 }
585
586 #[test]
587 fn deserialize_many_empty_list() {
588 let result = StateTransition::deserialize_many(&[]);
589 assert_eq!(result.unwrap(), vec![]);
590 }
591
592 #[test]
593 fn deserialize_many_with_invalid_entry() {
594 let result = StateTransition::deserialize_many(&[vec![0xFF]]);
595 assert!(
596 result.is_err(),
597 "deserialize_many with invalid entry should fail"
598 );
599 }
600
601 #[test]
602 #[cfg(feature = "random-identities")]
603 fn deserialize_many_with_valid_entries() {
604 let platform_version = PlatformVersion::latest();
605 let identity = Identity::random_identity(5, Some(5), platform_version)
606 .expect("expected a random identity");
607
608 let make_transition = |amount: u64, nonce: u64| -> StateTransition {
609 let t = IdentityCreditWithdrawalTransitionV0 {
610 identity_id: identity.id(),
611 amount,
612 core_fee_per_byte: 34,
613 pooling: Pooling::Standard,
614 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
615 nonce,
616 user_fee_increase: 0,
617 signature_public_key_id: 0,
618 signature: [1u8; 65].to_vec().into(),
619 };
620 t.into()
621 };
622
623 let st1 = make_transition(1000000, 1);
624 let st2 = make_transition(2000000, 2);
625 let st3 = make_transition(3000000, 3);
626
627 let raw: Vec<Vec<u8>> = vec![
628 st1.serialize_to_bytes().unwrap(),
629 st2.serialize_to_bytes().unwrap(),
630 st3.serialize_to_bytes().unwrap(),
631 ];
632
633 let recovered = StateTransition::deserialize_many(&raw).expect("should deserialize all");
634 assert_eq!(recovered.len(), 3);
635 assert_eq!(recovered[0], st1);
636 assert_eq!(recovered[1], st2);
637 assert_eq!(recovered[2], st3);
638 }
639}