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