sui_move_natives_latest/object_runtime/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub(crate) mod accumulator;
5mod fingerprint;
6pub(crate) mod object_store;
7
8use crate::object_runtime::object_store::{CacheMetadata, ChildObjectEffectV1};
9
10use self::object_store::{ChildObjectEffectV0, ChildObjectEffects, ObjectResult};
11use super::get_object_id;
12use better_any::{Tid, TidAble};
13use indexmap::map::IndexMap;
14use indexmap::set::IndexSet;
15use move_binary_format::errors::{PartialVMError, PartialVMResult};
16use move_core_types::{
17    account_address::AccountAddress,
18    annotated_value::{MoveTypeLayout, MoveValue},
19    annotated_visitor as AV,
20    effects::Op,
21    language_storage::StructTag,
22    runtime_value as R,
23    vm_status::StatusCode,
24};
25use move_vm_runtime::native_extensions::NativeExtensionMarker;
26use move_vm_types::values::{GlobalValue, Value};
27use object_store::{ActiveChildObject, ChildObjectStore};
28use std::{
29    collections::{BTreeMap, BTreeSet},
30    sync::Arc,
31};
32use sui_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
33use sui_types::{
34    SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_BRIDGE_OBJECT_ID,
35    SUI_CLOCK_OBJECT_ID, SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID,
36    SUI_DISPLAY_REGISTRY_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
37    TypeTag,
38    base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress},
39    committee::EpochId,
40    error::{ExecutionError, ExecutionErrorKind, VMMemoryLimitExceededSubStatusCode},
41    execution::DynamicallyLoadedObjectMetadata,
42    id::UID,
43    metrics::LimitsMetrics,
44    object::{MoveObject, Owner},
45    storage::ChildObjectResolver,
46};
47use tracing::error;
48
49pub use accumulator::*;
50
51pub enum ObjectEvent {
52    /// Transfer to a new address or object. Or make it shared or immutable.
53    Transfer(Owner, MoveObject),
54    /// An object ID is deleted
55    DeleteObjectID(ObjectID),
56}
57
58type Set<K> = IndexSet<K>;
59
60#[derive(Default)]
61pub(crate) struct TestInventories {
62    pub(crate) objects: BTreeMap<ObjectID, Value>,
63    // address inventories. Most recent objects are at the back of the set
64    pub(crate) address_inventories: BTreeMap<SuiAddress, BTreeMap<MoveObjectType, Set<ObjectID>>>,
65    // global inventories.Most recent objects are at the back of the set
66    pub(crate) shared_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
67    pub(crate) immutable_inventory: BTreeMap<MoveObjectType, Set<ObjectID>>,
68    pub(crate) taken_immutable_values: BTreeMap<MoveObjectType, BTreeMap<ObjectID, Value>>,
69    // object has been taken from the inventory
70    pub(crate) taken: BTreeMap<ObjectID, Owner>,
71    // allocated receiving tickets
72    pub(crate) allocated_tickets: BTreeMap<ObjectID, (DynamicallyLoadedObjectMetadata, Value)>,
73}
74
75pub struct LoadedRuntimeObject {
76    pub version: SequenceNumber,
77    pub is_modified: bool,
78}
79
80pub struct RuntimeResults {
81    pub writes: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
82    pub user_events: Vec<(StructTag, Value)>,
83    pub accumulator_events: Vec<MoveAccumulatorEvent>,
84    // Loaded child objects, their loaded version/digest and whether they were modified.
85    pub loaded_child_objects: BTreeMap<ObjectID, LoadedRuntimeObject>,
86    pub created_object_ids: Set<ObjectID>,
87    pub deleted_object_ids: Set<ObjectID>,
88    pub settlement_input_sui: u64,
89    pub settlement_output_sui: u64,
90}
91
92#[derive(Default)]
93pub(crate) struct ObjectRuntimeState {
94    pub(crate) input_objects: BTreeMap<ObjectID, Owner>,
95    // new ids from object::new. This does not contain any new-and-subsequently-deleted ids
96    new_ids: Set<ObjectID>,
97    // contains all ids generated in the txn including any new-and-subsequently-deleted ids
98    generated_ids: Set<ObjectID>,
99    // ids passed to object::delete
100    deleted_ids: Set<ObjectID>,
101    // transfers to a new owner (shared, immutable, object, or account address)
102    // TODO these struct tags can be removed if type_to_type_tag was exposed in the session
103    transfers: IndexMap<ObjectID, (Owner, MoveObjectType, Value)>,
104    events: Vec<(StructTag, Value)>,
105    accumulator_events: Vec<MoveAccumulatorEvent>,
106    // total size of events emitted so far
107    total_events_size: u64,
108    received: IndexMap<ObjectID, DynamicallyLoadedObjectMetadata>,
109    // Used to track SUI conservation in settlement transactions. Settlement transactions
110    // gather up withdraws and deposits from other transactions, and record them to accumulator
111    // fields. The settlement transaction records the total amount of SUI being disbursed here,
112    // so that we can verify that the amount stored in the fields at the end of the transaction
113    // is correct.
114    settlement_input_sui: u64,
115    settlement_output_sui: u64,
116}
117
118#[derive(Tid)]
119pub struct ObjectRuntime<'a> {
120    child_object_store: ChildObjectStore<'a>,
121    // inventories for test scenario
122    pub(crate) test_inventories: TestInventories,
123    // the internal state
124    pub(crate) state: ObjectRuntimeState,
125    // whether or not this TX is gas metered
126    is_metered: bool,
127
128    pub(crate) protocol_config: &'a ProtocolConfig,
129    pub(crate) metrics: Arc<LimitsMetrics>,
130}
131
132impl<'a> NativeExtensionMarker<'a> for ObjectRuntime<'a> {}
133
134pub enum TransferResult {
135    New,
136    SameOwner,
137    OwnerChanged,
138}
139
140pub struct InputObject {
141    pub contained_uids: BTreeSet<ObjectID>,
142    pub version: SequenceNumber,
143    pub owner: Owner,
144}
145
146impl TestInventories {
147    fn new() -> Self {
148        Self::default()
149    }
150}
151
152impl<'a> ObjectRuntime<'a> {
153    pub fn new(
154        object_resolver: &'a dyn ChildObjectResolver,
155        input_objects: BTreeMap<ObjectID, InputObject>,
156        is_metered: bool,
157        protocol_config: &'a ProtocolConfig,
158        metrics: Arc<LimitsMetrics>,
159        epoch_id: EpochId,
160    ) -> Self {
161        let mut input_object_owners = BTreeMap::new();
162        let mut root_version = BTreeMap::new();
163        let mut wrapped_object_containers = BTreeMap::new();
164        for (id, input_object) in input_objects {
165            let InputObject {
166                contained_uids,
167                version,
168                owner,
169            } = input_object;
170            input_object_owners.insert(id, owner);
171            debug_assert!(contained_uids.contains(&id));
172            for contained_uid in contained_uids {
173                root_version.insert(contained_uid, version);
174                if contained_uid != id {
175                    let prev = wrapped_object_containers.insert(contained_uid, id);
176                    debug_assert!(prev.is_none());
177                }
178            }
179        }
180        Self {
181            child_object_store: ChildObjectStore::new(
182                object_resolver,
183                root_version,
184                wrapped_object_containers,
185                is_metered,
186                protocol_config,
187                metrics.clone(),
188                epoch_id,
189            ),
190            test_inventories: TestInventories::new(),
191            state: ObjectRuntimeState {
192                input_objects: input_object_owners,
193                new_ids: Set::new(),
194                generated_ids: Set::new(),
195                deleted_ids: Set::new(),
196                transfers: IndexMap::new(),
197                events: vec![],
198                accumulator_events: vec![],
199                total_events_size: 0,
200                received: IndexMap::new(),
201                settlement_input_sui: 0,
202                settlement_output_sui: 0,
203            },
204            is_metered,
205            protocol_config,
206            metrics,
207        }
208    }
209
210    pub fn new_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
211        // If metered, we use the metered limit (non system tx limit) as the hard limit
212        // This macro takes care of that
213        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
214            self.is_metered,
215            self.state.new_ids.len(),
216            self.protocol_config.max_num_new_move_object_ids(),
217            self.protocol_config.max_num_new_move_object_ids_system_tx(),
218            self.metrics.excessive_new_move_object_ids
219        ) {
220            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
221                .with_message(format!("Creating more than {} IDs is not allowed", lim))
222                .with_sub_status(
223                    VMMemoryLimitExceededSubStatusCode::NEW_ID_COUNT_LIMIT_EXCEEDED as u64,
224                ));
225        };
226
227        // remove from deleted_ids for the case in dynamic fields where the Field object was deleted
228        // and then re-added in a single transaction. In that case, we also skip adding it
229        // to new_ids.
230        let was_present = self.state.deleted_ids.shift_remove(&id);
231        if !was_present {
232            // mark the id as new
233            self.state.generated_ids.insert(id);
234            self.state.new_ids.insert(id);
235        }
236        Ok(())
237    }
238
239    pub fn delete_id(&mut self, id: ObjectID) -> PartialVMResult<()> {
240        // This is defensive because `self.state.deleted_ids` may not indeed
241        // be called based on the `was_new` flag
242        // Metered transactions don't have limits for now
243
244        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
245            self.is_metered,
246            self.state.deleted_ids.len(),
247            self.protocol_config.max_num_deleted_move_object_ids(),
248            self.protocol_config
249                .max_num_deleted_move_object_ids_system_tx(),
250            self.metrics.excessive_deleted_move_object_ids
251        ) {
252            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
253                .with_message(format!("Deleting more than {} IDs is not allowed", lim))
254                .with_sub_status(
255                    VMMemoryLimitExceededSubStatusCode::DELETED_ID_COUNT_LIMIT_EXCEEDED as u64,
256                ));
257        };
258
259        let was_new = self.state.new_ids.shift_remove(&id);
260        if !was_new {
261            self.state.deleted_ids.insert(id);
262        }
263        Ok(())
264    }
265
266    /// In the new PTB adapter, this function is also used for persisting owners at the end
267    /// of the transaction. In which case, we don't check the transfer limits.
268    pub fn transfer(
269        &mut self,
270        owner: Owner,
271        ty: MoveObjectType,
272        obj: Value,
273        end_of_transaction: bool,
274    ) -> PartialVMResult<TransferResult> {
275        let id: ObjectID = get_object_id(obj.copy_value()?)?
276            .value_as::<AccountAddress>()?
277            .into();
278        // - An object is new if it is contained in the new ids or if it is one of the objects
279        //   created during genesis (the system state object or clock).
280        // - Otherwise, check the input objects for the previous owner
281        // - If it was not in the input objects, it must have been wrapped or must have been a
282        //   child object
283        let is_framework_obj = [
284            SUI_SYSTEM_STATE_OBJECT_ID,
285            SUI_CLOCK_OBJECT_ID,
286            SUI_AUTHENTICATOR_STATE_OBJECT_ID,
287            SUI_RANDOMNESS_STATE_OBJECT_ID,
288            SUI_DENY_LIST_OBJECT_ID,
289            SUI_BRIDGE_OBJECT_ID,
290            SUI_ACCUMULATOR_ROOT_OBJECT_ID,
291            SUI_COIN_REGISTRY_OBJECT_ID,
292            SUI_DISPLAY_REGISTRY_OBJECT_ID,
293        ]
294        .contains(&id);
295        let transfer_result = if self.state.new_ids.contains(&id) {
296            TransferResult::New
297        } else if let Some(prev_owner) = self.state.input_objects.get(&id) {
298            match (&owner, prev_owner) {
299                // don't use == for dummy values in Shared or ConsensusAddressOwner
300                (Owner::Shared { .. }, Owner::Shared { .. }) => TransferResult::SameOwner,
301                (
302                    Owner::ConsensusAddressOwner {
303                        owner: new_owner, ..
304                    },
305                    Owner::ConsensusAddressOwner {
306                        owner: old_owner, ..
307                    },
308                ) if new_owner == old_owner => TransferResult::SameOwner,
309                (new, old) if new == old => TransferResult::SameOwner,
310                _ => TransferResult::OwnerChanged,
311            }
312        } else if is_framework_obj {
313            // framework objects are always created when they are transferred, but the id is
314            // hard-coded so it is not yet in new_ids or generated_ids
315            self.state.new_ids.insert(id);
316            self.state.generated_ids.insert(id);
317            TransferResult::New
318        } else {
319            TransferResult::OwnerChanged
320        };
321        // assert!(end of transaction ==> same owner)
322        if end_of_transaction && !matches!(transfer_result, TransferResult::SameOwner) {
323            return Err(
324                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
325                    .with_message(format!("Untransferred object {} had its owner change", id)),
326            );
327        }
328
329        // Metered transactions don't have limits for now
330
331        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
332            // TODO: is this not redundant? Metered TX implies framework obj cannot be transferred
333            // We have higher limits for unmetered transactions and framework obj
334            // We don't check the limit for objects whose owner is persisted at the end of the
335            // transaction
336            self.is_metered && !is_framework_obj && !end_of_transaction,
337            self.state.transfers.len(),
338            self.protocol_config.max_num_transferred_move_object_ids(),
339            self.protocol_config
340                .max_num_transferred_move_object_ids_system_tx(),
341            self.metrics.excessive_transferred_move_object_ids
342        ) {
343            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
344                .with_message(format!("Transferring more than {} IDs is not allowed", lim))
345                .with_sub_status(
346                    VMMemoryLimitExceededSubStatusCode::TRANSFER_ID_COUNT_LIMIT_EXCEEDED as u64,
347                ));
348        };
349
350        self.state.transfers.insert(id, (owner, ty, obj));
351        Ok(transfer_result)
352    }
353
354    pub fn emit_event(&mut self, tag: StructTag, event: Value) -> PartialVMResult<()> {
355        if self.state.events.len() >= (self.protocol_config.max_num_event_emit() as usize) {
356            return Err(max_event_error(self.protocol_config.max_num_event_emit()));
357        }
358        self.state.events.push((tag, event));
359        Ok(())
360    }
361
362    pub fn take_user_events(&mut self) -> Vec<(StructTag, Value)> {
363        std::mem::take(&mut self.state.events)
364    }
365
366    pub fn emit_accumulator_event(
367        &mut self,
368        accumulator_id: ObjectID,
369        action: MoveAccumulatorAction,
370        target_addr: AccountAddress,
371        target_ty: TypeTag,
372        value: MoveAccumulatorValue,
373    ) -> PartialVMResult<()> {
374        let event = MoveAccumulatorEvent {
375            accumulator_id,
376            action,
377            target_addr,
378            target_ty,
379            value,
380        };
381        self.state.accumulator_events.push(event);
382        Ok(())
383    }
384
385    pub(crate) fn child_object_exists(
386        &mut self,
387        parent: ObjectID,
388        child: ObjectID,
389    ) -> PartialVMResult<CacheMetadata<bool>> {
390        self.child_object_store.object_exists(parent, child)
391    }
392
393    pub(crate) fn child_object_exists_and_has_type(
394        &mut self,
395        parent: ObjectID,
396        child: ObjectID,
397        child_type: &MoveObjectType,
398    ) -> PartialVMResult<CacheMetadata<bool>> {
399        self.child_object_store
400            .object_exists_and_has_type(parent, child, child_type)
401    }
402
403    pub(super) fn receive_object(
404        &mut self,
405        parent: ObjectID,
406        child: ObjectID,
407        child_version: SequenceNumber,
408        child_layout: &R::MoveTypeLayout,
409        child_fully_annotated_layout: &MoveTypeLayout,
410        child_move_type: MoveObjectType,
411    ) -> PartialVMResult<Option<ObjectResult<CacheMetadata<Value>>>> {
412        let Some((value, obj_meta)) = self.child_object_store.receive_object(
413            parent,
414            child,
415            child_version,
416            child_layout,
417            child_fully_annotated_layout,
418            child_move_type,
419        )?
420        else {
421            return Ok(None);
422        };
423        // NB: It is important that the object only be added to the received set after it has been
424        // fully authenticated and loaded.
425        if self.state.received.insert(child, obj_meta).is_some() {
426            // We should never hit this -- it means that we have received the same object twice which
427            // means we have a duplicated a receiving ticket somehow.
428            return Err(
429                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
430                    "Object {child} at version {child_version} already received. This can only happen \
431                    if multiple `Receiving` arguments exist for the same object in the transaction which is impossible."
432                )),
433            );
434        }
435        Ok(Some(value))
436    }
437
438    pub(crate) fn get_or_fetch_child_object(
439        &mut self,
440        parent: ObjectID,
441        child: ObjectID,
442        child_layout: &R::MoveTypeLayout,
443        child_fully_annotated_layout: &MoveTypeLayout,
444        child_move_type: MoveObjectType,
445    ) -> PartialVMResult<ObjectResult<CacheMetadata<&mut GlobalValue>>> {
446        let res = self.child_object_store.get_or_fetch_object(
447            parent,
448            child,
449            child_layout,
450            child_fully_annotated_layout,
451            child_move_type,
452        )?;
453        Ok(match res {
454            ObjectResult::MismatchedType => ObjectResult::MismatchedType,
455            ObjectResult::Loaded((cache_info, child_object)) => {
456                ObjectResult::Loaded((cache_info, &mut child_object.value))
457            }
458        })
459    }
460
461    pub(crate) fn add_child_object(
462        &mut self,
463        parent: ObjectID,
464        child: ObjectID,
465        child_move_type: MoveObjectType,
466        child_value: Value,
467    ) -> PartialVMResult<()> {
468        self.child_object_store
469            .add_object(parent, child, child_move_type, child_value)
470    }
471
472    pub(crate) fn config_setting_unsequenced_read(
473        &mut self,
474        config_id: ObjectID,
475        name_df_id: ObjectID,
476        field_setting_layout: &R::MoveTypeLayout,
477        field_setting_object_type: &MoveObjectType,
478    ) -> Option<Value> {
479        match self.child_object_store.config_setting_unsequenced_read(
480            config_id,
481            name_df_id,
482            field_setting_layout,
483            field_setting_object_type,
484        ) {
485            Err(e) => {
486                error!(
487                    "Failed to read config setting.
488                    config_id: {config_id},
489                    name_df_id: {name_df_id},
490                    field_setting_object_type:  {field_setting_object_type:?},
491                    error: {e}"
492                );
493                None
494            }
495            Ok(ObjectResult::MismatchedType) | Ok(ObjectResult::Loaded(None)) => None,
496            Ok(ObjectResult::Loaded(Some(value))) => Some(value),
497        }
498    }
499
500    pub(super) fn config_setting_cache_update(
501        &mut self,
502        config_id: ObjectID,
503        name_df_id: ObjectID,
504        setting_value_object_type: MoveObjectType,
505        value: Option<Value>,
506    ) {
507        self.child_object_store.config_setting_cache_update(
508            config_id,
509            name_df_id,
510            setting_value_object_type,
511            value,
512        )
513    }
514
515    // returns None if a child object is still borrowed
516    pub(crate) fn take_state(&mut self) -> ObjectRuntimeState {
517        std::mem::take(&mut self.state)
518    }
519
520    pub fn is_deleted(&self, id: &ObjectID) -> bool {
521        self.state.deleted_ids.contains(id)
522    }
523
524    pub fn is_transferred(&self, id: &ObjectID) -> Option<Owner> {
525        self.state
526            .transfers
527            .get(id)
528            .map(|(owner, _, _)| owner.clone())
529    }
530
531    pub fn finish(mut self) -> Result<RuntimeResults, ExecutionError> {
532        let loaded_child_objects = self.loaded_runtime_objects();
533        let child_effects = self.child_object_store.take_effects().map_err(|e| {
534            ExecutionError::invariant_violation(format!("Failed to take child object effects: {e}"))
535        })?;
536        self.state.finish(loaded_child_objects, child_effects)
537    }
538
539    pub(crate) fn all_active_child_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
540        self.child_object_store.all_active_objects()
541    }
542
543    pub fn loaded_runtime_objects(&self) -> BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata> {
544        // The loaded child objects, and the received objects, should be disjoint. If they are not,
545        // this is an error since it could lead to incorrect transaction dependency computations.
546        debug_assert!(
547            self.child_object_store
548                .cached_objects()
549                .keys()
550                .all(|id| !self.state.received.contains_key(id))
551        );
552        self.child_object_store
553            .cached_objects()
554            .iter()
555            .filter_map(|(id, obj_opt)| {
556                obj_opt.as_ref().map(|obj| {
557                    (
558                        *id,
559                        DynamicallyLoadedObjectMetadata {
560                            version: obj.version(),
561                            digest: obj.digest(),
562                            storage_rebate: obj.storage_rebate,
563                            owner: obj.owner.clone(),
564                            previous_transaction: obj.previous_transaction,
565                        },
566                    )
567                })
568            })
569            .chain(
570                self.state
571                    .received
572                    .iter()
573                    .map(|(id, meta)| (*id, meta.clone())),
574            )
575            .collect()
576    }
577
578    /// A map from wrapped objects to the object that wraps them at the beginning of the
579    /// transaction.
580    pub fn wrapped_object_containers(&self) -> BTreeMap<ObjectID, ObjectID> {
581        self.child_object_store.wrapped_object_containers().clone()
582    }
583
584    pub fn record_settlement_sui_conservation(&mut self, input_sui: u64, output_sui: u64) {
585        self.state.settlement_input_sui += input_sui;
586        self.state.settlement_output_sui += output_sui;
587    }
588
589    /// Return the set of all object IDs that were created during this transaction, including any
590    /// object IDs that were created and then deleted during the transaction.
591    pub fn generated_object_ids(&self) -> BTreeSet<ObjectID> {
592        self.state.generated_ids.iter().cloned().collect()
593    }
594}
595
596pub fn max_event_error(max_events: u64) -> PartialVMError {
597    PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
598        .with_message(format!(
599            "Emitting more than {} events is not allowed",
600            max_events
601        ))
602        .with_sub_status(VMMemoryLimitExceededSubStatusCode::EVENT_COUNT_LIMIT_EXCEEDED as u64)
603}
604
605impl ObjectRuntimeState {
606    /// Update `state_view` with the effects of successfully executing a transaction:
607    /// - Given the effects `Op<Value>` of child objects, processes the changes in terms of
608    ///   object writes/deletes
609    /// - Process `transfers` and `input_objects` to determine whether the type of change
610    ///   (WriteKind) to the object
611    /// - Process `deleted_ids` with previously determined information to determine the
612    ///   DeleteKind
613    /// - Passes through user events
614    pub(crate) fn finish(
615        mut self,
616        loaded_child_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
617        child_object_effects: ChildObjectEffects,
618    ) -> Result<RuntimeResults, ExecutionError> {
619        let mut loaded_child_objects: BTreeMap<_, _> = loaded_child_objects
620            .into_iter()
621            .map(|(id, metadata)| {
622                (
623                    id,
624                    LoadedRuntimeObject {
625                        version: metadata.version,
626                        is_modified: false,
627                    },
628                )
629            })
630            .collect();
631        self.apply_child_object_effects(&mut loaded_child_objects, child_object_effects);
632        let ObjectRuntimeState {
633            input_objects: _,
634            new_ids,
635            generated_ids,
636            deleted_ids,
637            transfers,
638            events: user_events,
639            total_events_size: _,
640            received,
641            accumulator_events,
642            settlement_input_sui,
643            settlement_output_sui,
644        } = self;
645
646        // The set of new ids is a subset of the generated ids.
647        debug_assert!(new_ids.is_subset(&generated_ids));
648
649        // Check new owners from transfers, reports an error on cycles.
650        // TODO can we have cycles in the new system?
651        check_circular_ownership(
652            transfers
653                .iter()
654                .map(|(id, (owner, _, _))| (*id, owner.clone())),
655        )?;
656        // For both written_objects and deleted_ids, we need to mark the loaded child object as modified.
657        // These may not be covered in the child object effects if they are taken out in one PT command and then
658        // transferred/deleted in a different command. Marking them as modified will allow us properly determine their
659        // mutation category in effects.
660        // TODO: This could get error-prone quickly: what if we forgot to mark an object as modified? There may be a cleaner
661        // sulution.
662        let written_objects: IndexMap<_, _> = transfers
663            .into_iter()
664            .map(|(id, (owner, type_, value))| {
665                if let Some(loaded_child) = loaded_child_objects.get_mut(&id) {
666                    loaded_child.is_modified = true;
667                }
668                (id, (owner, type_, value))
669            })
670            .collect();
671        for deleted_id in &deleted_ids {
672            if let Some(loaded_child) = loaded_child_objects.get_mut(deleted_id) {
673                loaded_child.is_modified = true;
674            }
675        }
676
677        // Any received objects are viewed as modified. They had to be loaded in order to be
678        // received so they must be in the loaded_child_objects map otherwise it's an invariant
679        // violation.
680        for (received_object, _) in received.into_iter() {
681            match loaded_child_objects.get_mut(&received_object) {
682                Some(loaded_child) => {
683                    loaded_child.is_modified = true;
684                }
685                None => {
686                    return Err(ExecutionError::invariant_violation(format!(
687                        "Failed to find received UID {received_object} in loaded child objects."
688                    )));
689                }
690            }
691        }
692
693        Ok(RuntimeResults {
694            writes: written_objects,
695            user_events,
696            accumulator_events,
697            loaded_child_objects,
698            created_object_ids: new_ids,
699            deleted_object_ids: deleted_ids,
700            settlement_input_sui,
701            settlement_output_sui,
702        })
703    }
704
705    pub fn events(&self) -> &[(StructTag, Value)] {
706        &self.events
707    }
708
709    pub fn total_events_size(&self) -> u64 {
710        self.total_events_size
711    }
712
713    pub fn incr_total_events_size(&mut self, size: u64) {
714        self.total_events_size += size;
715    }
716
717    fn apply_child_object_effects(
718        &mut self,
719        loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
720        child_object_effects: ChildObjectEffects,
721    ) {
722        match child_object_effects {
723            ChildObjectEffects::V0(child_object_effects) => {
724                self.apply_child_object_effects_v0(loaded_child_objects, child_object_effects)
725            }
726            ChildObjectEffects::V1(child_object_effects) => {
727                self.apply_child_object_effects_v1(loaded_child_objects, child_object_effects)
728            }
729        }
730    }
731
732    fn apply_child_object_effects_v0(
733        &mut self,
734        loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
735        child_object_effects: BTreeMap<ObjectID, ChildObjectEffectV0>,
736    ) {
737        for (child, child_object_effect) in child_object_effects {
738            let ChildObjectEffectV0 {
739                owner: parent,
740                ty,
741                effect,
742            } = child_object_effect;
743
744            if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
745                loaded_child.is_modified = true;
746            }
747
748            match effect {
749                // was modified, so mark it as mutated and transferred
750                Op::Modify(v) => {
751                    debug_assert!(!self.transfers.contains_key(&child));
752                    debug_assert!(!self.new_ids.contains(&child));
753                    debug_assert!(loaded_child_objects.contains_key(&child));
754                    self.transfers
755                        .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
756                }
757
758                Op::New(v) => {
759                    debug_assert!(!self.transfers.contains_key(&child));
760                    self.transfers
761                        .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
762                }
763
764                Op::Delete => {
765                    // was transferred so not actually deleted
766                    if self.transfers.contains_key(&child) {
767                        debug_assert!(!self.deleted_ids.contains(&child));
768                    }
769                    // ID was deleted too was deleted so mark as deleted
770                    if self.deleted_ids.contains(&child) {
771                        debug_assert!(!self.transfers.contains_key(&child));
772                        debug_assert!(!self.new_ids.contains(&child));
773                    }
774                }
775            }
776        }
777    }
778
779    fn apply_child_object_effects_v1(
780        &mut self,
781        loaded_child_objects: &mut BTreeMap<ObjectID, LoadedRuntimeObject>,
782        child_object_effects: BTreeMap<ObjectID, ChildObjectEffectV1>,
783    ) {
784        for (child, child_object_effect) in child_object_effects {
785            let ChildObjectEffectV1 {
786                owner: parent,
787                ty,
788                final_value,
789                object_changed,
790            } = child_object_effect;
791
792            if object_changed {
793                if let Some(loaded_child) = loaded_child_objects.get_mut(&child) {
794                    loaded_child.is_modified = true;
795                }
796
797                match final_value {
798                    None => {
799                        // Value was changed and is no longer present, it may have been wrapped,
800                        // transferred, or deleted.
801
802                        // If it was transferred, it should not have been deleted
803                        // transferred ==> !deleted
804                        debug_assert!(
805                            !self.transfers.contains_key(&child)
806                                || !self.deleted_ids.contains(&child)
807                        );
808                        // If it was deleted, it should not have been transferred. Additionally,
809                        // if it was deleted, it should no longer be marked as new.
810                        // deleted ==> !transferred and !new
811                        debug_assert!(
812                            !self.deleted_ids.contains(&child)
813                                || (!self.transfers.contains_key(&child)
814                                    && !self.new_ids.contains(&child))
815                        );
816                    }
817                    Some(v) => {
818                        // Value was changed (or the owner was changed)
819
820                        // It is still a dynamic field so it should not be transferred or deleted
821                        debug_assert!(
822                            !self.transfers.contains_key(&child)
823                                && !self.deleted_ids.contains(&child)
824                        );
825                        // If it was loaded, it must have been new. But keep in mind if it was not
826                        // loaded, it is not necessarily new since it could have been
827                        // input/wrapped/received
828                        // loaded ==> !new
829                        debug_assert!(
830                            !loaded_child_objects.contains_key(&child)
831                                || !self.new_ids.contains(&child)
832                        );
833                        // Mark the mutation of the new value and/or parent.
834                        self.transfers
835                            .insert(child, (Owner::ObjectOwner(parent.into()), ty, v));
836                    }
837                }
838            } else {
839                // The object was not changed.
840                // If it was created,
841                //   it must now have been moved elsewhere (wrapped or transferred).
842                // If it was deleted or transferred,
843                //   it must have been an input/received/wrapped object.
844                // In either case, the value must now have been moved elsewhere, giving us:
845                // (new or deleted or transferred or received) ==> no value
846                // which is equivalent to:
847                // has value ==> (!deleted and !transferred and !input)
848                // If the value is still there, it must have been loaded.
849                // Combining these to give us the check:
850                // has value ==> (loaded and !deleted and !transferred and !input and !received)
851                // which is equivalent to:
852                // !(no value) ==> (loaded and !deleted and !transferred and !input and !received)
853                debug_assert!(
854                    final_value.is_none()
855                        || (loaded_child_objects.contains_key(&child)
856                            && !self.deleted_ids.contains(&child)
857                            && !self.transfers.contains_key(&child)
858                            && !self.input_objects.contains_key(&child)
859                            && !self.received.contains_key(&child))
860                );
861                // In any case, if it was not changed, it should not be marked as modified
862                debug_assert!(
863                    loaded_child_objects
864                        .get(&child)
865                        .is_none_or(|loaded_child| !loaded_child.is_modified)
866                );
867            }
868        }
869    }
870}
871
872fn check_circular_ownership(
873    transfers: impl IntoIterator<Item = (ObjectID, Owner)>,
874) -> Result<(), ExecutionError> {
875    let mut object_owner_map = BTreeMap::new();
876    for (id, recipient) in transfers {
877        object_owner_map.remove(&id);
878        match recipient {
879            Owner::AddressOwner(_)
880            | Owner::Shared { .. }
881            | Owner::Immutable
882            | Owner::ConsensusAddressOwner { .. } => (),
883            Owner::ObjectOwner(new_owner) => {
884                let new_owner: ObjectID = new_owner.into();
885                let mut cur = new_owner;
886                loop {
887                    if cur == id {
888                        return Err(ExecutionError::from_kind(
889                            ExecutionErrorKind::CircularObjectOwnership { object: cur },
890                        ));
891                    }
892                    if let Some(parent) = object_owner_map.get(&cur) {
893                        cur = *parent;
894                    } else {
895                        break;
896                    }
897                }
898                object_owner_map.insert(id, new_owner);
899            }
900        }
901    }
902    Ok(())
903}
904
905/// WARNING! This function assumes that the bcs bytes have already been validated,
906/// and it will give an invariant violation otherwise.
907/// In short, we are relying on the invariant that the bytes are valid for objects
908/// in storage.  We do not need this invariant for dev-inspect, as the programmable
909/// transaction execution will validate the bytes before we get to this point.
910pub fn get_all_uids(
911    fully_annotated_layout: &MoveTypeLayout,
912    bcs_bytes: &[u8],
913) -> Result<BTreeSet<ObjectID>, /* invariant violation */ String> {
914    let mut ids = BTreeSet::new();
915    struct UIDTraversal<'i>(&'i mut BTreeSet<ObjectID>);
916    struct UIDCollector<'i>(&'i mut BTreeSet<ObjectID>);
917
918    impl<'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'_> {
919        type Error = AV::Error;
920
921        fn traverse_struct(
922            &mut self,
923            driver: &mut AV::StructDriver<'_, 'b, 'l>,
924        ) -> Result<(), Self::Error> {
925            if driver.struct_layout().type_ == UID::type_() {
926                while driver.next_field(&mut UIDCollector(self.0))?.is_some() {}
927            } else {
928                while driver.next_field(self)?.is_some() {}
929            }
930            Ok(())
931        }
932    }
933
934    impl<'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'_> {
935        type Error = AV::Error;
936        fn traverse_address(
937            &mut self,
938            _driver: &AV::ValueDriver<'_, 'b, 'l>,
939            value: AccountAddress,
940        ) -> Result<(), Self::Error> {
941            self.0.insert(value.into());
942            Ok(())
943        }
944    }
945
946    MoveValue::visit_deserialize(
947        bcs_bytes,
948        fully_annotated_layout,
949        &mut UIDTraversal(&mut ids),
950    )
951    .map_err(|e| format!("Failed to deserialize. {e}"))?;
952    Ok(ids)
953}