sui_move_natives_v2/object_runtime/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub(crate) mod object_store;
5
6use self::object_store::{ChildObjectEffect, ObjectResult};
7use super::get_object_id;
8use better_any::{Tid, TidAble};
9use indexmap::map::IndexMap;
10use indexmap::set::IndexSet;
11use move_binary_format::errors::{PartialVMError, PartialVMResult};
12use move_core_types::{
13    account_address::AccountAddress,
14    annotated_value::{MoveTypeLayout, MoveValue},
15    annotated_visitor as AV,
16    effects::Op,
17    language_storage::StructTag,
18    runtime_value as R,
19    vm_status::StatusCode,
20};
21use move_vm_types::{
22    loaded_data::runtime_types::Type,
23    values::{GlobalValue, Value},
24};
25use object_store::ChildObjectStore;
26use std::{
27    collections::{BTreeMap, BTreeSet},
28    sync::Arc,
29};
30use sui_protocol_config::{check_limit_by_meter, LimitThresholdCrossed, ProtocolConfig};
31use sui_types::{
32    base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
33    committee::EpochId,
34    error::{ExecutionError, VMMemoryLimitExceededSubStatusCode},
35    execution::DynamicallyLoadedObjectMetadata,
36    execution_status::ExecutionErrorKind,
37    id::UID,
38    metrics::LimitsMetrics,
39    object::{MoveObject, Owner},
40    storage::ChildObjectResolver,
41    SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
42    SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
43};
44
45pub enum ObjectEvent {
46    /// Transfer to a new address or object. Or make it shared or immutable.
47    Transfer(Owner, MoveObject),
48    /// An object ID is deleted
49    DeleteObjectID(ObjectID),
50}
51
52type Set<K> = IndexSet<K>;
53
54#[derive(Default)]
55pub(crate) struct TestInventories {
56    pub(crate) objects: BTreeMap<ObjectID, Value>,
57    // address inventories. Most recent objects are at the back of the set
58    pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<Type, Set<ObjectID>>>,
59    // global inventories.Most recent objects are at the back of the set
60    pub(crate) shared_inventory: BTreeMap<Type, Set<ObjectID>>,
61    pub(crate) immutable_inventory: BTreeMap<Type, Set<ObjectID>>,
62    pub(crate) taken_immutable_values: BTreeMap<Type, BTreeMap<ObjectID, Value>>,
63    // object has been taken from the inventory
64    pub(crate) taken: BTreeMap<ObjectID, Owner>,
65}
66
67pub struct LoadedRuntimeObject {
68    pub version: SequenceNumber,
69    pub is_modified: bool,
70}
71
72pub struct RuntimeResults {
73    pub writes: IndexMap<ObjectID, (Owner, Type, Value)>,
74    pub user_events: Vec<(Type, StructTag, Value)>,
75    // Loaded child objects, their loaded version/digest and whether they were modified.
76    pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
77    pub created_object_ids: Set<ObjectID>,
78    pub deleted_object_ids: Set<ObjectID>,
79}
80
81#[derive(Default)]
82pub(crate) struct ObjectRuntimeState {
83    pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
84    // new ids from object::new
85    new_ids: Set<ObjectID>,
86    // ids passed to object::delete
87    deleted_ids: Set<ObjectID>,
88    // transfers to a new owner (shared, immutable, object, or account address)
89    // TODO these struct tags can be removed if type_to_type_tag was exposed in the session
90    transfers: IndexMap<ObjectID, (Owner, Type, Value)>,
91    events: Vec<(Type, StructTag, Value)>,
92    // total size of events emitted so far
93    total_events_size: u64,
94    received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
95}
96
97#[derive(Tid)]
98pub struct ObjectRuntime<'a> {
99    child_object_store: ChildObjectStore<'a>,
100    // inventories for test scenario
101    pub(crate) test_inventories: TestInventories,
102    // the internal state
103    pub(crate) state: ObjectRuntimeState,
104    // whether or not this TX is gas metered
105    is_metered: bool,
106
107    pub(crate) protocol_config: &'a ProtocolConfig,
108    pub(crate) metrics: Arc<LimitsMetrics>,
109}
110
111pub enum TransferResult {
112    New,
113    SameOwner,
114    OwnerChanged,
115}
116
117pub struct InputObject {
118    pub contained_uids: BTreeSet<ObjectID>,
119    pub version: SequenceNumber,
120    pub owner: Owner,
121}
122
123impl TestInventories {
124    fn new() -> Self {
125        Self::default()
126    }
127}
128
129impl<'a> ObjectRuntime<'a> {
130    pub fn new(
131        object_resolver: &'a dyn ChildObjectResolver,
132        input_objects: BTreeMap<ObjectID, InputObject>,
133        is_metered: bool,
134        protocol_config: &'a ProtocolConfig,
135        metrics: Arc<LimitsMetrics>,
136        epoch_id: EpochId,
137    ) -> Self {
138        let mut input_object_owners = BTreeMap::new();
139        let mut root_version = BTreeMap::new();
140        let mut wrapped_object_containers = BTreeMap::new();
141        for (id, input_object) in input_objects {
142            let InputObject {
143                contained_uids,
144                version,
145                owner,
146            } = input_object;
147            input_object_owners.insert(id, owner);
148            debug_assert!(contained_uids.contains(&id));
149            for contained_uid in contained_uids {
150                root_version.insert(contained_uid, version);
151                if contained_uid != id {
152                    let prev = wrapped_object_containers.insert(contained_uid, id);
153                    debug_assert!(prev.is_none());
154                }
155            }
156        }
157        Self {
158            child_object_store: ChildObjectStore::new(
159                object_resolver,
160                root_version,
161                wrapped_object_containers,
162                is_metered,
163                protocol_config,
164                metrics.clone(),
165                epoch_id,
166            ),
167            test_inventories: TestInventories::new(),
168            state: ObjectRuntimeState {
169                input_objects: input_object_owners,
170                new_ids: Set::new(),
171                deleted_ids: Set::new(),
172                transfers: IndexMap::new(),
173                events: vec![],
174                total_events_size: 0,
175                received: IndexMap::new(),
176            },
177            is_metered,
178            protocol_config,
179            metrics,
180        }
181    }
182
183    pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
184        // If metered, we use the metered limit (non system tx limit) as the hard limit
185        // This macro takes care of that
186        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
187            self.is_metered,
188            self.state.new_ids.len(),
189            self.protocol_config.max_num_new_move_object_ids(),
190            self.protocol_config.max_num_new_move_object_ids_system_tx(),
191            self.metrics.excessive_new_move_object_ids
192        ) {
193            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
194                .with_message(format!("Creating more than {} IDs is not allowed", lim))
195                .with_sub_status(
196                    VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
197                ));
198        };
199
200        // remove from deleted_ids for the case in dynamic fields where the Field object was deleted
201        // and then re-added in a single transaction. In that case, we also skip adding it
202        // to new_ids.
203        let was_present = self.state.deleted_ids.shift_remove(&id);
204        if !was_present {
205            // mark the id as new
206            self.state.new_ids.insert(id);
207        }
208        Ok(())
209    }
210
211    pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
212        // This is defensive because `self.state.deleted_ids` may not indeed
213        // be called based on the `was_new` flag
214        // Metered transactions don't have limits for now
215
216        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
217            self.is_metered,
218            self.state.deleted_ids.len(),
219            self.protocol_config.max_num_deleted_move_object_ids(),
220            self.protocol_config
221                .max_num_deleted_move_object_ids_system_tx(),
222            self.metrics.excessive_deleted_move_object_ids
223        ) {
224            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
225                .with_message(format!("Deleting more than {} IDs is not allowed", lim))
226                .with_sub_status(
227                    VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
228                ));
229        };
230
231        let was_new = self.state.new_ids.shift_remove(&id);
232        if !was_new {
233            self.state.deleted_ids.insert(id);
234        }
235        Ok(())
236    }
237
238    pub fn transfer(
239        &mut self,
240        owner: Owner,
241        ty: Type,
242        obj: Value,
243    ) -> PartialVMResult<TransferResult> {
244        let id: ObjectID = get_object_id(obj.copy_value()?)?
245            .value_as::<AccountAddress>()?
246            .into();
247        // - An object is new if it is contained in the new ids or if it is one of the objects
248        //   created during genesis (the system state object or clock).
249        // - Otherwise, check the input objects for the previous owner
250        // - If it was not in the input objects, it must have been wrapped or must have been a
251        //   child object
252        let is_framework_obj = [
253            SUI_SYSTEM_STATE_OBJECT_ID,
254            SUI_CLOCK_OBJECT_ID,
255            SUI_AUTHENTICATOR_STATE_OBJECT_ID,
256            SUI_RANDOMNESS_STATE_OBJECT_ID,
257            SUI_DENY_LIST_OBJECT_ID,
258        ]
259        .contains(&id);
260        let transfer_result = if self.state.new_ids.contains(&id) {
261            TransferResult::New
262        } else if is_framework_obj {
263            // framework objects are always created when they are transferred, but the id is
264            // hard-coded so it is not yet in new_ids
265            self.state.new_ids.insert(id);
266            TransferResult::New
267        } else if let Some(prev_owner) = self.state.input_objects.get(&id) {
268            match (&owner, prev_owner) {
269                // don't use == for dummy values in Shared owner
270                (Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
271                (new, old) if new == old => TransferResult::SameOwner,
272                _ => TransferResult::OwnerChanged,
273            }
274        } else {
275            TransferResult::OwnerChanged
276        };
277
278        // Metered transactions don't have limits for now
279
280        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
281            // TODO: is this not redundant? Metered TX implies framework obj cannot be transferred
282            self.is_metered && !is_framework_obj, // We have higher limits for unmetered transactions and framework obj
283            self.state.transfers.len(),
284            self.protocol_config.max_num_transferred_move_object_ids(),
285            self.protocol_config
286                .max_num_transferred_move_object_ids_system_tx(),
287            self.metrics.excessive_transferred_move_object_ids
288        ) {
289            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
290                .with_message(format!("Transferring more than {} IDs is not allowed", lim))
291                .with_sub_status(
292                    VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
293                ));
294        };
295
296        self.state.transfers.insert(id, (owner, ty, obj));
297        Ok(transfer_result)
298    }
299
300    pub fn emit_event(&mut self, ty: Type, tag: StructTag, event: Value) -> PartialVMResult<()> {
301        if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
302            return Err(max_event_error(self.protocol_config.max_num_event_emit()));
303        }
304        self.state.events.push((ty, tag, event));
305        Ok(())
306    }
307
308    pub fn take_user_events(&mut self) -> Vec<(Type, StructTag, Value)> {
309        std::mem::take(&mut self.state.events)
310    }
311
312    pub(crate) fn child_object_exists(
313        &mut self,
314        parent: ObjectID,
315        child: ObjectID,
316    ) -> PartialVMResult<bool> {
317        self.child_object_store.object_exists(parent, child)
318    }
319
320    pub(crate) fn child_object_exists_and_has_type(
321        &mut self,
322        parent: ObjectID,
323        child: ObjectID,
324        child_type: &MoveObjectType,
325    ) -> PartialVMResult<bool> {
326        self.child_object_store
327            .object_exists_and_has_type(parent, child, child_type)
328    }
329
330    pub(super) fn receive_object(
331        &mut self,
332        parent: ObjectID,
333        child: ObjectID,
334        child_version: SequenceNumber,
335        child_ty: &Type,
336        child_layout: &R::MoveTypeLayout,
337        child_fully_annotated_layout: &MoveTypeLayout,
338        child_move_type: MoveObjectType,
339    ) -> PartialVMResult<Option<ObjectResult<Value>>> {
340        let Some((value, obj_meta)) = self.child_object_store.receive_object(
341            parent,
342            child,
343            child_version,
344            child_ty,
345            child_layout,
346            child_fully_annotated_layout,
347            child_move_type,
348        )?
349        else {
350            return Ok(None);
351        };
352        // NB: It is important that the object only be added to the received set after it has been
353        // fully authenticated and loaded.
354        if self.state.received.insert(child, obj_meta).is_some() {
355            // We should never hit this -- it means that we have received the same object twice which
356            // means we have a duplicated a receiving ticket somehow.
357            return Err(
358                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
359                    "Object {child} at version {child_version} already received. This can only happen \
360                    if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
361                )),
362            );
363        }
364        Ok(Some(value))
365    }
366
367    pub(crate) fn get_or_fetch_child_object(
368        &mut self,
369        parent: ObjectID,
370        child: ObjectID,
371        child_ty: &Type,
372        child_layout: &R::MoveTypeLayout,
373        child_fully_annotated_layout: &MoveTypeLayout,
374        child_move_type: MoveObjectType,
375    ) -> PartialVMResult<ObjectResult<&mut GlobalValue>> {
376        let res = self.child_object_store.get_or_fetch_object(
377            parent,
378            child,
379            child_ty,
380            child_layout,
381            child_fully_annotated_layout,
382            child_move_type,
383        )?;
384        Ok(match res {
385            ObjectResult::MismatchedType => ObjectResult::MismatchedType,
386            ObjectResult::Loaded(child_object) => ObjectResult::Loaded(&mut child_object.value),
387        })
388    }
389
390    pub(crate) fn add_child_object(
391        &mut self,
392        parent: ObjectID,
393        child: ObjectID,
394        child_ty: &Type,
395        child_move_type: MoveObjectType,
396        child_value: Value,
397    ) -> PartialVMResult<()> {
398        self.child_object_store
399            .add_object(parent, child, child_ty, child_move_type, child_value)
400    }
401
402    // returns None if a child object is still borrowed
403    pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
404        std::mem::take(&mut self.state)
405    }
406
407    pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
408        let loaded_child_objects = self.loaded_runtime_objects();
409        let child_effects = self.child_object_store.take_effects();
410        self.state.finish(loaded_child_objects, child_effects)
411    }
412
413    pub(crate) fn all_active_child_objects(
414        &self,
415    ) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
416        self.child_object_store.all_active_objects()
417    }
418
419    pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
420        // The loaded child objects, and the received objects, should be disjoint. If they are not,
421        // this is an error since it could lead to incorrect transaction dependency computations.
422        debug_assert!(self
423            .child_object_store
424            .cached_objects()
425            .keys()
426            .all(|id| !self.state.received.contains_key(id)));
427        self.child_object_store
428            .cached_objects()
429            .iter()
430            .filter_map(|(id, obj_opt)| {
431                obj_opt.as_ref().map(|obj| {
432                    (
433                        *id,
434                        DynamicallyLoadedObjectMetadata {
435                            version: obj.version(),
436                            digest: obj.digest(),
437                            storage_rebate: obj.storage_rebate,
438                            owner: obj.owner.clone(),
439                            previous_transaction: obj.previous_transaction,
440                        },
441                    )
442                })
443            })
444            .chain(
445                self.state
446                    .received
447                    .iter()
448                    .map(|(id, meta)| (*id, meta.clone())),
449            )
450            .collect()
451    }
452
453    /// A map from wrapped objects to the object that wraps them at the beginning of the
454    /// transaction.
455    pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
456        self.child_object_store.wrapped_object_containers().clone()
457    }
458}
459
460pub fn max_event_error(max_events: u64) -> PartialVMError {
461    PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
462        .with_message(format!(
463            "Emitting more than {} events is not allowed",
464            max_events
465        ))
466        .with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
467}
468
469impl ObjectRuntimeState {
470    /// Update `state_view` with the effects of successfully executing a transaction:
471    /// - Given the effects `Op<Value>` of child objects, processes the changes in terms of
472    ///   object writes/deletes
473    /// - Process `transfers` and `input_objects` to determine whether the type of change
474    ///   (WriteKind) to the object
475    /// - Process `deleted_ids` with previously determined information to determine the
476    ///   DeleteKind
477    /// - Passes through user events
478    pub(crate) fn finish(
479        mut self,
480        loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
481        child_object_effects: BTreeMap<ObjectID, ChildObjectEffect>,
482    ) -> Result<RuntimeResults, ExecutionError> {
483        let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
484            .into_iter()
485            .map(|(id, metadata)| {
486                (
487                    id,
488                    LoadedRuntimeObject {
489                        version: metadata.version,
490                        is_modified: false,
491                    },
492                )
493            })
494            .collect();
495        for (child, child_object_effect) in child_object_effects {
496            let ChildObjectEffect {
497                owner: parent,
498                ty,
499                effect,
500            } = child_object_effect;
501
502            if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
503                loaded_child.is_modified = true;
504            }
505
506            match effect {
507                // was modified, so mark it as mutated and transferred
508                Op::Modify(v) => {
509                    debug_assert!(!self.transfers.contains_key(&child));
510                    debug_assert!(!self.new_ids.contains(&child));
511                    debug_assert!(loaded_child_objects.contains_key(&child));
512                    self.transfers
513                        .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
514                }
515
516                Op::New(v) => {
517                    debug_assert!(!self.transfers.contains_key(&child));
518                    self.transfers
519                        .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
520                }
521
522                Op::Delete => {
523                    // was transferred so not actually deleted
524                    if self.transfers.contains_key(&child) {
525                        debug_assert!(!self.deleted_ids.contains(&child));
526                    }
527                    // ID was deleted too was deleted so mark as deleted
528                    if self.deleted_ids.contains(&child) {
529                        debug_assert!(!self.transfers.contains_key(&child));
530                        debug_assert!(!self.new_ids.contains(&child));
531                    }
532                }
533            }
534        }
535        let ObjectRuntimeState {
536            input_objects: _,
537            new_ids,
538            deleted_ids,
539            transfers,
540            events: user_events,
541            total_events_size: _,
542            received,
543        } = self;
544
545        // Check new owners from transfers, reports an error on cycles.
546        // TODO can we have cycles in the new system?
547        check_circular_ownership(
548            transfers
549                .iter()
550                .map(|(id, (owner, _, _))| (*id, owner.clone())),
551        )?;
552        // For both written_objects and deleted_ids, we need to mark the loaded child object as modified.
553        // These may not be covered in the child object effects if they are taken out in one PT command and then
554        // transferred/deleted in a different command. Marking them as modified will allow us properly determine their
555        // mutation category in effects.
556        // TODO: This could get error-prone quickly: what if we forgot to mark an object as modified? There may be a cleaner
557        // sulution.
558        let written_objects: IndexMap<_, _> = transfers
559            .into_iter()
560            .map(|(id, (owner, type_, value))| {
561                if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
562                    loaded_child.is_modified = true;
563                }
564                (id, (owner, type_, value))
565            })
566            .collect();
567        for deleted_id in &deleted_ids {
568            if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
569                loaded_child.is_modified = true;
570            }
571        }
572
573        // Any received objects are viewed as modified. They had to be loaded in order to be
574        // received so they must be in the loaded_child_objects map otherwise it's an invariant
575        // violation.
576        for (received_object, _) in received.into_iter() {
577            match loaded_child_objects.get_mut(&received_object) {
578                Some(loaded_child) => {
579                    loaded_child.is_modified = true;
580                }
581                None => {
582                    return Err(ExecutionError::invariant_violation(format!(
583                        "Failed to find received UID {received_object} in loaded child objects."
584                    )))
585                }
586            }
587        }
588
589        Ok(RuntimeResults {
590            writes: written_objects,
591            user_events,
592            loaded_child_objects,
593            created_object_ids: new_ids,
594            deleted_object_ids: deleted_ids,
595        })
596    }
597
598    pub fn total_events_size(&self) -> u64 {
599        self.total_events_size
600    }
601
602    pub fn incr_total_events_size(&mut self, size: u64) {
603        self.total_events_size += size;
604    }
605}
606
607fn check_circular_ownership(
608    transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
609) -> Result<(), ExecutionError> {
610    let mut object_owner_map = BTreeMap::new();
611    for (id, recipient) in transfers {
612        object_owner_map.remove(&id);
613        match recipient {
614            Owner::AddressOwner(_) | Owner::Shared { .. } | Owner::Immutable => (),
615            Owner::ObjectOwner(new_owner) => {
616                let new_owner: ObjectID = new_owner.into();
617                let mut cur = new_owner;
618                loop {
619                    if cur == id {
620                        return Err(ExecutionError::from_kind(
621                            ExecutionErrorKind::CircularObjectOwnership { object: cur },
622                        ));
623                    }
624                    if let Some(parent) = object_owner_map.get(&cur) {
625                        cur = *parent;
626                    } else {
627                        break;
628                    }
629                }
630                object_owner_map.insert(id, new_owner);
631            }
632            Owner::ConsensusAddressOwner { .. } => {
633                unimplemented!("ConsensusAddressOwner does not exist for this execution version")
634            }
635        }
636    }
637    Ok(())
638}
639
640/// WARNING! This function assumes that the bcs bytes have already been validated,
641/// and it will give an invariant violation otherwise.
642/// In short, we are relying on the invariant that the bytes are valid for objects
643/// in storage.  We do not need this invariant for dev-inspect, as the programmable
644/// transaction execution will validate the bytes before we get to this point.
645pub fn get_all_uids(
646    fully_annotated_layout: &MoveTypeLayout,
647    bcs_bytes: &[u8],
648) -> Result<BTreeSet<ObjectID>, /* invariant violation */ String> {
649    let mut ids = BTreeSet::new();
650    struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
651    struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
652
653    impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
654        type Error = AV::Error;
655
656        fn traverse_struct(
657            &mut self,
658            driver: &mut AV::StructDriver<'_, 'b, 'l>,
659        ) -> Result<(), Self::Error> {
660            if driver.struct_layout().type_ == UID::type_() {
661                while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
662            } else {
663                while driver.next_field(self)?.is_some() {}
664            }
665            Ok(())
666        }
667    }
668
669    impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
670        type Error = AV::Error;
671        fn traverse_address(
672            &mut self,
673            _driver: &AV::ValueDriver<'_, 'b, 'l>,
674            value: AccountAddress,
675        ) -> Result<(), Self::Error> {
676            self.0.insert(value.into());
677            Ok(())
678        }
679    }
680
681    MoveValue::visit_deserialize(
682        bcs_bytes,
683        fully_annotated_layout,
684        &mut UIDTraversal(&mut ids),
685    )
686    .map_err(|e| format!("Failed to deserialize. {e}"))?;
687    Ok(ids)
688}