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