1pub use linera_sdk_derive::StableEnum;
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
10#[doc(hidden)]
15pub mod __private {
16 pub use serde;
17 pub use serde_reflection;
18}
19#[cfg(not(target_arch = "wasm32"))]
20use serde_reflection::TracerConfig;
21use serde_reflection::{
22 json_converter::{
23 DeserializationContext, DeserializationEnvironment, SerializationContext,
24 SerializationEnvironment, SymbolTableEnvironment,
25 },
26 Format, Registry, Samples, Tracer,
27};
28
29#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq)]
32#[serde(rename_all = "UPPERCASE")]
33pub struct Formats {
34 pub registry: Registry,
36 pub operation: Format,
38 pub response: Format,
40 pub message: Format,
42 pub event_value: Format,
44}
45
46pub trait BcsApplication {
48 type Abi;
50
51 fn formats() -> serde_reflection::Result<Formats>;
55
56 #[cfg(not(target_arch = "wasm32"))]
61 fn pruned_formats() -> Result<Formats, PruneError> {
62 let mut formats = Self::formats()?;
63 formats.prune_known_primitives()?;
64 Ok(formats)
65 }
66}
67
68pub trait StableEnumTrace: Sized + Serialize {
81 const STABLE_VARIANTS: &'static [(&'static str, u32)];
83
84 fn trace_all_variants(
87 tracer: &mut Tracer,
88 samples: &Samples,
89 ) -> serde_reflection::Result<Format>;
90}
91
92pub trait StableEnum: StableEnumTrace + Serialize + DeserializeOwned {}
99
100impl<T> StableEnum for T where T: StableEnumTrace + Serialize + DeserializeOwned {}
101
102pub trait TracerExt {
112 fn trace_stable_enum_type<T>(&mut self, samples: &Samples) -> serde_reflection::Result<Format>
116 where
117 T: StableEnumTrace;
118}
119
120impl TracerExt for Tracer {
121 fn trace_stable_enum_type<T>(&mut self, samples: &Samples) -> serde_reflection::Result<Format>
122 where
123 T: StableEnumTrace,
124 {
125 T::trace_all_variants(self, samples)
126 }
127}
128
129fn bcs_to_json(
136 bytes: &[u8],
137 format: &Format,
138 registry: &Registry,
139) -> bcs::Result<serde_json::Value> {
140 let context = DeserializationContext {
141 format: format.clone(),
142 registry,
143 environment: &LineraEnvironment,
144 };
145 bcs::from_bytes_seed(context, bytes)
146}
147
148fn json_to_bcs(
154 value: &serde_json::Value,
155 format: &Format,
156 registry: &Registry,
157) -> bcs::Result<Vec<u8>> {
158 let context = SerializationContext {
159 value,
160 format,
161 registry,
162 environment: &LineraEnvironment,
163 };
164 bcs::to_bytes(&context)
165}
166
167fn primitive_to_json<'de, T, D>(deserializer: D) -> Result<serde_json::Value, String>
170where
171 T: serde::Deserialize<'de> + serde::Serialize,
172 D: serde::Deserializer<'de>,
173{
174 let value = T::deserialize(deserializer).map_err(|error| error.to_string())?;
175 serde_json::to_value(&value).map_err(|error| error.to_string())
176}
177
178fn primitive_from_json<T, S>(value: &serde_json::Value, serializer: S) -> Result<S::Ok, S::Error>
181where
182 T: serde::Serialize + serde::de::DeserializeOwned,
183 S: serde::Serializer,
184{
185 let value: T = T::deserialize(value).map_err(serde::ser::Error::custom)?;
186 value.serialize(serializer)
187}
188
189macro_rules! known_human_readable_primitives {
194 ($($name:literal => $ty:ty),* $(,)?) => {
195 pub const KNOWN_PRIMITIVE_NAMES: &[&str] = &[$($name),*];
200
201 #[derive(Clone, Copy, Debug, Default)]
209 pub struct LineraEnvironment;
210
211 impl SymbolTableEnvironment for LineraEnvironment {}
212
213 impl<'de> DeserializationEnvironment<'de> for LineraEnvironment {
214 fn deserialize<D>(
215 &self,
216 name: String,
217 deserializer: D,
218 ) -> Result<serde_json::Value, String>
219 where
220 D: serde::Deserializer<'de>,
221 {
222 match name.as_str() {
223 $( $name => primitive_to_json::<$ty, D>(deserializer), )*
224 _ => Err(format!("No external definition available for {name}")),
225 }
226 }
227 }
228
229 impl SerializationEnvironment for LineraEnvironment {
230 fn serialize<S>(
231 &self,
232 name: &str,
233 value: &serde_json::Value,
234 serializer: S,
235 ) -> Result<S::Ok, S::Error>
236 where
237 S: serde::Serializer,
238 {
239 match name {
240 $( $name => primitive_from_json::<$ty, S>(value, serializer), )*
241 _ => Err(serde::ser::Error::custom(format!(
242 "No external serializer available for {name}"
243 ))),
244 }
245 }
246 }
247
248 #[cfg(not(target_arch = "wasm32"))]
251 fn expected_primitive_registry() -> serde_reflection::Result<Registry> {
252 let mut tracer = Tracer::new(
253 TracerConfig::default()
254 .record_samples_for_newtype_structs(true)
255 .record_samples_for_tuple_structs(true),
256 );
257 let samples = Samples::new();
258 $( tracer.trace_type::<$ty>(&samples)?; )*
259 tracer.trace_type::<crate::linera_base_types::VmRuntime>(&samples)?;
262 tracer.trace_type::<crate::linera_base_types::BlobType>(&samples)?;
263 tracer.trace_type::<crate::linera_base_types::GenericApplicationId>(&samples)?;
264 tracer.registry()
265 }
266 };
267}
268
269known_human_readable_primitives! {
277 "CryptoHash" => crate::linera_base_types::CryptoHash,
278 "AccountOwner" => crate::linera_base_types::AccountOwner,
279 "Amount" => crate::linera_base_types::Amount,
280 "Epoch" => crate::linera_base_types::Epoch,
281 "BlobId" => crate::linera_base_types::BlobId,
282 "StreamId" => crate::linera_base_types::StreamId,
283 "ModuleId" => crate::linera_base_types::ModuleId,
284 "ApplicationId" => crate::linera_base_types::ApplicationId,
285}
286
287#[cfg(not(target_arch = "wasm32"))]
289#[derive(Debug, thiserror::Error)]
290pub enum PruneError {
291 #[error("failed to compute the canonical primitive formats: {0}")]
293 Reflection(#[from] serde_reflection::Error),
294 #[error(
298 "registry entry for `{name}` does not match the canonical linera-base format; \
299 refusing to prune"
300 )]
301 Mismatch {
302 name: String,
304 },
305}
306
307impl Formats {
308 pub fn decode_operation(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
310 bcs_to_json(bytes, &self.operation, &self.registry)
311 }
312
313 pub fn decode_response(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
315 bcs_to_json(bytes, &self.response, &self.registry)
316 }
317
318 pub fn decode_message(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
320 bcs_to_json(bytes, &self.message, &self.registry)
321 }
322
323 pub fn decode_event_value(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
325 bcs_to_json(bytes, &self.event_value, &self.registry)
326 }
327
328 pub fn encode_operation(&self, value: &serde_json::Value) -> bcs::Result<Vec<u8>> {
331 json_to_bcs(value, &self.operation, &self.registry)
332 }
333
334 pub fn encode_response(&self, value: &serde_json::Value) -> bcs::Result<Vec<u8>> {
337 json_to_bcs(value, &self.response, &self.registry)
338 }
339
340 pub fn encode_message(&self, value: &serde_json::Value) -> bcs::Result<Vec<u8>> {
343 json_to_bcs(value, &self.message, &self.registry)
344 }
345
346 pub fn encode_event_value(&self, value: &serde_json::Value) -> bcs::Result<Vec<u8>> {
349 json_to_bcs(value, &self.event_value, &self.registry)
350 }
351
352 #[cfg(not(target_arch = "wasm32"))]
366 pub fn prune_known_primitives(&mut self) -> Result<(), PruneError> {
367 let expected = expected_primitive_registry()?;
368 for name in KNOWN_PRIMITIVE_NAMES {
370 let (Some(actual), Some(expected_format)) =
371 (self.registry.get(*name), expected.get(*name))
372 else {
373 continue;
374 };
375 if actual != expected_format {
376 return Err(PruneError::Mismatch {
377 name: (*name).to_string(),
378 });
379 }
380 }
381 for name in KNOWN_PRIMITIVE_NAMES {
382 self.registry.remove(*name);
383 }
384 Ok(())
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use serde::{Deserialize, Serialize};
391 use serde_json::json;
392 use serde_reflection::{Samples, Tracer, TracerConfig};
393
394 use super::*;
395
396 fn trace_format<T>() -> (Format, Registry)
397 where
398 T: Serialize + for<'de> Deserialize<'de>,
399 {
400 let mut tracer = Tracer::new(
401 TracerConfig::default()
402 .record_samples_for_newtype_structs(true)
403 .record_samples_for_tuple_structs(true),
404 );
405 let samples = Samples::new();
406 let (format, _) = tracer.trace_type::<T>(&samples).unwrap();
407 let registry = tracer.registry().unwrap();
408 (format, registry)
409 }
410
411 #[test]
412 fn primitive_round_trip() {
413 let (format, registry) = trace_format::<u64>();
414 let bytes = bcs::to_bytes(&42u64).unwrap();
415 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
416 assert_eq!(value, json!(42));
417 }
418
419 #[test]
420 fn struct_round_trip() {
421 #[derive(Serialize, Deserialize)]
422 struct Point {
423 x: i32,
424 y: i32,
425 }
426
427 let (format, registry) = trace_format::<Point>();
428 let bytes = bcs::to_bytes(&Point { x: 10, y: -7 }).unwrap();
429 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
430 assert_eq!(value, json!({ "x": 10, "y": -7 }));
431 }
432
433 #[test]
434 fn enum_unit_and_struct_variants() {
435 #[derive(Serialize, Deserialize)]
436 enum Op {
437 Increment,
438 Set { value: u64 },
439 Add(i64, i64),
440 }
441
442 let (format, registry) = trace_format::<Op>();
443
444 let bytes = bcs::to_bytes(&Op::Increment).unwrap();
445 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
446 assert_eq!(value, json!({ "Increment": null }));
447
448 let bytes = bcs::to_bytes(&Op::Set { value: 99 }).unwrap();
449 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
450 assert_eq!(value, json!({ "Set": { "value": 99 } }));
451
452 let bytes = bcs::to_bytes(&Op::Add(2, 3)).unwrap();
453 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
454 assert_eq!(value, json!({ "Add": [2, 3] }));
455 }
456
457 #[test]
458 fn nested_with_option_and_seq() {
459 #[derive(Serialize, Deserialize)]
460 struct Outer {
461 tag: String,
462 items: Vec<u32>,
463 note: Option<String>,
464 }
465
466 let (format, registry) = trace_format::<Outer>();
467 let value = Outer {
468 tag: "hello".to_string(),
469 items: vec![1, 2, 3],
470 note: None,
471 };
472 let bytes = bcs::to_bytes(&value).unwrap();
473 let json_value = bcs_to_json(&bytes, &format, ®istry).unwrap();
474 assert_eq!(
475 json_value,
476 json!({ "tag": "hello", "items": [1, 2, 3], "note": null })
477 );
478 }
479
480 #[test]
481 fn formats_decode_helpers() {
482 #[derive(Serialize, Deserialize)]
483 enum Operation {
484 Ping,
485 Echo(String),
486 }
487 #[derive(Serialize, Deserialize)]
488 struct Response {
489 ok: bool,
490 }
491
492 let (operation, op_registry) = trace_format::<Operation>();
493 let (response, resp_registry) = trace_format::<Response>();
494
495 let mut registry = op_registry;
497 registry.extend(resp_registry);
498
499 let (message, _) = trace_format::<()>();
500 let (event_value, _) = trace_format::<()>();
501
502 let formats = Formats {
503 registry,
504 operation,
505 response,
506 message,
507 event_value,
508 };
509
510 let op_bytes = bcs::to_bytes(&Operation::Echo("hi".to_string())).unwrap();
511 assert_eq!(
512 formats.decode_operation(&op_bytes).unwrap(),
513 json!({ "Echo": "hi" })
514 );
515
516 let resp_bytes = bcs::to_bytes(&Response { ok: true }).unwrap();
517 assert_eq!(
518 formats.decode_response(&resp_bytes).unwrap(),
519 json!({ "ok": true })
520 );
521
522 let unit_bytes = bcs::to_bytes(&()).unwrap();
524 assert_eq!(formats.decode_message(&unit_bytes).unwrap(), json!(null));
525 assert_eq!(
526 formats.decode_event_value(&unit_bytes).unwrap(),
527 json!(null)
528 );
529
530 assert_eq!(
532 formats.encode_operation(&json!({ "Echo": "hi" })).unwrap(),
533 op_bytes
534 );
535 assert_eq!(
536 formats.encode_response(&json!({ "ok": true })).unwrap(),
537 resp_bytes
538 );
539 assert_eq!(formats.encode_message(&json!(null)).unwrap(), unit_bytes);
540 assert_eq!(
541 formats.encode_event_value(&json!(null)).unwrap(),
542 unit_bytes
543 );
544 }
545
546 #[test]
547 fn malformed_bytes_return_error() {
548 let (format, registry) = trace_format::<u64>();
549 assert!(bcs_to_json(&[1, 2, 3], &format, ®istry).is_err());
551 }
552
553 #[test]
554 fn expected_registry_builds() {
555 let registry = expected_primitive_registry().unwrap();
556 for name in KNOWN_PRIMITIVE_NAMES {
557 assert!(registry.contains_key(*name), "missing {name}");
558 }
559 }
560
561 #[test]
562 fn known_primitives_decode_as_human_readable() {
563 use std::str::FromStr as _;
564
565 use crate::linera_base_types::{AccountOwner, Amount, CryptoHash, ModuleId, VmRuntime};
566
567 #[derive(Serialize, Deserialize)]
568 struct Sample {
569 owner: AccountOwner,
570 amount: Amount,
571 hash: CryptoHash,
572 module: Option<ModuleId>,
573 }
574
575 let hash = CryptoHash::from_str(&"ab".repeat(32)).unwrap();
576 let value = Sample {
577 owner: AccountOwner::Address32(hash),
578 amount: Amount::from_tokens(5),
579 hash,
580 module: Some(ModuleId::new(hash, hash, VmRuntime::Wasm)),
581 };
582
583 let mut tracer = Tracer::new(
587 TracerConfig::default()
588 .record_samples_for_newtype_structs(true)
589 .record_samples_for_tuple_structs(true),
590 );
591 let samples = Samples::new();
592 let (operation, _) = tracer.trace_type::<Sample>(&samples).unwrap();
593 tracer.trace_type::<AccountOwner>(&samples).unwrap();
594 tracer.trace_type::<VmRuntime>(&samples).unwrap();
595 let registry = tracer.registry().unwrap();
596
597 let unit = Format::Unit;
598 let mut formats = Formats {
599 registry,
600 operation,
601 response: unit.clone(),
602 message: unit.clone(),
603 event_value: unit,
604 };
605
606 let bytes = bcs::to_bytes(&value).unwrap();
608 assert!(formats.registry.contains_key("CryptoHash"));
609
610 formats.prune_known_primitives().unwrap();
613 assert!(!formats.registry.contains_key("CryptoHash"));
614 assert!(!formats.registry.contains_key("AccountOwner"));
615
616 let decoded = formats.decode_operation(&bytes).unwrap();
617 let expected = serde_json::to_value(&value).unwrap();
618 assert_eq!(decoded, expected);
619 assert_eq!(decoded["hash"], json!("ab".repeat(32)));
621 assert_eq!(decoded["amount"], json!(value.amount.to_string()));
622
623 let reencoded = formats.encode_operation(&decoded).unwrap();
626 assert_eq!(reencoded, bytes);
627 }
628
629 #[test]
630 fn prune_rejects_colliding_format() {
631 use serde_reflection::ContainerFormat;
632
633 let mut registry = Registry::new();
635 registry.insert(
636 "CryptoHash".to_string(),
637 ContainerFormat::NewTypeStruct(Box::new(Format::U64)),
638 );
639 let unit = Format::Unit;
640 let mut formats = Formats {
641 registry,
642 operation: Format::TypeName("CryptoHash".to_string()),
643 response: unit.clone(),
644 message: unit.clone(),
645 event_value: unit,
646 };
647
648 let error = formats.prune_known_primitives().unwrap_err();
649 assert!(matches!(error, PruneError::Mismatch { name } if name == "CryptoHash"));
650 assert!(formats.registry.contains_key("CryptoHash"));
652 }
653
654 #[test]
655 fn stable_enum_round_trip() {
656 use linera_sdk_derive::StableEnumInCrate;
657 use serde_reflection::ContainerFormat;
658
659 #[derive(Debug, PartialEq, StableEnumInCrate)]
660 enum Op {
661 Increment,
662 Set { value: u64 },
663 Add(i64, i64),
664 Echo(String),
665 }
666
667 for c in [
669 Op::Increment,
670 Op::Set { value: 99 },
671 Op::Add(2, 3),
672 Op::Echo("hi".into()),
673 ] {
674 let bytes = bcs::to_bytes(&c).unwrap();
675 assert!(bytes.len() >= 4, "tag must be at least 4 bytes: {c:?}");
676 assert_eq!(bytes[0] & 0x80, 0x80, "byte 0 has continuation");
678 assert_eq!(bytes[1] & 0x80, 0x80, "byte 1 has continuation");
679 assert_eq!(bytes[2] & 0x80, 0x80, "byte 2 has continuation");
680 assert_eq!(bytes[3] & 0x80, 0x00, "byte 3 terminates");
681
682 let back: Op = bcs::from_bytes(&bytes).unwrap();
683 assert_eq!(back, c);
684 }
685
686 let bogus = bcs::to_bytes(&0u32).unwrap();
688 assert!(bcs::from_bytes::<Op>(&bogus).is_err());
689
690 for &(name, tag) in <Op as StableEnumTrace>::STABLE_VARIANTS {
692 let sample = match name {
693 "Increment" => bcs::to_bytes(&Op::Increment).unwrap(),
694 "Set" => bcs::to_bytes(&Op::Set { value: 0 }).unwrap(),
695 "Add" => bcs::to_bytes(&Op::Add(0, 0)).unwrap(),
696 "Echo" => bcs::to_bytes(&Op::Echo(String::new())).unwrap(),
697 _ => unreachable!(),
698 };
699 let decoded = decode_uleb_u32(&sample[..4]);
700 assert_eq!(decoded, tag, "variant {name} tag mismatch");
701 }
702
703 let mut tracer = Tracer::new(TracerConfig::default());
705 let samples = Samples::new();
706 let format = tracer.trace_stable_enum_type::<Op>(&samples).unwrap();
707 let registry = tracer.registry().unwrap();
708 match registry.get("Op").unwrap() {
709 ContainerFormat::Enum(variants) => {
710 let mut keys: Vec<_> = variants.keys().copied().collect();
711 keys.sort();
712 let mut expected: Vec<u32> = <Op as StableEnumTrace>::STABLE_VARIANTS
713 .iter()
714 .map(|(_, t)| *t)
715 .collect();
716 expected.sort();
717 assert_eq!(keys, expected);
718 }
719 _ => panic!("expected enum"),
720 }
721
722 let bytes = bcs::to_bytes(&Op::Set { value: 99 }).unwrap();
724 let value = bcs_to_json(&bytes, &format, ®istry).unwrap();
725 assert_eq!(value, json!({ "Set": { "value": 99 } }));
726 }
727
728 fn decode_uleb_u32(bytes: &[u8]) -> u32 {
730 let b0 = (bytes[0] & 0x7f) as u32;
731 let b1 = (bytes[1] & 0x7f) as u32;
732 let b2 = (bytes[2] & 0x7f) as u32;
733 let b3 = bytes[3] as u32;
734 b0 | (b1 << 7) | (b2 << 14) | (b3 << 21)
735 }
736}