iota_types/
proto_value.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use move_core_types::{
6    account_address::AccountAddress, annotated_value as A, annotated_visitor as AV,
7    language_storage::TypeTag, u256::U256,
8};
9use prost_types::{Struct, Value, value::Kind};
10
11use crate::{
12    balance::Balance,
13    base_types::{RESOLVED_STD_OPTION, move_ascii_str_layout, move_utf8_str_layout, url_layout},
14    id::{ID, UID},
15    object::option_visitor as OV,
16};
17
18/// This is the maximum depth of a proto message
19/// The maximum depth of a proto message is 100. Given this value may be nested
20/// itself somewhere we'll conservitively cap this to ~80% of that.
21const MAX_DEPTH: usize = 80;
22
23pub struct ProtoVisitorBuilder {
24    /// Budget to spend on visiting.
25    bound: usize,
26
27    /// Current level of nesting depth while visiting.
28    depth: usize,
29}
30
31struct ProtoVisitor<'a> {
32    /// Budget left to spend on visiting.
33    bound: &'a mut usize,
34
35    /// Current level of nesting depth while visiting.
36    depth: &'a mut usize,
37}
38
39#[derive(thiserror::Error, Debug)]
40pub enum Error {
41    #[error(transparent)]
42    Visitor(#[from] AV::Error),
43
44    #[error("Deserialized value too large")]
45    OutOfBudget,
46
47    #[error("Exceeded maximum depth")]
48    TooNested,
49
50    #[error("Unexpected type")]
51    UnexpectedType,
52}
53
54impl ProtoVisitorBuilder {
55    pub fn new(bound: usize) -> Self {
56        Self { bound, depth: 0 }
57    }
58
59    fn new_visitor(&mut self) -> Result<ProtoVisitor<'_>, Error> {
60        ProtoVisitor::new(&mut self.bound, &mut self.depth)
61    }
62
63    /// Deserialize `bytes` as a `MoveValue` with layout `layout`. Can fail if
64    /// the bytes do not represent a value with this layout, or if the
65    /// deserialized value exceeds the field/type size budget.
66    pub fn deserialize_value(
67        mut self,
68        bytes: &[u8],
69        layout: &A::MoveTypeLayout,
70    ) -> anyhow::Result<Value> {
71        let mut visitor = self.new_visitor()?;
72        A::MoveValue::visit_deserialize(bytes, layout, &mut visitor)
73    }
74}
75
76impl Drop for ProtoVisitor<'_> {
77    fn drop(&mut self) {
78        self.dec_depth();
79    }
80}
81
82impl<'a> ProtoVisitor<'a> {
83    fn new(bound: &'a mut usize, depth: &'a mut usize) -> Result<Self, Error> {
84        // Increment the depth since we're creating a new Visitor instance
85        Self::inc_depth(depth)?;
86        Ok(Self { bound, depth })
87    }
88
89    fn inc_depth(depth: &mut usize) -> Result<(), Error> {
90        if *depth > MAX_DEPTH {
91            Err(Error::TooNested)
92        } else {
93            *depth += 1;
94            Ok(())
95        }
96    }
97
98    fn dec_depth(&mut self) {
99        if *self.depth == 0 {
100            panic!("BUG: logic bug in Visitor implementation");
101        } else {
102            *self.depth -= 1;
103        }
104    }
105
106    /// Deduct `size` from the overall budget. Errors if `size` exceeds the
107    /// current budget.
108    fn debit(&mut self, size: usize) -> Result<(), Error> {
109        if *self.bound < size {
110            Err(Error::OutOfBudget)
111        } else {
112            *self.bound -= size;
113            Ok(())
114        }
115    }
116
117    fn debit_value(&mut self) -> Result<(), Error> {
118        self.debit(size_of::<Value>())
119    }
120
121    fn debit_string_value(&mut self, s: &str) -> Result<(), Error> {
122        self.debit_str(s)?;
123        self.debit_value()
124    }
125
126    fn debit_str(&mut self, s: &str) -> Result<(), Error> {
127        self.debit(s.len())
128    }
129}
130
131impl<'b, 'l> AV::Visitor<'b, 'l> for ProtoVisitor<'_> {
132    type Value = Value;
133    type Error = Error;
134
135    fn visit_u8(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: u8) -> Result<Value, Error> {
136        self.debit_value()?;
137        Ok(Value::from(value))
138    }
139
140    fn visit_u16(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: u16) -> Result<Value, Error> {
141        self.debit_value()?;
142        Ok(Value::from(value))
143    }
144
145    fn visit_u32(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: u32) -> Result<Value, Error> {
146        self.debit_value()?;
147        Ok(Value::from(value))
148    }
149
150    fn visit_u64(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: u64) -> Result<Value, Error> {
151        let value = value.to_string();
152        self.debit_string_value(&value)?;
153        Ok(Value::from(value))
154    }
155
156    fn visit_u128(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: u128) -> Result<Value, Error> {
157        let value = value.to_string();
158        self.debit_string_value(&value)?;
159        Ok(Value::from(value))
160    }
161
162    fn visit_u256(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: U256) -> Result<Value, Error> {
163        let value = value.to_string();
164        self.debit_string_value(&value)?;
165        Ok(Value::from(value))
166    }
167
168    fn visit_bool(&mut self, _: &AV::ValueDriver<'_, 'b, 'l>, value: bool) -> Result<Value, Error> {
169        self.debit_value()?;
170        Ok(Value::from(value))
171    }
172
173    fn visit_address(
174        &mut self,
175        _: &AV::ValueDriver<'_, 'b, 'l>,
176        value: AccountAddress,
177    ) -> Result<Value, Error> {
178        let value = value.to_canonical_string(true);
179        self.debit_string_value(&value)?;
180        Ok(Value::from(value))
181    }
182
183    fn visit_signer(
184        &mut self,
185        _: &AV::ValueDriver<'_, 'b, 'l>,
186        value: AccountAddress,
187    ) -> Result<Value, Error> {
188        let value = value.to_canonical_string(true);
189        self.debit_string_value(&value)?;
190        Ok(Value::from(value))
191    }
192
193    fn visit_vector(&mut self, driver: &mut AV::VecDriver<'_, 'b, 'l>) -> Result<Value, Error> {
194        let value = if driver.element_layout().is_type(&TypeTag::U8) {
195            // Base64 encode arbitrary bytes
196            use base64::{Engine, engine::general_purpose::STANDARD};
197
198            if let Some(bytes) = driver
199                .bytes()
200                .get(driver.position()..(driver.position() + driver.len() as usize))
201            {
202                let b64 = STANDARD.encode(bytes);
203                self.debit_string_value(&b64)?;
204                Value::from(b64)
205            } else {
206                return Err(AV::Error::UnexpectedEof.into());
207            }
208        } else {
209            let mut elems = vec![];
210            self.debit_value()?;
211
212            while let Some(elem) =
213                driver.next_element(&mut ProtoVisitor::new(self.bound, self.depth)?)?
214            {
215                elems.push(elem);
216            }
217
218            Value::from(elems)
219        };
220
221        Ok(value)
222    }
223
224    fn visit_struct(&mut self, driver: &mut AV::StructDriver<'_, 'b, 'l>) -> Result<Value, Error> {
225        let ty = &driver.struct_layout().type_;
226        let layout = driver.struct_layout();
227
228        let value = if layout == &move_ascii_str_layout()
229            || layout == &move_utf8_str_layout()
230            || layout == &url_layout()
231        {
232            // 0x1::ascii::String or 0x1::string::String or 0x2::url::Url
233
234            let lo = driver.position();
235            driver.skip_field()?;
236            let hi = driver.position();
237
238            // HACK: Bypassing the layout to deserialize its bytes as a Rust type.
239            let bytes = &driver.bytes()[lo..hi];
240            let s: &str = bcs::from_bytes(bytes).map_err(|_| Error::UnexpectedType)?;
241            self.debit_string_value(s)?;
242            Value::from(s)
243        } else if layout == &UID::layout() || layout == &ID::layout() {
244            // 0x2::object::UID or 0x2::object::ID
245
246            let lo = driver.position();
247            driver.skip_field()?;
248            let hi = driver.position();
249
250            // HACK: Bypassing the layout to deserialize its bytes as a Rust type.
251            let bytes = &driver.bytes()[lo..hi];
252            let id = AccountAddress::from_bytes(bytes)
253                .map_err(|_| Error::UnexpectedType)?
254                .to_canonical_string(true);
255
256            self.debit_string_value(&id)?;
257            Value::from(id)
258        } else if (&ty.address, ty.module.as_ref(), ty.name.as_ref()) == RESOLVED_STD_OPTION {
259            // 0x1::option::Option
260            self.debit_value()?;
261            match OV::OptionVisitor(self).visit_struct(driver)? {
262                Some(value) => value,
263                None => Kind::NullValue(0).into(),
264            }
265        } else if Balance::is_balance_layout(layout) {
266            // 0x2::balance::Balance
267
268            let lo = driver.position();
269            driver.skip_field()?;
270            let hi = driver.position();
271
272            // HACK: Bypassing the layout to deserialize its bytes as a Rust type.
273            let bytes = &driver.bytes()[lo..hi];
274            let balance = bcs::from_bytes::<u64>(bytes)
275                .map_err(|_| Error::UnexpectedType)?
276                .to_string();
277            self.debit_string_value(&balance)?;
278            Value::from(balance)
279        } else {
280            // Arbitrary structs
281            let mut map = Struct::default();
282
283            self.debit_value()?;
284            for field in &driver.struct_layout().fields {
285                self.debit_str(field.name.as_str())?;
286            }
287
288            while let Some((field, elem)) =
289                driver.next_field(&mut ProtoVisitor::new(self.bound, self.depth)?)?
290            {
291                map.fields.insert(field.name.as_str().to_owned(), elem);
292            }
293            Value::from(Kind::StructValue(map))
294        };
295        Ok(value)
296    }
297
298    fn visit_variant(
299        &mut self,
300        driver: &mut AV::VariantDriver<'_, 'b, 'l>,
301    ) -> Result<Value, Error> {
302        let mut map = Struct::default();
303        self.debit_value()?;
304
305        self.debit_str("@variant")?;
306        self.debit_string_value(driver.variant_name().as_str())?;
307
308        map.fields
309            .insert("@variant".to_owned(), driver.variant_name().as_str().into());
310
311        for field in driver.variant_layout() {
312            self.debit_str(field.name.as_str())?;
313        }
314
315        while let Some((field, elem)) =
316            driver.next_field(&mut ProtoVisitor::new(self.bound, self.depth)?)?
317        {
318            map.fields.insert(field.name.as_str().to_owned(), elem);
319        }
320
321        Ok(Value::from(Kind::StructValue(map)))
322    }
323}
324
325impl From<OV::Error> for Error {
326    fn from(OV::Error: OV::Error) -> Self {
327        Error::UnexpectedType
328    }
329}
330
331#[cfg(test)]
332pub(crate) mod tests {
333    use std::str::FromStr;
334
335    use A::{MoveTypeLayout as L, MoveValue as V};
336    use expect_test::expect;
337    use move_core_types::{
338        annotated_value::{MoveFieldLayout, MoveStructLayout},
339        ident_str,
340        language_storage::StructTag,
341    };
342    use serde_json::json;
343
344    use super::*;
345    use crate::object::bounded_visitor::tests::{layout_, serialize, value_};
346
347    #[test]
348    fn test_simple() {
349        let type_layout = layout_(
350            "0x0::foo::Bar",
351            vec![
352                ("a", L::U64),
353                ("b", L::Vector(Box::new(L::U64))),
354                ("c", layout_("0x0::foo::Baz", vec![("d", L::U64)])),
355            ],
356        );
357
358        let value = value_(
359            "0x0::foo::Bar",
360            vec![
361                ("a", V::U64(42)),
362                ("b", V::Vector(vec![V::U64(43)])),
363                ("c", value_("0x0::foo::Baz", vec![("d", V::U64(44))])),
364            ],
365        );
366
367        let expected = json!({
368            "a": "42",
369            "b": ["43"],
370            "c": {
371                "d": "44"
372            }
373        });
374        let bound = required_budget(&expected);
375
376        let bytes = serialize(value.clone());
377
378        let deser = ProtoVisitorBuilder::new(bound)
379            .deserialize_value(&bytes, &type_layout)
380            .unwrap();
381
382        assert_eq!(expected, proto_value_to_json_value(deser));
383
384        ProtoVisitorBuilder::new(bound - 1)
385            .deserialize_value(&bytes, &type_layout)
386            .unwrap_err();
387    }
388
389    #[test]
390    fn test_too_deep() {
391        let mut layout = L::U64;
392        let mut value = V::U64(42);
393        let mut expected = serde_json::Value::from("42");
394
395        const DEPTH: usize = MAX_DEPTH;
396        for _ in 0..DEPTH {
397            layout = layout_("0x0::foo::Bar", vec![("f", layout)]);
398            value = value_("0x0::foo::Bar", vec![("f", value)]);
399            expected = json!({
400                "f": expected
401            });
402        }
403
404        let bound = required_budget(&expected);
405        let bytes = serialize(value.clone());
406
407        let deser = ProtoVisitorBuilder::new(bound)
408            .deserialize_value(&bytes, &layout)
409            .unwrap();
410
411        assert_eq!(expected, proto_value_to_json_value(deser));
412
413        // One deeper
414        layout = layout_("0x0::foo::Bar", vec![("f", layout)]);
415        value = value_("0x0::foo::Bar", vec![("f", value)]);
416
417        let bytes = serialize(value.clone());
418
419        let err = ProtoVisitorBuilder::new(bound)
420            .deserialize_value(&bytes, &layout)
421            .unwrap_err();
422
423        let expect = expect!["Exceeded maximum depth"];
424        expect.assert_eq(&err.to_string());
425    }
426
427    fn proto_value_to_json_value(proto: Value) -> serde_json::Value {
428        match proto.kind {
429            Some(Kind::NullValue(_)) | None => serde_json::Value::Null,
430            // Move doesn't support floats so for these tests can do a convert to u32
431            Some(Kind::NumberValue(n)) => serde_json::Value::from(n as u32),
432            Some(Kind::StringValue(s)) => serde_json::Value::from(s),
433            Some(Kind::BoolValue(b)) => serde_json::Value::from(b),
434            Some(Kind::StructValue(map)) => serde_json::Value::Object(
435                map.fields
436                    .into_iter()
437                    .map(|(k, v)| (k, proto_value_to_json_value(v)))
438                    .collect(),
439            ),
440            Some(Kind::ListValue(list_value)) => serde_json::Value::Array(
441                list_value
442                    .values
443                    .into_iter()
444                    .map(proto_value_to_json_value)
445                    .collect(),
446            ),
447        }
448    }
449
450    fn required_budget(json: &serde_json::Value) -> usize {
451        size_of::<Value>()
452            + match json {
453                serde_json::Value::Null => 0,
454                serde_json::Value::Bool(_) => 0,
455                serde_json::Value::Number(_) => 0,
456                serde_json::Value::String(s) => s.len(),
457                serde_json::Value::Array(vec) => vec.iter().map(required_budget).sum(),
458                serde_json::Value::Object(map) => {
459                    map.iter().map(|(k, v)| k.len() + required_budget(v)).sum()
460                }
461            }
462    }
463
464    // Tests for proper format rendering
465    //
466
467    fn json<T: serde::Serialize>(layout: A::MoveTypeLayout, data: T) -> serde_json::Value {
468        let bcs = bcs::to_bytes(&data).unwrap();
469        let proto_value = ProtoVisitorBuilder::new(1024 * 1024)
470            .deserialize_value(&bcs, &layout)
471            .unwrap();
472        proto_value_to_json_value(proto_value)
473    }
474
475    macro_rules! struct_layout {
476        ($type:literal { $($name:literal : $layout:expr),* $(,)?}) => {
477            A::MoveTypeLayout::Struct(Box::new(MoveStructLayout {
478                type_: StructTag::from_str($type).expect("Failed to parse struct"),
479                fields: vec![$(MoveFieldLayout {
480                    name: ident_str!($name).to_owned(),
481                    layout: $layout,
482                }),*]
483            }))
484        }
485    }
486
487    macro_rules! vector_layout {
488        ($inner:expr) => {
489            A::MoveTypeLayout::Vector(Box::new($inner))
490        };
491    }
492
493    fn address(a: &str) -> iota_sdk_types::Address {
494        iota_sdk_types::Address::from_str(a).unwrap()
495    }
496
497    #[test]
498    fn json_bool() {
499        let actual = json(L::Bool, true);
500        let expect = json!(true);
501        assert_eq!(expect, actual);
502
503        let actual = json(L::Bool, false);
504        let expect = json!(false);
505        assert_eq!(expect, actual);
506    }
507
508    #[test]
509    fn json_u8() {
510        let actual = json(L::U8, 42u8);
511        let expect = json!(42u8);
512        assert_eq!(expect, actual);
513    }
514
515    #[test]
516    fn json_u16() {
517        let actual = json(L::U16, 424u16);
518        let expect = json!(424u16);
519        assert_eq!(expect, actual);
520    }
521
522    #[test]
523    fn json_u32() {
524        let actual = json(L::U32, 432_432u32);
525        let expect = json!(432_432u32);
526        assert_eq!(expect, actual);
527    }
528
529    #[test]
530    fn json_u64() {
531        let actual = json(L::U64, 432_432_432_432u64);
532        let expect = json!(432_432_432_432u64.to_string());
533        assert_eq!(expect, actual);
534    }
535
536    #[test]
537    fn json_u128() {
538        let actual = json(L::U128, 424_242_424_242_424_242_424u128);
539        let expect = json!(424_242_424_242_424_242_424u128.to_string());
540        assert_eq!(expect, actual);
541    }
542
543    #[test]
544    fn json_u256() {
545        let actual = json(
546            L::U256,
547            U256::from_str("42424242424242424242424242424242424242424").unwrap(),
548        );
549        let expect = json!("42424242424242424242424242424242424242424");
550        assert_eq!(expect, actual);
551    }
552
553    #[test]
554    fn json_ascii_string() {
555        let l = struct_layout!("0x1::ascii::String" {
556            "bytes": vector_layout!(L::U8)
557        });
558        let actual = json(l, "The quick brown fox");
559        let expect = json!("The quick brown fox");
560        assert_eq!(expect, actual);
561    }
562
563    #[test]
564    fn json_utf8_string() {
565        let l = struct_layout!("0x1::string::String" {
566            "bytes": vector_layout!(L::U8)
567        });
568        let actual = json(l, "The quick brown fox");
569        let expect = json!("The quick brown fox");
570        assert_eq!(expect, actual);
571    }
572
573    #[test]
574    fn json_url() {
575        let l = struct_layout!("0x2::url::Url" {
576            "url": struct_layout!("0x1::ascii::String" {
577                "bytes": vector_layout!(L::U8)
578            })
579        });
580        let actual = json(l, "https://example.com");
581        let expect = json!("https://example.com");
582        assert_eq!(expect, actual);
583    }
584
585    #[test]
586    fn json_address() {
587        let actual = json(L::Address, address("0x42"));
588        let expect = json!(address("0x42").to_string());
589        assert_eq!(expect, actual);
590    }
591
592    #[test]
593    fn json_signer() {
594        let actual = json(L::Signer, address("0x42"));
595        let expect = json!(address("0x42").to_string());
596        assert_eq!(expect, actual);
597    }
598
599    #[test]
600    fn json_id() {
601        let l = struct_layout!("0x2::object::ID" {
602            "bytes": L::Address,
603        });
604        let actual = json(l, address("0x42"));
605        let expect = json!(address("0x42").to_string());
606        assert_eq!(expect, actual);
607    }
608
609    #[test]
610    fn json_uid() {
611        let l = struct_layout!("0x2::object::UID" {
612            "id": struct_layout!("0x2::object::ID" {
613                "bytes": L::Address,
614            })
615        });
616        let actual = json(l, address("0x42"));
617        let expect = json!(address("0x42").to_string());
618        assert_eq!(expect, actual);
619    }
620
621    #[test]
622    fn json_option() {
623        let l = struct_layout!("0x42::foo::Bar" {
624            "baz": struct_layout!("0x1::option::Option<u8>" { "vec": vector_layout!(L::U8) }),
625        });
626
627        let actual = json(l, Option::<Vec<u8>>::None);
628        let expect = json!({
629            "baz": null,
630        });
631        assert_eq!(expect, actual);
632    }
633
634    #[test]
635    fn json_balance() {
636        let l = struct_layout!("0x2::balance::Balance<0x2::iota::IOTA>" {
637            "value": L::U64,
638        });
639
640        let actual = json(l, 100u64);
641        let expect = json!(100u64.to_string());
642        assert_eq!(expect, actual);
643    }
644
645    #[test]
646    fn json_compound() {
647        let l = struct_layout!("0x42::foo::Bar" {
648            "baz": struct_layout!("0x1::option::Option<u8>" { "vec": vector_layout!(L::U8) }),
649            "qux": vector_layout!(struct_layout!("0x43::xy::Zzy" {
650                "quy": L::U16,
651                "quz": struct_layout!("0x1::option::Option<0x1::ascii::String>" {
652                    "vec": vector_layout!(struct_layout!("0x1::ascii::String" {
653                        "bytes": vector_layout!(L::U8),
654                    }))
655                }),
656                "frob": L::Address,
657            })),
658        });
659
660        let actual = json(
661            l,
662            (
663                Option::<Vec<u8>>::None,
664                vec![
665                    (44u16, Some("Hello, world!"), address("0x45")),
666                    (46u16, None, address("0x47")),
667                ],
668            ),
669        );
670        let expect = json!({
671            "baz": null,
672            "qux": [{
673                "quy": 44,
674                "quz": "Hello, world!",
675                "frob": address("0x45").to_string(),
676            },
677            {
678                "quy": 46,
679                "quz": null,
680                "frob": address("0x47").to_string(),
681            }
682            ],
683        });
684        assert_eq!(expect, actual);
685    }
686}