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