iota_types/dynamic_field/
visitor.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use iota_sdk_types::TypeTag;
6use move_core_types::{
7    account_address::AccountAddress,
8    annotated_value as A,
9    annotated_visitor::{self, StructDriver, ValueDriver, VariantDriver, VecDriver, Visitor},
10    u256::U256,
11};
12
13use super::{DynamicFieldInfo, DynamicFieldType};
14use crate::{
15    base_types::ObjectID,
16    id::UID,
17    iota_sdk_types_conversions::{struct_tag_core_to_sdk, type_tag_core_to_sdk},
18};
19
20/// Visitor to deserialize the outer structure of a `0x2::dynamic_field::Field`
21/// while leaving its name and value untouched.
22pub struct FieldVisitor;
23
24#[derive(Debug, Clone)]
25pub struct Field<'b, 'l> {
26    pub id: ObjectID,
27    pub kind: DynamicFieldType,
28    pub name_layout: &'l A::MoveTypeLayout,
29    pub name_bytes: &'b [u8],
30    pub value_layout: &'l A::MoveTypeLayout,
31    pub value_bytes: &'b [u8],
32}
33
34pub enum ValueMetadata {
35    DynamicField(TypeTag),
36    DynamicObjectField(ObjectID),
37}
38
39#[derive(thiserror::Error, Debug)]
40pub enum Error {
41    #[error("Not a dynamic field")]
42    NotADynamicField,
43
44    #[error("Not a dynamic object field")]
45    NotADynamicObjectField,
46
47    #[error("{0}")]
48    Visitor(#[from] annotated_visitor::Error),
49}
50
51impl FieldVisitor {
52    /// Deserialize the top-level structure from a dynamic field's
53    /// `0x2::dynamic_field::Field` without having to fully deserialize its
54    /// name or value.
55    pub fn deserialize<'b, 'l>(
56        bytes: &'b [u8],
57        layout: &'l A::MoveTypeLayout,
58    ) -> anyhow::Result<Field<'b, 'l>> {
59        A::MoveValue::visit_deserialize(bytes, layout, &mut FieldVisitor)
60    }
61}
62
63impl Field<'_, '_> {
64    /// If this field is a dynamic field, returns its value's type. If it is a
65    /// dynamic object field, it returns the ID of the object the value
66    /// points to (which must be fetched to extract its type).
67    pub fn value_metadata(&self) -> Result<ValueMetadata, Error> {
68        match self.kind {
69            DynamicFieldType::DynamicField => {
70                Ok(ValueMetadata::DynamicField(type_tag_core_to_sdk(
71                    &move_core_types::language_storage::TypeTag::from(self.value_layout),
72                )))
73            }
74
75            DynamicFieldType::DynamicObject => {
76                let id: ObjectID =
77                    bcs::from_bytes(self.value_bytes).map_err(|_| Error::NotADynamicObjectField)?;
78                Ok(ValueMetadata::DynamicObjectField(id))
79            }
80        }
81    }
82}
83
84impl<'b, 'l> Visitor<'b, 'l> for FieldVisitor {
85    type Value = Field<'b, 'l>;
86    type Error = Error;
87
88    fn visit_struct(
89        &mut self,
90        driver: &mut StructDriver<'_, 'b, 'l>,
91    ) -> Result<Self::Value, Error> {
92        if !DynamicFieldInfo::is_dynamic_field(&struct_tag_core_to_sdk(
93            &driver.struct_layout().type_,
94        )) {
95            return Err(Error::NotADynamicField);
96        }
97
98        // Set-up optionals to fill while visiting fields -- all of them must be filled
99        // by the end to successfully return a `Field`.
100        let mut id = None;
101        let mut name_parts = None;
102        let mut value_parts = None;
103
104        while let Some(A::MoveFieldLayout { name, layout }) = driver.peek_field() {
105            match name.as_str() {
106                "id" => {
107                    let lo = driver.position();
108                    driver.skip_field()?;
109                    let hi = driver.position();
110
111                    if !matches!(layout, A::MoveTypeLayout::Struct(s) if s.as_ref() == &UID::layout())
112                    {
113                        return Err(Error::NotADynamicField);
114                    }
115
116                    // HACK: Bypassing `id`'s layout to deserialize its bytes as a Rust type.
117                    let bytes = &driver.bytes()[lo..hi];
118                    id = Some(ObjectID::from_bytes(bytes).map_err(|_| Error::NotADynamicField)?);
119                }
120
121                "name" => {
122                    let lo = driver.position();
123                    driver.skip_field()?;
124                    let hi = driver.position();
125
126                    let (kind, layout) = extract_name_layout(layout)?;
127                    name_parts = Some((&driver.bytes()[lo..hi], layout, kind));
128                }
129
130                "value" => {
131                    let lo = driver.position();
132                    driver.skip_field()?;
133                    let hi = driver.position();
134                    value_parts = Some((&driver.bytes()[lo..hi], layout));
135                }
136
137                _ => {
138                    return Err(Error::NotADynamicField);
139                }
140            }
141        }
142
143        let (Some(id), Some((name_bytes, name_layout, kind)), Some((value_bytes, value_layout))) =
144            (id, name_parts, value_parts)
145        else {
146            return Err(Error::NotADynamicField);
147        };
148
149        Ok(Field {
150            id,
151            kind,
152            name_layout,
153            name_bytes,
154            value_layout,
155            value_bytes,
156        })
157    }
158
159    // === Empty/default cases ===
160    //
161    // A dynamic field must be a struct, so if the visitor is fed anything else, it
162    // complains.
163
164    fn visit_u8(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u8) -> Result<Self::Value, Error> {
165        Err(Error::NotADynamicField)
166    }
167
168    fn visit_u16(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u16) -> Result<Self::Value, Error> {
169        Err(Error::NotADynamicField)
170    }
171
172    fn visit_u32(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u32) -> Result<Self::Value, Error> {
173        Err(Error::NotADynamicField)
174    }
175
176    fn visit_u64(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u64) -> Result<Self::Value, Error> {
177        Err(Error::NotADynamicField)
178    }
179
180    fn visit_u128(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u128) -> Result<Self::Value, Error> {
181        Err(Error::NotADynamicField)
182    }
183
184    fn visit_u256(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: U256) -> Result<Self::Value, Error> {
185        Err(Error::NotADynamicField)
186    }
187
188    fn visit_bool(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: bool) -> Result<Self::Value, Error> {
189        Err(Error::NotADynamicField)
190    }
191
192    fn visit_address(
193        &mut self,
194        _: &ValueDriver<'_, 'b, 'l>,
195        _: AccountAddress,
196    ) -> Result<Self::Value, Error> {
197        Err(Error::NotADynamicField)
198    }
199
200    fn visit_signer(
201        &mut self,
202        _: &ValueDriver<'_, 'b, 'l>,
203        _: AccountAddress,
204    ) -> Result<Self::Value, Error> {
205        Err(Error::NotADynamicField)
206    }
207
208    fn visit_vector(&mut self, _: &mut VecDriver<'_, 'b, 'l>) -> Result<Self::Value, Error> {
209        Err(Error::NotADynamicField)
210    }
211
212    fn visit_variant(&mut self, _: &mut VariantDriver<'_, 'b, 'l>) -> Result<Self::Value, Error> {
213        Err(Error::NotADynamicField)
214    }
215}
216
217/// Extract the type and layout of a dynamic field name, from the layout of its
218/// `Field.name`.
219fn extract_name_layout(
220    layout: &A::MoveTypeLayout,
221) -> Result<(DynamicFieldType, &A::MoveTypeLayout), Error> {
222    let A::MoveTypeLayout::Struct(struct_) = layout else {
223        return Ok((DynamicFieldType::DynamicField, layout));
224    };
225
226    if !DynamicFieldInfo::is_dynamic_object_field_wrapper(&struct_tag_core_to_sdk(&struct_.type_)) {
227        return Ok((DynamicFieldType::DynamicField, layout));
228    }
229
230    // Wrapper contains just one field
231    let [A::MoveFieldLayout { name, layout }] = &struct_.fields[..] else {
232        return Err(Error::NotADynamicField);
233    };
234
235    // ...called `name`
236    if name.as_str() != "name" {
237        return Err(Error::NotADynamicField);
238    }
239
240    Ok((DynamicFieldType::DynamicObject, layout))
241}
242
243#[cfg(test)]
244mod tests {
245    use std::str::FromStr;
246
247    use move_core_types::{
248        account_address::AccountAddress, annotated_value as A, language_storage::TypeTag,
249    };
250
251    use super::*;
252    use crate::{
253        base_types::ObjectID,
254        dynamic_field,
255        id::UID,
256        object::bounded_visitor::tests::{enum_, layout_, value_, variant_},
257    };
258
259    #[test]
260    fn test_dynamic_field_name() {
261        for (name, name_layout, name_bcs) in fixtures() {
262            for (value, value_layout, value_bcs) in fixtures() {
263                let df = serialized_df("0x264", name.clone(), value.clone());
264                let df_layout = df_layout(name_layout.clone(), value_layout.clone());
265                let field = FieldVisitor::deserialize(&df, &df_layout)
266                    .unwrap_or_else(|e| panic!("Failed to deserialize {name} => {value}: {e}"));
267
268                assert_eq!(field.id, oid_("0x264"), "{name} => {value}");
269                assert_eq!(field.name_bytes, &name_bcs, "{name} => {value}");
270                assert_eq!(field.value_bytes, &value_bcs, "{name} => {value}");
271
272                assert_eq!(
273                    field.kind,
274                    DynamicFieldType::DynamicField,
275                    "{name} => {value}",
276                );
277
278                assert_eq!(
279                    TypeTag::from(field.name_layout),
280                    TypeTag::from(&name_layout),
281                    "{name} => {value}",
282                );
283
284                assert_eq!(
285                    TypeTag::from(field.value_layout),
286                    TypeTag::from(&value_layout),
287                    "{name} => {value}",
288                );
289            }
290        }
291    }
292
293    #[test]
294    fn test_dynamic_object_field_name() {
295        let addr = A::MoveValue::Address(AccountAddress::ONE);
296        let id = value_("0x2::object::ID", vec![("bytes", addr)]);
297        let id_bcs = id.clone().undecorate().simple_serialize().unwrap();
298
299        for (name, name_layout, name_bcs) in fixtures() {
300            let df = serialized_df("0x264", name.clone(), id.clone());
301            let df_layout = dof_layout(name_layout.clone());
302            let field = FieldVisitor::deserialize(&df, &df_layout)
303                .unwrap_or_else(|e| panic!("Failed to deserialize {name}: {e}"));
304
305            assert_eq!(field.id, oid_("0x264"), "{name}");
306            assert_eq!(field.name_bytes, &name_bcs, "{name}");
307            assert_eq!(field.value_bytes, &id_bcs, "{name}");
308
309            assert_eq!(field.kind, DynamicFieldType::DynamicObject, "{name}",);
310
311            assert_eq!(
312                TypeTag::from(field.name_layout),
313                TypeTag::from(&name_layout),
314                "{name}",
315            );
316
317            assert_eq!(
318                TypeTag::from(field.value_layout),
319                TypeTag::from(&id_layout()),
320                "{name}",
321            );
322        }
323    }
324
325    #[test]
326    fn test_name_from_not_dynamic_field() {
327        for (value, layout, bytes) in fixtures() {
328            let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
329                panic!("Expected NotADynamicField error for {value}");
330            };
331
332            assert_eq!(
333                e.to_string(),
334                "Not a dynamic field",
335                "Unexpected error for {value}"
336            );
337        }
338    }
339
340    /// If the visitor is run over a type that isn't actually a
341    /// `0x2::dynamic_field::Field`, it will complain.
342    #[test]
343    fn test_from_bad_type() {
344        for (value, layout, bytes) in fixtures() {
345            let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
346                panic!("Expected NotADynamicField error for {value}");
347            };
348
349            assert_eq!(
350                e.to_string(),
351                "Not a dynamic field",
352                "Unexpected error for {value}"
353            );
354        }
355    }
356
357    #[test]
358    fn test_from_dynamic_field_missing_id() {
359        let bytes = bcs::to_bytes(&(42u8, 43u8)).unwrap();
360        let layout = layout_(
361            "0x2::dynamic_field::Field<u8, u8>",
362            vec![
363                ("name", A::MoveTypeLayout::U8),
364                ("value", A::MoveTypeLayout::U8),
365            ],
366        );
367
368        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
369            panic!("Expected NotADynamicField error");
370        };
371
372        assert_eq!(e.to_string(), "Not a dynamic field");
373    }
374
375    #[test]
376    fn test_from_dynamic_field_missing_name() {
377        let bytes = bcs::to_bytes(&(oid_("0x264"), 43u8)).unwrap();
378        let layout = layout_(
379            "0x2::dynamic_field::Field<u8, u8>",
380            vec![("id", id_layout()), ("value", A::MoveTypeLayout::U8)],
381        );
382
383        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
384            panic!("Expected NotADynamicField error");
385        };
386
387        assert_eq!(e.to_string(), "Not a dynamic field");
388    }
389
390    #[test]
391    fn test_from_dynamic_field_missing_value() {
392        let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap();
393        let layout = layout_(
394            "0x2::dynamic_field::Field<u8, u8>",
395            vec![("id", id_layout()), ("name", A::MoveTypeLayout::U8)],
396        );
397
398        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
399            panic!("Expected NotADynamicField error");
400        };
401
402        assert_eq!(e.to_string(), "Not a dynamic field");
403    }
404
405    #[test]
406    fn test_from_dynamic_field_weird_id() {
407        let bytes = bcs::to_bytes(&(42u8, 43u8, 44u8)).unwrap();
408        let layout = layout_(
409            "0x2::dynamic_field::Field<u8, u8>",
410            vec![
411                ("id", A::MoveTypeLayout::U8),
412                ("name", A::MoveTypeLayout::U8),
413                ("value", A::MoveTypeLayout::U8),
414            ],
415        );
416
417        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
418            panic!("Expected NotADynamicField error");
419        };
420
421        assert_eq!(e.to_string(), "Not a dynamic field");
422    }
423
424    /// If the name is wrapped in `0x2::dynamic_object_field::Wrapper`, but the
425    /// wrapper's structure is somehow incorrect, that will result in an
426    /// error.
427    #[test]
428    fn test_from_dynamic_object_field_bad_wrapper() {
429        let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap();
430        let layout = layout_(
431            "0x2::dynamic_field::Field<0x2::dynamic_object_field::Wrapper<u8>, u8>",
432            vec![
433                ("id", id_layout()),
434                (
435                    "name",
436                    layout_(
437                        "0x2::dynamic_object_field::Wrapper<u8>",
438                        // In the real type, the field is called "name"
439                        vec![("wrapped", A::MoveTypeLayout::U8)],
440                    ),
441                ),
442                ("value", A::MoveTypeLayout::U8),
443            ],
444        );
445
446        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
447            panic!("Expected NotADynamicField error");
448        };
449
450        assert_eq!(e.to_string(), "Not a dynamic field");
451    }
452
453    /// Various Move values to use as dynamic field names and values.
454    fn fixtures() -> Vec<(A::MoveValue, A::MoveTypeLayout, Vec<u8>)> {
455        use A::{MoveTypeLayout as T, MoveValue as V};
456
457        vec![
458            fixture(V::U8(42), T::U8),
459            fixture(V::Address(AccountAddress::ONE), T::Address),
460            fixture(
461                V::Vector(vec![V::U32(43), V::U32(44), V::U32(45)]),
462                T::Vector(Box::new(T::U32)),
463            ),
464            fixture(
465                value_(
466                    "0x2::object::ID",
467                    vec![("bytes", V::Address(AccountAddress::TWO))],
468                ),
469                layout_("0x2::object::ID", vec![("bytes", T::Address)]),
470            ),
471            fixture(
472                variant_(
473                    "0x1::option::Option<u64>",
474                    "Some",
475                    1,
476                    vec![("value", V::U64(46))],
477                ),
478                enum_(
479                    "0x1::option::Option<u64>",
480                    vec![
481                        (("None", 0), vec![]),
482                        (("Some", 1), vec![("value", T::U64)]),
483                    ],
484                ),
485            ),
486        ]
487    }
488
489    fn fixture(
490        value: A::MoveValue,
491        layout: A::MoveTypeLayout,
492    ) -> (A::MoveValue, A::MoveTypeLayout, Vec<u8>) {
493        let bytes = value
494            .clone()
495            .undecorate()
496            .simple_serialize()
497            .unwrap_or_else(|| panic!("Failed to serialize {}", value.clone()));
498
499        (value, layout, bytes)
500    }
501
502    fn oid_(rep: &str) -> ObjectID {
503        ObjectID::from_str(rep).unwrap()
504    }
505
506    fn serialized_df(id: &str, name: A::MoveValue, value: A::MoveValue) -> Vec<u8> {
507        bcs::to_bytes(&dynamic_field::Field {
508            id: UID::new(oid_(id)),
509            name: name.undecorate(),
510            value: value.undecorate(),
511        })
512        .unwrap()
513    }
514
515    fn id_layout() -> A::MoveTypeLayout {
516        let addr = A::MoveTypeLayout::Address;
517        layout_("0x2::object::ID", vec![("bytes", addr)])
518    }
519
520    fn df_layout(name: A::MoveTypeLayout, value: A::MoveTypeLayout) -> A::MoveTypeLayout {
521        let uid = layout_("0x2::object::UID", vec![("id", id_layout())]);
522        let field = format!(
523            "0x2::dynamic_field::Field<{}, {}>",
524            TypeTag::from(&name).to_canonical_display(/* with_prefix */ true),
525            TypeTag::from(&value).to_canonical_display(/* with_prefix */ true)
526        );
527
528        layout_(&field, vec![("id", uid), ("name", name), ("value", value)])
529    }
530
531    fn dof_layout(name: A::MoveTypeLayout) -> A::MoveTypeLayout {
532        let tag = TypeTag::from(&name);
533        let wrapper = format!(
534            "0x2::dynamic_object_field::Wrapper<{}>",
535            tag.to_canonical_display(/* with_prefix */ true)
536        );
537
538        let name = layout_(&wrapper, vec![("name", name)]);
539        df_layout(name, id_layout())
540    }
541}