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