Skip to main content

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_move_types::iota_framework::object::UID;
6use iota_sdk_types::{ObjectId, TypeTag};
7use move_core_types::{
8    account_address::AccountAddress,
9    annotated_value as A,
10    annotated_visitor::{self, StructDriver, ValueDriver, VariantDriver, VecDriver, Visitor},
11    u256::U256,
12};
13
14use super::{DynamicFieldInfo, DynamicFieldType};
15use crate::{
16    iota_sdk_types_conversions::{struct_tag_core_to_sdk, type_tag_core_to_sdk},
17    move_layout::MoveLayout,
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 iota_sdk_move_types::iota_framework::{dynamic_field::Field, object::UID};
248    use move_core_types::{
249        account_address::AccountAddress, annotated_value as A, language_storage::TypeTag,
250    };
251
252    use super::*;
253    use crate::object::bounded_visitor::tests::{enum_, layout_, value_, variant_};
254
255    #[test]
256    fn test_dynamic_field_name() {
257        for (name, name_layout, name_bcs) in fixtures() {
258            for (value, value_layout, value_bcs) in fixtures() {
259                let df = serialized_df("0x264", name.clone(), value.clone());
260                let df_layout = df_layout(name_layout.clone(), value_layout.clone());
261                let field = FieldVisitor::deserialize(&df, &df_layout)
262                    .unwrap_or_else(|e| panic!("Failed to deserialize {name} => {value}: {e}"));
263
264                assert_eq!(field.id, oid_("0x264"), "{name} => {value}");
265                assert_eq!(field.name_bytes, &name_bcs, "{name} => {value}");
266                assert_eq!(field.value_bytes, &value_bcs, "{name} => {value}");
267
268                assert_eq!(
269                    field.kind,
270                    DynamicFieldType::DynamicField,
271                    "{name} => {value}",
272                );
273
274                assert_eq!(
275                    TypeTag::from(field.name_layout),
276                    TypeTag::from(&name_layout),
277                    "{name} => {value}",
278                );
279
280                assert_eq!(
281                    TypeTag::from(field.value_layout),
282                    TypeTag::from(&value_layout),
283                    "{name} => {value}",
284                );
285            }
286        }
287    }
288
289    #[test]
290    fn test_dynamic_object_field_name() {
291        let addr = A::MoveValue::Address(AccountAddress::ONE);
292        let id = value_("0x2::object::ID", vec![("bytes", addr)]);
293        let id_bcs = id.clone().undecorate().simple_serialize().unwrap();
294
295        for (name, name_layout, name_bcs) in fixtures() {
296            let df = serialized_df("0x264", name.clone(), id.clone());
297            let df_layout = dof_layout(name_layout.clone());
298            let field = FieldVisitor::deserialize(&df, &df_layout)
299                .unwrap_or_else(|e| panic!("Failed to deserialize {name}: {e}"));
300
301            assert_eq!(field.id, oid_("0x264"), "{name}");
302            assert_eq!(field.name_bytes, &name_bcs, "{name}");
303            assert_eq!(field.value_bytes, &id_bcs, "{name}");
304
305            assert_eq!(field.kind, DynamicFieldType::DynamicObject, "{name}",);
306
307            assert_eq!(
308                TypeTag::from(field.name_layout),
309                TypeTag::from(&name_layout),
310                "{name}",
311            );
312
313            assert_eq!(
314                TypeTag::from(field.value_layout),
315                TypeTag::from(&id_layout()),
316                "{name}",
317            );
318        }
319    }
320
321    #[test]
322    fn test_name_from_not_dynamic_field() {
323        for (value, layout, bytes) in fixtures() {
324            let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
325                panic!("Expected NotADynamicField error for {value}");
326            };
327
328            assert_eq!(
329                e.to_string(),
330                "Not a dynamic field",
331                "Unexpected error for {value}"
332            );
333        }
334    }
335
336    /// If the visitor is run over a type that isn't actually a
337    /// `0x2::dynamic_field::Field`, it will complain.
338    #[test]
339    fn test_from_bad_type() {
340        for (value, layout, bytes) in fixtures() {
341            let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
342                panic!("Expected NotADynamicField error for {value}");
343            };
344
345            assert_eq!(
346                e.to_string(),
347                "Not a dynamic field",
348                "Unexpected error for {value}"
349            );
350        }
351    }
352
353    #[test]
354    fn test_from_dynamic_field_missing_id() {
355        let bytes = bcs::to_bytes(&(42u8, 43u8)).unwrap();
356        let layout = layout_(
357            "0x2::dynamic_field::Field<u8, u8>",
358            vec![
359                ("name", A::MoveTypeLayout::U8),
360                ("value", A::MoveTypeLayout::U8),
361            ],
362        );
363
364        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
365            panic!("Expected NotADynamicField error");
366        };
367
368        assert_eq!(e.to_string(), "Not a dynamic field");
369    }
370
371    #[test]
372    fn test_from_dynamic_field_missing_name() {
373        let bytes = bcs::to_bytes(&(oid_("0x264"), 43u8)).unwrap();
374        let layout = layout_(
375            "0x2::dynamic_field::Field<u8, u8>",
376            vec![("id", id_layout()), ("value", A::MoveTypeLayout::U8)],
377        );
378
379        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
380            panic!("Expected NotADynamicField error");
381        };
382
383        assert_eq!(e.to_string(), "Not a dynamic field");
384    }
385
386    #[test]
387    fn test_from_dynamic_field_missing_value() {
388        let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap();
389        let layout = layout_(
390            "0x2::dynamic_field::Field<u8, u8>",
391            vec![("id", id_layout()), ("name", A::MoveTypeLayout::U8)],
392        );
393
394        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
395            panic!("Expected NotADynamicField error");
396        };
397
398        assert_eq!(e.to_string(), "Not a dynamic field");
399    }
400
401    #[test]
402    fn test_from_dynamic_field_weird_id() {
403        let bytes = bcs::to_bytes(&(42u8, 43u8, 44u8)).unwrap();
404        let layout = layout_(
405            "0x2::dynamic_field::Field<u8, u8>",
406            vec![
407                ("id", A::MoveTypeLayout::U8),
408                ("name", A::MoveTypeLayout::U8),
409                ("value", A::MoveTypeLayout::U8),
410            ],
411        );
412
413        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
414            panic!("Expected NotADynamicField error");
415        };
416
417        assert_eq!(e.to_string(), "Not a dynamic field");
418    }
419
420    /// If the name is wrapped in `0x2::dynamic_object_field::Wrapper`, but the
421    /// wrapper's structure is somehow incorrect, that will result in an
422    /// error.
423    #[test]
424    fn test_from_dynamic_object_field_bad_wrapper() {
425        let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap();
426        let layout = layout_(
427            "0x2::dynamic_field::Field<0x2::dynamic_object_field::Wrapper<u8>, u8>",
428            vec![
429                ("id", id_layout()),
430                (
431                    "name",
432                    layout_(
433                        "0x2::dynamic_object_field::Wrapper<u8>",
434                        // In the real type, the field is called "name"
435                        vec![("wrapped", A::MoveTypeLayout::U8)],
436                    ),
437                ),
438                ("value", A::MoveTypeLayout::U8),
439            ],
440        );
441
442        let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else {
443            panic!("Expected NotADynamicField error");
444        };
445
446        assert_eq!(e.to_string(), "Not a dynamic field");
447    }
448
449    /// Various Move values to use as dynamic field names and values.
450    fn fixtures() -> Vec<(A::MoveValue, A::MoveTypeLayout, Vec<u8>)> {
451        use A::{MoveTypeLayout as T, MoveValue as V};
452
453        vec![
454            fixture(V::U8(42), T::U8),
455            fixture(V::Address(AccountAddress::ONE), T::Address),
456            fixture(
457                V::Vector(vec![V::U32(43), V::U32(44), V::U32(45)]),
458                T::Vector(Box::new(T::U32)),
459            ),
460            fixture(
461                value_(
462                    "0x2::object::ID",
463                    vec![("bytes", V::Address(AccountAddress::TWO))],
464                ),
465                layout_("0x2::object::ID", vec![("bytes", T::Address)]),
466            ),
467            fixture(
468                variant_(
469                    "0x1::option::Option<u64>",
470                    "Some",
471                    1,
472                    vec![("value", V::U64(46))],
473                ),
474                enum_(
475                    "0x1::option::Option<u64>",
476                    vec![
477                        (("None", 0), vec![]),
478                        (("Some", 1), vec![("value", T::U64)]),
479                    ],
480                ),
481            ),
482        ]
483    }
484
485    fn fixture(
486        value: A::MoveValue,
487        layout: A::MoveTypeLayout,
488    ) -> (A::MoveValue, A::MoveTypeLayout, Vec<u8>) {
489        let bytes = value
490            .clone()
491            .undecorate()
492            .simple_serialize()
493            .unwrap_or_else(|| panic!("Failed to serialize {}", value.clone()));
494
495        (value, layout, bytes)
496    }
497
498    fn oid_(rep: &str) -> ObjectId {
499        ObjectId::from_str(rep).unwrap()
500    }
501
502    fn serialized_df(id: &str, name: A::MoveValue, value: A::MoveValue) -> Vec<u8> {
503        bcs::to_bytes(&Field {
504            id: UID::new(oid_(id)),
505            name: name.undecorate(),
506            value: value.undecorate(),
507        })
508        .unwrap()
509    }
510
511    fn id_layout() -> A::MoveTypeLayout {
512        let addr = A::MoveTypeLayout::Address;
513        layout_("0x2::object::ID", vec![("bytes", addr)])
514    }
515
516    fn df_layout(name: A::MoveTypeLayout, value: A::MoveTypeLayout) -> A::MoveTypeLayout {
517        let uid = layout_("0x2::object::UID", vec![("id", id_layout())]);
518        let field = format!(
519            "0x2::dynamic_field::Field<{}, {}>",
520            TypeTag::from(&name).to_canonical_display(/* with_prefix */ true),
521            TypeTag::from(&value).to_canonical_display(/* with_prefix */ true)
522        );
523
524        layout_(&field, vec![("id", uid), ("name", name), ("value", value)])
525    }
526
527    fn dof_layout(name: A::MoveTypeLayout) -> A::MoveTypeLayout {
528        let tag = TypeTag::from(&name);
529        let wrapper = format!(
530            "0x2::dynamic_object_field::Wrapper<{}>",
531            tag.to_canonical_display(/* with_prefix */ true)
532        );
533
534        let name = layout_(&wrapper, vec![("name", name)]);
535        df_layout(name, id_layout())
536    }
537}