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