Skip to main content

linera_sdk/
formats.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Support the declaration of the binary formats used by an application.
5
6/// Re-exports the `#[derive(StableEnum)]` macro for stable-tagged enums.
7pub use linera_sdk_derive::StableEnum;
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
10/// Private re-exports of crates referenced by the macros in `linera-sdk-derive`.
11/// Lets downstream crates use the macros without taking direct dependencies on
12/// the crates being re-exported. Not part of the public API; do not use
13/// directly.
14#[doc(hidden)]
15pub mod __private {
16    pub use serde;
17    pub use serde_reflection;
18}
19use serde_reflection::{
20    json_converter::{DeserializationContext, EmptyEnvironment},
21    Format, Registry, Samples, Tracer,
22};
23
24/// The serde formats used by an application. The exact serde encoding in use must be
25/// known separately.
26#[derive(Serialize, Deserialize, Debug, Eq, Clone, PartialEq)]
27#[serde(rename_all = "UPPERCASE")]
28pub struct Formats {
29    /// The registry of container definitions.
30    pub registry: Registry,
31    /// The format of operations.
32    pub operation: Format,
33    /// The format of operation responses.
34    pub response: Format,
35    /// The format of messages.
36    pub message: Format,
37    /// The format of events.
38    pub event_value: Format,
39}
40
41/// An application using BCS as binary encoding.
42pub trait BcsApplication {
43    /// Link the public Abi of application for good measure.
44    type Abi;
45
46    /// Returns the serde formats for this application's ABI types.
47    fn formats() -> serde_reflection::Result<Formats>;
48}
49
50/// Companion trait of [`StableEnum`]: exposes each variant's stable tag and
51/// provides the implementation backing
52/// [`TracerExt::trace_stable_enum_type`]. Implemented by the
53/// `#[derive(StableEnum)]` macro.
54///
55/// The derive auto-generates `trace_all_variants` by calling
56/// [`Tracer::trace_type_once`] for each field type to obtain a sample value,
57/// then [`Tracer::trace_value`] to record the variant. As a result, every
58/// field type must implement [`serde::de::DeserializeOwned`] (or otherwise be
59/// traceable by [`Tracer::trace_type_once`]). Nested `StableEnum` fields are
60/// not supported automatically — pre-trace them with
61/// `trace_stable_enum_type::<NestedEnum>` first.
62pub trait StableEnumTrace: Sized + Serialize {
63    /// The `(variant_name, variant_tag)` pairs in declaration order.
64    const STABLE_VARIANTS: &'static [(&'static str, u32)];
65
66    /// Trace each variant of `Self` into `tracer`'s registry. The default
67    /// derive implementation is sufficient for most cases.
68    fn trace_all_variants(
69        tracer: &mut Tracer,
70        samples: &Samples,
71    ) -> serde_reflection::Result<Format>;
72}
73
74/// Marker trait for enums whose variant tags on the wire are derived from
75/// `Keccak-256(variant_name)`. Apply with `#[derive(StableEnum)]`.
76///
77/// The blanket impl below covers every type for which all three of
78/// [`Serialize`], [`DeserializeOwned`], and [`StableEnumTrace`] are
79/// implemented — `#[derive(StableEnum)]` emits all three at once.
80pub trait StableEnum: StableEnumTrace + Serialize + DeserializeOwned {}
81
82impl<T> StableEnum for T where T: StableEnumTrace + Serialize + DeserializeOwned {}
83
84/// Extension methods on [`Tracer`] for tracing enums whose variant tags are
85/// not contiguous starting at zero.
86///
87/// The standard [`Tracer::trace_type`] discovers an enum's variants by probing
88/// `0, 1, 2, …` until each `u32` index has been seen. With Keccak-derived
89/// stable tags those indices are not consecutive (they live in `[2^27, 2^28)`),
90/// so probing never terminates. This trait delegates to the enum's
91/// [`StableEnumTrace`] impl, which drives tracing variant-by-variant via the
92/// enum's [`Serialize`] impl.
93pub trait TracerExt {
94    /// Trace every variant of a stable-tagged enum, returning the enum's
95    /// [`Format`] for use in [`Formats::operation`], [`Formats::response`],
96    /// etc.
97    fn trace_stable_enum_type<T>(&mut self, samples: &Samples) -> serde_reflection::Result<Format>
98    where
99        T: StableEnumTrace;
100}
101
102impl TracerExt for Tracer {
103    fn trace_stable_enum_type<T>(&mut self, samples: &Samples) -> serde_reflection::Result<Format>
104    where
105        T: StableEnumTrace,
106    {
107        T::trace_all_variants(self, samples)
108    }
109}
110
111/// Decode BCS-serialized `bytes` into a [`serde_json::Value`], guided by `format`
112/// and the container `registry`.
113pub fn bcs_to_json(
114    bytes: &[u8],
115    format: &Format,
116    registry: &Registry,
117) -> bcs::Result<serde_json::Value> {
118    let context = DeserializationContext {
119        format: format.clone(),
120        registry,
121        environment: &EmptyEnvironment,
122    };
123    bcs::from_bytes_seed(context, bytes)
124}
125
126impl Formats {
127    /// Decode BCS-encoded operation bytes into a JSON value.
128    pub fn decode_operation(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
129        bcs_to_json(bytes, &self.operation, &self.registry)
130    }
131
132    /// Decode BCS-encoded operation response bytes into a JSON value.
133    pub fn decode_response(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
134        bcs_to_json(bytes, &self.response, &self.registry)
135    }
136
137    /// Decode BCS-encoded message bytes into a JSON value.
138    pub fn decode_message(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
139        bcs_to_json(bytes, &self.message, &self.registry)
140    }
141
142    /// Decode BCS-encoded event value bytes into a JSON value.
143    pub fn decode_event_value(&self, bytes: &[u8]) -> bcs::Result<serde_json::Value> {
144        bcs_to_json(bytes, &self.event_value, &self.registry)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use serde::{Deserialize, Serialize};
151    use serde_json::json;
152    use serde_reflection::{ContainerFormat, Samples, Tracer, TracerConfig};
153
154    use super::*;
155
156    fn trace_format<T>() -> (Format, Registry)
157    where
158        T: Serialize + for<'de> Deserialize<'de>,
159    {
160        let mut tracer = Tracer::new(
161            TracerConfig::default()
162                .record_samples_for_newtype_structs(true)
163                .record_samples_for_tuple_structs(true),
164        );
165        let samples = Samples::new();
166        let (format, _) = tracer.trace_type::<T>(&samples).unwrap();
167        let registry = tracer.registry().unwrap();
168        (format, registry)
169    }
170
171    #[test]
172    fn primitive_round_trip() {
173        let (format, registry) = trace_format::<u64>();
174        let bytes = bcs::to_bytes(&42u64).unwrap();
175        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
176        assert_eq!(value, json!(42));
177    }
178
179    #[test]
180    fn struct_round_trip() {
181        #[derive(Serialize, Deserialize)]
182        struct Point {
183            x: i32,
184            y: i32,
185        }
186
187        let (format, registry) = trace_format::<Point>();
188        let bytes = bcs::to_bytes(&Point { x: 10, y: -7 }).unwrap();
189        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
190        assert_eq!(value, json!({ "x": 10, "y": -7 }));
191    }
192
193    #[test]
194    fn enum_unit_and_struct_variants() {
195        #[derive(Serialize, Deserialize)]
196        enum Op {
197            Increment,
198            Set { value: u64 },
199            Add(i64, i64),
200        }
201
202        let (format, registry) = trace_format::<Op>();
203
204        let bytes = bcs::to_bytes(&Op::Increment).unwrap();
205        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
206        assert_eq!(value, json!({ "Increment": null }));
207
208        let bytes = bcs::to_bytes(&Op::Set { value: 99 }).unwrap();
209        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
210        assert_eq!(value, json!({ "Set": { "value": 99 } }));
211
212        let bytes = bcs::to_bytes(&Op::Add(2, 3)).unwrap();
213        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
214        assert_eq!(value, json!({ "Add": [2, 3] }));
215    }
216
217    #[test]
218    fn nested_with_option_and_seq() {
219        #[derive(Serialize, Deserialize)]
220        struct Outer {
221            tag: String,
222            items: Vec<u32>,
223            note: Option<String>,
224        }
225
226        let (format, registry) = trace_format::<Outer>();
227        let value = Outer {
228            tag: "hello".to_string(),
229            items: vec![1, 2, 3],
230            note: None,
231        };
232        let bytes = bcs::to_bytes(&value).unwrap();
233        let json_value = bcs_to_json(&bytes, &format, &registry).unwrap();
234        assert_eq!(
235            json_value,
236            json!({ "tag": "hello", "items": [1, 2, 3], "note": null })
237        );
238    }
239
240    #[test]
241    fn formats_decode_helpers() {
242        #[derive(Serialize, Deserialize)]
243        enum Operation {
244            Ping,
245            Echo(String),
246        }
247        #[derive(Serialize, Deserialize)]
248        struct Response {
249            ok: bool,
250        }
251
252        let (operation, op_registry) = trace_format::<Operation>();
253        let (response, resp_registry) = trace_format::<Response>();
254
255        let mut registry = op_registry;
256        registry.extend(resp_registry);
257
258        let (message, _) = trace_format::<()>();
259        let (event_value, _) = trace_format::<()>();
260
261        let formats = Formats {
262            registry,
263            operation,
264            response,
265            message,
266            event_value,
267        };
268
269        let op_bytes = bcs::to_bytes(&Operation::Echo("hi".to_string())).unwrap();
270        assert_eq!(
271            formats.decode_operation(&op_bytes).unwrap(),
272            json!({ "Echo": "hi" })
273        );
274
275        let resp_bytes = bcs::to_bytes(&Response { ok: true }).unwrap();
276        assert_eq!(
277            formats.decode_response(&resp_bytes).unwrap(),
278            json!({ "ok": true })
279        );
280
281        let unit_bytes = bcs::to_bytes(&()).unwrap();
282        assert_eq!(formats.decode_message(&unit_bytes).unwrap(), json!(null));
283        assert_eq!(
284            formats.decode_event_value(&unit_bytes).unwrap(),
285            json!(null)
286        );
287    }
288
289    #[test]
290    fn malformed_bytes_return_error() {
291        let (format, registry) = trace_format::<u64>();
292        assert!(bcs_to_json(&[1, 2, 3], &format, &registry).is_err());
293    }
294
295    #[test]
296    fn stable_enum_round_trip() {
297        use linera_sdk_derive::StableEnumInCrate;
298
299        #[derive(Debug, PartialEq, StableEnumInCrate)]
300        enum Op {
301            Increment,
302            Set { value: u64 },
303            Add(i64, i64),
304            Echo(String),
305        }
306
307        // Wire format: each variant tag is exactly 4 ULEB128 bytes.
308        for c in [
309            Op::Increment,
310            Op::Set { value: 99 },
311            Op::Add(2, 3),
312            Op::Echo("hi".into()),
313        ] {
314            let bytes = bcs::to_bytes(&c).unwrap();
315            assert!(bytes.len() >= 4, "tag must be at least 4 bytes: {c:?}");
316            // 4-byte ULEB128: first 3 bytes have continuation bit, 4th doesn't.
317            assert_eq!(bytes[0] & 0x80, 0x80, "byte 0 has continuation");
318            assert_eq!(bytes[1] & 0x80, 0x80, "byte 1 has continuation");
319            assert_eq!(bytes[2] & 0x80, 0x80, "byte 2 has continuation");
320            assert_eq!(bytes[3] & 0x80, 0x00, "byte 3 terminates");
321
322            let back: Op = bcs::from_bytes(&bytes).unwrap();
323            assert_eq!(back, c);
324        }
325
326        // Unknown tags must be rejected.
327        let bogus = bcs::to_bytes(&0u32).unwrap();
328        assert!(bcs::from_bytes::<Op>(&bogus).is_err());
329
330        // Tags published in the trait const match what BCS actually emits.
331        for &(name, tag) in <Op as StableEnumTrace>::STABLE_VARIANTS {
332            let sample = match name {
333                "Increment" => bcs::to_bytes(&Op::Increment).unwrap(),
334                "Set" => bcs::to_bytes(&Op::Set { value: 0 }).unwrap(),
335                "Add" => bcs::to_bytes(&Op::Add(0, 0)).unwrap(),
336                "Echo" => bcs::to_bytes(&Op::Echo(String::new())).unwrap(),
337                _ => unreachable!(),
338            };
339            let decoded = decode_uleb_u32(&sample[..4]);
340            assert_eq!(decoded, tag, "variant {name} tag mismatch");
341        }
342
343        // Tracing via the extension trait records the Keccak tags in the registry.
344        let mut tracer = Tracer::new(TracerConfig::default());
345        let samples = Samples::new();
346        let format = tracer.trace_stable_enum_type::<Op>(&samples).unwrap();
347        let registry = tracer.registry().unwrap();
348        match registry.get("Op").unwrap() {
349            ContainerFormat::Enum(variants) => {
350                let mut keys: Vec<_> = variants.keys().copied().collect();
351                keys.sort();
352                let mut expected: Vec<u32> = <Op as StableEnumTrace>::STABLE_VARIANTS
353                    .iter()
354                    .map(|(_, t)| *t)
355                    .collect();
356                expected.sort();
357                assert_eq!(keys, expected);
358            }
359            _ => panic!("expected enum"),
360        }
361
362        // End-to-end: bcs_to_json works against the reflected registry.
363        let bytes = bcs::to_bytes(&Op::Set { value: 99 }).unwrap();
364        let value = bcs_to_json(&bytes, &format, &registry).unwrap();
365        assert_eq!(value, json!({ "Set": { "value": 99 } }));
366    }
367
368    /// Decode a 4-byte (exactly) ULEB128 sequence into a `u32`.
369    fn decode_uleb_u32(bytes: &[u8]) -> u32 {
370        let b0 = (bytes[0] & 0x7f) as u32;
371        let b1 = (bytes[1] & 0x7f) as u32;
372        let b2 = (bytes[2] & 0x7f) as u32;
373        let b3 = bytes[3] as u32;
374        b0 | (b1 << 7) | (b2 << 14) | (b3 << 21)
375    }
376}