Skip to main content

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