Skip to main content

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