sui_types/dynamic_field/
visitor.rs

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