iota_move_natives_latest/object_runtime/
mod.rs

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