sui_adapter_latest/
temporary_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::gas_charger::{GasCharger, PaymentLocation};
5use mysten_metrics::monitored_scope;
6use parking_lot::RwLock;
7use std::collections::{BTreeMap, BTreeSet, HashSet};
8use sui_protocol_config::ProtocolConfig;
9use sui_types::accumulator_event::AccumulatorEvent;
10use sui_types::accumulator_root::AccumulatorObjId;
11use sui_types::base_types::VersionDigest;
12use sui_types::committee::EpochId;
13use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
14use sui_types::effects::{
15    AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
16    TransactionEvents,
17};
18use sui_types::execution::{
19    DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
20};
21use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
22use sui_types::inner_temporary_store::InnerTemporaryStore;
23use sui_types::layout_resolver::LayoutResolver;
24use sui_types::object::Data;
25use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
26use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
27use sui_types::{
28    SUI_DENY_LIST_OBJECT_ID,
29    base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
30    effects::EffectsObjectChange,
31    error::{ExecutionError, SuiResult},
32    gas::GasCostSummary,
33    object::Object,
34    object::Owner,
35    storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
36    transaction::InputObjects,
37};
38use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
39
40pub struct TemporaryStore<'backing> {
41    // The backing store for retrieving Move packages onchain.
42    // When executing a Move call, the dependent packages are not going to be
43    // in the input objects. They will be fetched from the backing store.
44    // Also used for fetching the backing parent_sync to get the last known version for wrapped
45    // objects
46    store: &'backing dyn BackingStore,
47    tx_digest: TransactionDigest,
48    input_objects: BTreeMap<ObjectID, Object>,
49
50    /// Store the original versions of the non-exclusive write inputs, in order to detect
51    /// mutations (which are illegal, but not prevented by the type system).
52    non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
53
54    stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber /* start_version */>,
55    /// The version to assign to all objects written by the transaction using this store.
56    lamport_timestamp: SequenceNumber,
57    /// Inputs that will be mutated by the transaction. Does not include NonExclusiveWrite inputs,
58    /// which can be taken as `&mut T` but cannot be directly mutated.
59    mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
60    execution_results: ExecutionResultsV2,
61    /// Objects that were loaded during execution (dynamic fields + received objects).
62    loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
63    /// A map from wrapped object to its container. Used during expensive invariant checks.
64    wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
65    protocol_config: &'backing ProtocolConfig,
66
67    /// Every package that was loaded from DB store during execution.
68    /// These packages were not previously loaded into the temporary store.
69    runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
70
71    /// The set of objects that we may receive during execution. Not guaranteed to receive all, or
72    /// any of the objects referenced in this set.
73    receiving_objects: Vec<ObjectRef>,
74
75    /// The set of all generated object IDs from the object runtime during the transaction. This includes any
76    /// created-and-then-deleted objects in addition to any `new_ids` which contains only the set
77    /// of created (but not deleted) IDs in the transaction.
78    generated_runtime_ids: BTreeSet<ObjectID>,
79
80    // TODO: Now that we track epoch here, there are a few places we don't need to pass it around.
81    /// The current epoch.
82    cur_epoch: EpochId,
83
84    /// The set of per-epoch config objects that were loaded during execution, and are not in the
85    /// input objects. This allows us to commit them to the effects.
86    loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
87}
88
89impl<'backing> TemporaryStore<'backing> {
90    /// Creates a new store associated with an authority store, and populates it with
91    /// initial objects.
92    pub fn new(
93        store: &'backing dyn BackingStore,
94        input_objects: InputObjects,
95        receiving_objects: Vec<ObjectRef>,
96        tx_digest: TransactionDigest,
97        protocol_config: &'backing ProtocolConfig,
98        cur_epoch: EpochId,
99    ) -> Self {
100        let mutable_input_refs = input_objects.exclusive_mutable_inputs();
101        let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
102
103        let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
104        let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
105        let objects = input_objects.into_object_map();
106        #[cfg(debug_assertions)]
107        {
108            // Ensure that input objects and receiving objects must not overlap.
109            assert!(
110                objects
111                    .keys()
112                    .collect::<HashSet<_>>()
113                    .intersection(
114                        &receiving_objects
115                            .iter()
116                            .map(|oref| &oref.0)
117                            .collect::<HashSet<_>>()
118                    )
119                    .next()
120                    .is_none()
121            );
122        }
123        Self {
124            store,
125            tx_digest,
126            input_objects: objects,
127            non_exclusive_input_original_versions,
128            stream_ended_consensus_objects,
129            lamport_timestamp,
130            mutable_input_refs,
131            execution_results: ExecutionResultsV2::default(),
132            protocol_config,
133            loaded_runtime_objects: BTreeMap::new(),
134            wrapped_object_containers: BTreeMap::new(),
135            runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
136            receiving_objects,
137            generated_runtime_ids: BTreeSet::new(),
138            cur_epoch,
139            loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
140        }
141    }
142
143    // Helpers to access private fields
144    pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
145        &self.input_objects
146    }
147
148    pub fn update_object_version_and_prev_tx(&mut self) {
149        self.execution_results.update_version_and_previous_tx(
150            self.lamport_timestamp,
151            self.tx_digest,
152            &self.input_objects,
153            self.protocol_config.reshare_at_same_initial_version(),
154        );
155
156        #[cfg(debug_assertions)]
157        {
158            self.check_invariants();
159        }
160    }
161
162    fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
163        let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
164        let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
165        for event in &self.execution_results.accumulator_events {
166            match &event.write.value {
167                AccumulatorValue::Integer(amount) => match event.write.operation {
168                    AccumulatorOperation::Split => {
169                        let entry = running_net_withdraws
170                            .entry(event.accumulator_obj)
171                            .or_default();
172                        *entry += *amount as i128;
173                        if *entry > 0 {
174                            let max_entry = running_max_withdraws
175                                .entry(event.accumulator_obj)
176                                .or_default();
177                            *max_entry = (*max_entry).max(*entry as u128);
178                        }
179                    }
180                    AccumulatorOperation::Merge => {
181                        let entry = running_net_withdraws
182                            .entry(event.accumulator_obj)
183                            .or_default();
184                        *entry -= *amount as i128;
185                    }
186                },
187                AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
188            }
189        }
190        running_max_withdraws
191    }
192
193    /// Ensure that there is one entry for each accumulator object in the accumulator events.
194    fn merge_accumulator_events(&mut self) {
195        self.execution_results.accumulator_events = self
196            .execution_results
197            .accumulator_events
198            .iter()
199            .fold(
200                BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
201                |mut map, event| {
202                    map.entry(event.accumulator_obj)
203                        .or_default()
204                        .push(event.write.clone());
205                    map
206                },
207            )
208            .into_iter()
209            .map(|(obj_id, writes)| {
210                AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
211            })
212            .collect();
213    }
214
215    /// Break up the structure and return its internal stores (objects, active_inputs, written, deleted)
216    pub fn into_inner(
217        self,
218        accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
219    ) -> InnerTemporaryStore {
220        let results = self.execution_results;
221        InnerTemporaryStore {
222            input_objects: self.input_objects,
223            stream_ended_consensus_objects: self.stream_ended_consensus_objects,
224            mutable_inputs: self.mutable_input_refs,
225            written: results.written_objects,
226            events: TransactionEvents {
227                data: results.user_events,
228            },
229            accumulator_events: results.accumulator_events,
230            loaded_runtime_objects: self.loaded_runtime_objects,
231            runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
232            lamport_version: self.lamport_timestamp,
233            binary_config: self.protocol_config.binary_config(None),
234            accumulator_running_max_withdraws,
235        }
236    }
237
238    /// For every object from active_inputs (i.e. all mutable objects), if they are not
239    /// mutated during the transaction execution, force mutating them by incrementing the
240    /// sequence number. This is required to achieve safety.
241    pub(crate) fn ensure_active_inputs_mutated(&mut self) {
242        let mut to_be_updated = vec![];
243        // Note: we do not mutate input objects if they are non-exclusive write
244        for id in self.mutable_input_refs.keys() {
245            if !self.execution_results.modified_objects.contains(id) {
246                // We cannot update here but have to push to `to_be_updated` and update later
247                // because the for loop is holding a reference to `self`, and calling
248                // `self.mutate_input_object` requires a mutable reference to `self`.
249                to_be_updated.push(self.input_objects[id].clone());
250            }
251        }
252        for object in to_be_updated {
253            // The object must be mutated as it was present in the input objects
254            self.mutate_input_object(object.clone());
255        }
256    }
257
258    fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
259        let results = &self.execution_results;
260        let all_ids = results
261            .created_object_ids
262            .iter()
263            .chain(&results.deleted_object_ids)
264            .chain(&results.modified_objects)
265            .chain(results.written_objects.keys())
266            .collect::<BTreeSet<_>>();
267        all_ids
268            .into_iter()
269            .map(|id| {
270                (
271                    *id,
272                    EffectsObjectChange::new(
273                        self.get_object_modified_at(id)
274                            .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
275                        results.written_objects.get(id),
276                        results.created_object_ids.contains(id),
277                        results.deleted_object_ids.contains(id),
278                    ),
279                )
280            })
281            .chain(results.accumulator_events.iter().cloned().map(
282                |AccumulatorEvent {
283                     accumulator_obj,
284                     write,
285                 }| {
286                    (
287                        *accumulator_obj.inner(),
288                        EffectsObjectChange::new_from_accumulator_write(write),
289                    )
290                },
291            ))
292            .collect()
293    }
294
295    pub fn into_effects(
296        mut self,
297        shared_object_refs: Vec<SharedInput>,
298        transaction_digest: &TransactionDigest,
299        mut transaction_dependencies: BTreeSet<TransactionDigest>,
300        gas_cost_summary: GasCostSummary,
301        status: ExecutionStatus,
302        gas_charger: &mut GasCharger,
303        epoch: EpochId,
304    ) -> (InnerTemporaryStore, TransactionEffects) {
305        self.update_object_version_and_prev_tx();
306        // This must happens before merge_accumulator_events.
307        let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
308        self.merge_accumulator_events();
309
310        // Regardless of execution status (including aborts), we insert the previous transaction
311        // for any successfully received objects during the transaction.
312        for (id, expected_version, expected_digest) in &self.receiving_objects {
313            // If the receiving object is in the loaded runtime objects, then that means that it
314            // was actually successfully loaded (so existed, and there was authenticated mutable
315            // access to it). So we insert the previous transaction as a dependency.
316            if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
317                // Check that the expected version, digest, and owner match the loaded version,
318                // digest, and owner. If they don't then don't register a dependency.
319                // This is because this could be "spoofed" by loading a dynamic object field.
320                let loaded_via_receive = obj_meta.version == *expected_version
321                    && obj_meta.digest == *expected_digest
322                    && obj_meta.owner.is_address_owned();
323                if loaded_via_receive {
324                    transaction_dependencies.insert(obj_meta.previous_transaction);
325                }
326            }
327        }
328
329        assert!(self.protocol_config.enable_effects_v2());
330
331        // In the case of special transactions that don't require a gas object,
332        // we don't really care about the effects to gas, just use the input for it.
333        // Gas coins are guaranteed to be at least size 1 and if more than 1
334        // the first coin is where all the others are merged.
335        let gas_coin = gas_charger
336            .gas_payment_amount()
337            .and_then(|gp| match gp.location {
338                PaymentLocation::Coin(coin_id) => Some(coin_id),
339                PaymentLocation::AddressBalance(_) => None,
340            });
341
342        let object_changes = self.get_object_changes();
343
344        let lamport_version = self.lamport_timestamp;
345        // TODO: Cleanup this clone. Potentially add unchanged_shraed_objects directly to InnerTempStore.
346        let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
347        let inner = self.into_inner(accumulator_running_max_withdraws);
348
349        let effects = TransactionEffects::new_from_execution_v2(
350            status,
351            epoch,
352            gas_cost_summary,
353            // TODO: Provide the list of read-only shared objects directly.
354            shared_object_refs,
355            loaded_per_epoch_config_objects,
356            *transaction_digest,
357            lamport_version,
358            object_changes,
359            gas_coin,
360            if inner.events.data.is_empty() {
361                None
362            } else {
363                Some(inner.events.digest())
364            },
365            transaction_dependencies.into_iter().collect(),
366        );
367
368        (inner, effects)
369    }
370
371    /// An internal check of the invariants (will only fire in debug)
372    #[cfg(debug_assertions)]
373    fn check_invariants(&self) {
374        // Check not both deleted and written
375        debug_assert!(
376            {
377                self.execution_results
378                    .written_objects
379                    .keys()
380                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
381            },
382            "Object both written and deleted."
383        );
384
385        // Check all mutable inputs are modified
386        debug_assert!(
387            {
388                self.mutable_input_refs
389                    .keys()
390                    .all(|id| self.execution_results.modified_objects.contains(id))
391            },
392            "Mutable input not modified."
393        );
394
395        debug_assert!(
396            {
397                self.execution_results
398                    .written_objects
399                    .values()
400                    .all(|obj| obj.previous_transaction == self.tx_digest)
401            },
402            "Object previous transaction not properly set",
403        );
404    }
405
406    /// Mutate a mutable input object. This is used to mutate input objects outside of PT execution.
407    pub fn mutate_input_object(&mut self, object: Object) {
408        let id = object.id();
409        debug_assert!(self.input_objects.contains_key(&id));
410        debug_assert!(!object.is_immutable());
411        self.execution_results.modified_objects.insert(id);
412        self.execution_results.written_objects.insert(id, object);
413    }
414
415    pub fn mutate_new_or_input_object(&mut self, object: Object) {
416        let id = object.id();
417        debug_assert!(!object.is_immutable());
418        if self.input_objects.contains_key(&id) {
419            self.execution_results.modified_objects.insert(id);
420        }
421        self.execution_results.written_objects.insert(id, object);
422    }
423
424    /// Mutate a child object outside of PT. This should be used extremely rarely.
425    /// Currently it's only used by advance_epoch_safe_mode because it's all native
426    /// without PT. This should almost never be used otherwise.
427    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
428        let id = new_object.id();
429        let old_ref = old_object.compute_object_reference();
430        debug_assert_eq!(old_ref.0, id);
431        self.loaded_runtime_objects.insert(
432            id,
433            DynamicallyLoadedObjectMetadata {
434                version: old_ref.1,
435                digest: old_ref.2,
436                owner: old_object.owner.clone(),
437                storage_rebate: old_object.storage_rebate,
438                previous_transaction: old_object.previous_transaction,
439            },
440        );
441        self.execution_results.modified_objects.insert(id);
442        self.execution_results
443            .written_objects
444            .insert(id, new_object);
445    }
446
447    /// Upgrade system package during epoch change. This requires special treatment
448    /// since the system package to be upgraded is not in the input objects.
449    /// We could probably fix above to make it less special.
450    pub fn upgrade_system_package(&mut self, package: Object) {
451        let id = package.id();
452        assert!(package.is_package() && is_system_package(id));
453        self.execution_results.modified_objects.insert(id);
454        self.execution_results.written_objects.insert(id, package);
455    }
456
457    /// Crate a new objcet. This is used to create objects outside of PT execution.
458    pub fn create_object(&mut self, object: Object) {
459        // Created mutable objects' versions are set to the store's lamport timestamp when it is
460        // committed to effects. Creating an object at a non-zero version risks violating the
461        // lamport timestamp invariant (that a transaction's lamport timestamp is strictly greater
462        // than all versions witnessed by the transaction).
463        debug_assert!(
464            object.is_immutable() || object.version() == SequenceNumber::MIN,
465            "Created mutable objects should not have a version set",
466        );
467        let id = object.id();
468        self.execution_results.created_object_ids.insert(id);
469        self.execution_results.written_objects.insert(id, object);
470    }
471
472    /// Delete a mutable input object. This is used to delete input objects outside of PT execution.
473    pub fn delete_input_object(&mut self, id: &ObjectID) {
474        // there should be no deletion after write
475        debug_assert!(!self.execution_results.written_objects.contains_key(id));
476        debug_assert!(self.input_objects.contains_key(id));
477        self.execution_results.modified_objects.insert(*id);
478        self.execution_results.deleted_object_ids.insert(*id);
479    }
480
481    pub fn drop_writes(&mut self) {
482        self.execution_results.drop_writes();
483    }
484
485    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
486        // there should be no read after delete
487        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
488        self.execution_results
489            .written_objects
490            .get(id)
491            .or_else(|| self.input_objects.get(id))
492    }
493
494    pub fn save_loaded_runtime_objects(
495        &mut self,
496        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
497    ) {
498        #[cfg(debug_assertions)]
499        {
500            for (id, v1) in &loaded_runtime_objects {
501                if let Some(v2) = self.loaded_runtime_objects.get(id) {
502                    assert_eq!(v1, v2);
503                }
504            }
505            for (id, v1) in &self.loaded_runtime_objects {
506                if let Some(v2) = loaded_runtime_objects.get(id) {
507                    assert_eq!(v1, v2);
508                }
509            }
510        }
511        // Merge the two maps because we may be calling the execution engine more than once
512        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
513        self.loaded_runtime_objects.extend(loaded_runtime_objects);
514    }
515
516    pub fn save_wrapped_object_containers(
517        &mut self,
518        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
519    ) {
520        #[cfg(debug_assertions)]
521        {
522            for (id, container1) in &wrapped_object_containers {
523                if let Some(container2) = self.wrapped_object_containers.get(id) {
524                    assert_eq!(container1, container2);
525                }
526            }
527            for (id, container1) in &self.wrapped_object_containers {
528                if let Some(container2) = wrapped_object_containers.get(id) {
529                    assert_eq!(container1, container2);
530                }
531            }
532        }
533        // Merge the two maps because we may be calling the execution engine more than once
534        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
535        self.wrapped_object_containers
536            .extend(wrapped_object_containers);
537    }
538
539    pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
540        #[cfg(debug_assertions)]
541        {
542            for id in &self.generated_runtime_ids {
543                assert!(!generated_ids.contains(id))
544            }
545            for id in &generated_ids {
546                assert!(!self.generated_runtime_ids.contains(id));
547            }
548        }
549        self.generated_runtime_ids.extend(generated_ids);
550    }
551
552    pub fn estimate_effects_size_upperbound(&self) -> usize {
553        TransactionEffects::estimate_effects_size_upperbound_v2(
554            self.execution_results.written_objects.len(),
555            self.execution_results.modified_objects.len(),
556            self.input_objects.len(),
557        )
558    }
559
560    pub fn written_objects_size(&self) -> usize {
561        self.execution_results
562            .written_objects
563            .values()
564            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
565    }
566
567    /// If there are unmetered storage rebate (due to system transaction), we put them into
568    /// the storage rebate of 0x5 object.
569    /// TODO: This will not work for potential future new system transactions if 0x5 is not in the input.
570    /// We should fix this.
571    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
572        if unmetered_storage_rebate == 0 {
573            // If unmetered_storage_rebate is 0, we are most likely executing the genesis transaction.
574            // And in that case we cannot mutate the 0x5 object because it's newly created.
575            // And there is no storage rebate that needs distribution anyway.
576            return;
577        }
578        tracing::debug!(
579            "Amount of unmetered storage rebate from system tx: {:?}",
580            unmetered_storage_rebate
581        );
582        let mut system_state_wrapper = self
583            .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
584            .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
585            .clone();
586        // In unmetered execution, storage_rebate field of mutated object must be 0.
587        // If not, we would be dropping SUI on the floor by overriding it.
588        assert_eq!(system_state_wrapper.storage_rebate, 0);
589        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
590        self.mutate_input_object(system_state_wrapper);
591    }
592
593    /// Add an accumulator event to the execution results.
594    pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
595        self.execution_results.accumulator_events.push(event);
596    }
597
598    /// Given an object ID, if it's not modified, returns None.
599    /// Otherwise returns its metadata, including version, digest, owner and storage rebate.
600    /// A modified object must be either a mutable input, or a loaded child object.
601    /// The only exception is when we upgrade system packages, in which case the upgraded
602    /// system packages are not part of input, but are modified.
603    fn get_object_modified_at(
604        &self,
605        object_id: &ObjectID,
606    ) -> Option<DynamicallyLoadedObjectMetadata> {
607        if self.execution_results.modified_objects.contains(object_id) {
608            Some(
609                self.mutable_input_refs
610                    .get(object_id)
611                    .map(
612                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
613                            version: *version,
614                            digest: *digest,
615                            owner: owner.clone(),
616                            // It's guaranteed that a mutable input object is an input object.
617                            storage_rebate: self.input_objects[object_id].storage_rebate,
618                            previous_transaction: self.input_objects[object_id]
619                                .previous_transaction,
620                        },
621                    )
622                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
623                    .unwrap_or_else(|| {
624                        debug_assert!(is_system_package(*object_id));
625                        let package_obj =
626                            self.store.get_package_object(object_id).unwrap().unwrap();
627                        let obj = package_obj.object();
628                        DynamicallyLoadedObjectMetadata {
629                            version: obj.version(),
630                            digest: obj.digest(),
631                            owner: obj.owner.clone(),
632                            storage_rebate: obj.storage_rebate,
633                            previous_transaction: obj.previous_transaction,
634                        }
635                    }),
636            )
637        } else {
638            None
639        }
640    }
641}
642
643impl TemporaryStore<'_> {
644    // check that every object read is owned directly or indirectly by sender, sponsor,
645    // or a shared object input
646    pub fn check_ownership_invariants(
647        &self,
648        sender: &SuiAddress,
649        sponsor: &Option<SuiAddress>,
650        gas_charger: &mut GasCharger,
651        mutable_inputs: &HashSet<ObjectID>,
652        is_epoch_change: bool,
653    ) -> SuiResult<()> {
654        let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
655        let gas_owner = sponsor.as_ref().unwrap_or(sender);
656
657        // mark input objects as authenticated
658        let mut authenticated_for_mutation: HashSet<_> = self
659            .input_objects
660            .iter()
661            .filter_map(|(id, obj)| {
662                match &obj.owner {
663                    Owner::AddressOwner(a) => {
664                        if gas_objs.contains(id) {
665                            // gas object must be owned by sender or sponsor
666                            assert!(
667                                a == gas_owner,
668                                "Gas object must be owned by sender or sponsor"
669                            );
670                        } else {
671                            assert!(sender == a, "Input object must be owned by sender");
672                        }
673                        Some(id)
674                    }
675                    Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
676                    Owner::Immutable => {
677                        // object is authenticated, but it cannot own other objects,
678                        // so we should not add it to `authenticated_objs`
679                        // However, we would definitely want to add immutable objects
680                        // to the set of authenticated roots if we were doing runtime
681                        // checks inside the VM instead of after-the-fact in the temporary
682                        // store. Here, we choose not to add them because this will catch a
683                        // bug where we mutate or delete an object that belongs to an immutable
684                        // object (though it will show up somewhat opaquely as an authentication
685                        // failure), whereas adding the immutable object to the roots will prevent
686                        // us from catching this.
687                        None
688                    }
689                    Owner::ObjectOwner(_parent) => {
690                        unreachable!(
691                            "Input objects must be address owned, shared, consensus, or immutable"
692                        )
693                    }
694                }
695            })
696            .filter(|id| {
697                // remove any non-mutable inputs. This will remove deleted or readonly shared
698                // objects
699                mutable_inputs.contains(id)
700            })
701            .copied()
702            // Add any object IDs generated in the object runtime during execution to the
703            // authenticated set (i.e., new (non-package) objects, and possibly ephemeral UIDs).
704            .chain(self.generated_runtime_ids.iter().copied())
705            .collect();
706
707        // Add sender and sponsor (if present) to authenticated set
708        authenticated_for_mutation.insert((*sender).into());
709        if let Some(sponsor) = sponsor {
710            authenticated_for_mutation.insert((*sponsor).into());
711        }
712
713        // check all modified objects are authenticated
714        let mut objects_to_authenticate = self
715            .execution_results
716            .modified_objects
717            .iter()
718            .copied()
719            .collect::<Vec<_>>();
720
721        while let Some(to_authenticate) = objects_to_authenticate.pop() {
722            if authenticated_for_mutation.contains(&to_authenticate) {
723                // object has already been authenticated
724                continue;
725            }
726
727            let parent = if let Some(container_id) =
728                self.wrapped_object_containers.get(&to_authenticate)
729            {
730                // It's a wrapped object, so check that the container is authenticated
731                *container_id
732            } else {
733                // It's non-wrapped, so check the owner -- we can load the object from the
734                // store.
735                let Some(old_obj) = self.store.get_object(&to_authenticate) else {
736                    panic!(
737                        "Failed to load object {to_authenticate:?}.\n \
738                         If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
739                        &self.wrapped_object_containers
740                    )
741                };
742
743                match &old_obj.owner {
744                    // We mutated a dynamic field, we can continue to trace this back to verify
745                    // proper ownership.
746                    Owner::ObjectOwner(parent) => ObjectID::from(*parent),
747                    // We mutated an address owned or sequenced address owned object -- one of two cases apply:
748                    // 1) the object is owned by an object or address in the authenticated set,
749                    // 2) the object is owned by some other address, in which case we should
750                    //    continue to trace this back.
751                    Owner::AddressOwner(parent)
752                    | Owner::ConsensusAddressOwner { owner: parent, .. } => {
753                        // For Receiving<_> objects, the address owner is actually an object.
754                        // If it was actually an address, we should have caught it as an input and
755                        // it would already have been in authenticated_for_mutation
756                        ObjectID::from(*parent)
757                    }
758                    // We mutated a shared object -- we checked if this object was in the
759                    // authenticated set at the top of this loop and it wasn't so this is a failure.
760                    owner @ Owner::Shared { .. } => {
761                        panic!(
762                            "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
763                             Potentially covering objects in: {authenticated_for_mutation:#?}"
764                        );
765                    }
766                    Owner::Immutable => {
767                        assert!(
768                            is_epoch_change,
769                            "Immutable objects cannot be written, except for \
770                             Sui Framework/Move stdlib upgrades at epoch change boundaries"
771                        );
772                        // Note: this assumes that the only immutable objects an epoch change
773                        // tx can update are system packages,
774                        // but in principle we could allow others.
775                        assert!(
776                            is_system_package(to_authenticate),
777                            "Only system packages can be upgraded"
778                        );
779                        continue;
780                    }
781                }
782            };
783
784            // we now assume the object is authenticated and check the parent
785            authenticated_for_mutation.insert(to_authenticate);
786            objects_to_authenticate.push(parent);
787        }
788        Ok(())
789    }
790}
791
792impl TemporaryStore<'_> {
793    /// Track storage gas for each mutable input object (including the gas coin)
794    /// and each created object. Compute storage refunds for each deleted object.
795    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
796    /// All objects will be updated with their new (current) storage rebate/cost.
797    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
798    /// overall storage rebate and cost.
799    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
800        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
801        let old_storage_rebates: Vec<_> = self
802            .execution_results
803            .written_objects
804            .keys()
805            .map(|object_id| {
806                self.get_object_modified_at(object_id)
807                    .map(|metadata| metadata.storage_rebate)
808                    .unwrap_or_default()
809            })
810            .collect();
811        for (object, old_storage_rebate) in self
812            .execution_results
813            .written_objects
814            .values_mut()
815            .zip(old_storage_rebates)
816        {
817            // new object size
818            let new_object_size = object.object_size_for_gas_metering();
819            // track changes and compute the new object `storage_rebate`
820            let new_storage_rebate = gas_charger.track_storage_mutation(
821                object.id(),
822                new_object_size,
823                old_storage_rebate,
824            );
825            object.storage_rebate = new_storage_rebate;
826        }
827
828        self.collect_rebate(gas_charger);
829    }
830
831    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
832        for object_id in &self.execution_results.modified_objects {
833            if self
834                .execution_results
835                .written_objects
836                .contains_key(object_id)
837            {
838                continue;
839            }
840            // get and track the deleted object `storage_rebate`
841            let storage_rebate = self
842                .get_object_modified_at(object_id)
843                // Unwrap is safe because this loop iterates through all modified objects.
844                .unwrap()
845                .storage_rebate;
846            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
847        }
848    }
849
850    pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
851        assert_invariant!(
852            self.execution_results
853                .created_object_ids
854                .iter()
855                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
856                    && !self.execution_results.modified_objects.contains(id)),
857            "Created object IDs cannot also be deleted or modified"
858        );
859        assert_invariant!(
860            self.execution_results.modified_objects.iter().all(|id| {
861                self.mutable_input_refs.contains_key(id)
862                    || self.loaded_runtime_objects.contains_key(id)
863                    || is_system_package(*id)
864            }),
865            "A modified object must be either a mutable input, a loaded child object, or a system package"
866        );
867        Ok(())
868    }
869}
870//==============================================================================
871// Charge gas current - end
872//==============================================================================
873
874impl TemporaryStore<'_> {
875    pub fn advance_epoch_safe_mode(
876        &mut self,
877        params: &AdvanceEpochParams,
878        protocol_config: &ProtocolConfig,
879    ) {
880        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
881            .expect("System state wrapper object must exist");
882        let (old_object, new_object) =
883            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
884        self.mutate_child_object(old_object, new_object);
885    }
886}
887
888type ModifiedObjectInfo<'a> = (
889    ObjectID,
890    // old object metadata, including version, digest, owner, and storage rebate.
891    Option<DynamicallyLoadedObjectMetadata>,
892    Option<&'a Object>,
893);
894
895impl TemporaryStore<'_> {
896    fn get_input_sui(
897        &self,
898        id: &ObjectID,
899        expected_version: SequenceNumber,
900        layout_resolver: &mut impl LayoutResolver,
901    ) -> Result<u64, ExecutionError> {
902        if let Some(obj) = self.input_objects.get(id) {
903            // the assumption here is that if it is in the input objects must be the right one
904            if obj.version() != expected_version {
905                invariant_violation!(
906                    "Version mismatching when resolving input object to check conservation--\
907                     expected {}, got {}",
908                    expected_version,
909                    obj.version(),
910                );
911            }
912            obj.get_total_sui(layout_resolver).map_err(|e| {
913                make_invariant_violation!(
914                    "Failed looking up input SUI in SUI conservation checking for input with \
915                         type {:?}: {e:#?}",
916                    obj.struct_tag(),
917                )
918            })
919        } else {
920            // not in input objects, must be a dynamic field
921            let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
922                invariant_violation!(
923                    "Failed looking up dynamic field {id} in SUI conservation checking"
924                );
925            };
926            obj.get_total_sui(layout_resolver).map_err(|e| {
927                make_invariant_violation!(
928                    "Failed looking up input SUI in SUI conservation checking for type \
929                         {:?}: {e:#?}",
930                    obj.struct_tag(),
931                )
932            })
933        }
934    }
935
936    /// Return the list of all modified objects, for each object, returns
937    /// - Object ID,
938    /// - Input: If the object existed prior to this transaction, include their version and storage_rebate,
939    /// - Output: If a new version of the object is written, include the new object.
940    fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
941        self.execution_results
942            .modified_objects
943            .iter()
944            .map(|id| {
945                let metadata = self.get_object_modified_at(id);
946                let output = self.execution_results.written_objects.get(id);
947                (*id, metadata, output)
948            })
949            .chain(
950                self.execution_results
951                    .written_objects
952                    .iter()
953                    .filter_map(|(id, object)| {
954                        if self.execution_results.modified_objects.contains(id) {
955                            None
956                        } else {
957                            Some((*id, None, Some(object)))
958                        }
959                    }),
960            )
961            .collect()
962    }
963
964    /// Check that this transaction neither creates nor destroys SUI. This should hold for all txes
965    /// except the epoch change tx, which mints staking rewards equal to the gas fees burned in the
966    /// previous epoch.  Specifically, this checks two key invariants about storage
967    /// fees and storage rebate:
968    ///
969    /// 1. all SUI in storage rebate fields of input objects should flow either to the transaction
970    ///    storage rebate, or the transaction non-refundable storage rebate
971    /// 2. all SUI charged for storage should flow into the storage rebate field of some output
972    ///    object
973    ///
974    /// This function is intended to be called *after* we have charged for
975    /// gas + applied the storage rebate to the gas object, but *before* we
976    /// have updated object versions.
977    pub fn check_sui_conserved(
978        &self,
979        simple_conservation_checks: bool,
980        gas_summary: &GasCostSummary,
981    ) -> Result<(), ExecutionError> {
982        if !simple_conservation_checks {
983            return Ok(());
984        }
985        // total amount of SUI in storage rebate of input objects
986        let mut total_input_rebate = 0;
987        // total amount of SUI in storage rebate of output objects
988        let mut total_output_rebate = 0;
989        for (_id, input, output) in self.get_modified_objects() {
990            if let Some(input) = input {
991                total_input_rebate += input.storage_rebate;
992            }
993            if let Some(object) = output {
994                total_output_rebate += object.storage_rebate;
995            }
996        }
997
998        if gas_summary.storage_cost == 0 {
999            // this condition is usually true when the transaction went OOG and no
1000            // gas is left for storage charges.
1001            // The storage cost has to be there at least for the gas coin which
1002            // will not be deleted even when going to 0.
1003            // However if the storage cost is 0 and if there is any object touched
1004            // or deleted the value in input must be equal to the output plus rebate and
1005            // non refundable.
1006            // Rebate and non refundable will be positive when there are object deleted
1007            // (gas smashing being the primary and possibly only example).
1008            // A more typical condition is for all storage charges in summary to be 0 and
1009            // then input and output must be the same value
1010            if total_input_rebate
1011                != total_output_rebate
1012                    + gas_summary.storage_rebate
1013                    + gas_summary.non_refundable_storage_fee
1014            {
1015                return Err(ExecutionError::invariant_violation(format!(
1016                    "SUI conservation failed -- no storage charges in gas summary \
1017                        and total storage input rebate {} not equal  \
1018                        to total storage output rebate {}",
1019                    total_input_rebate, total_output_rebate,
1020                )));
1021            }
1022        } else {
1023            // all SUI in storage rebate fields of input objects should flow either to
1024            // the transaction storage rebate, or the non-refundable storage rebate pool
1025            if total_input_rebate
1026                != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1027            {
1028                return Err(ExecutionError::invariant_violation(format!(
1029                    "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1030                        {} SUI in tx storage rebate or tx non-refundable storage rebate",
1031                    total_input_rebate, gas_summary.non_refundable_storage_fee,
1032                )));
1033            }
1034
1035            // all SUI charged for storage should flow into the storage rebate field
1036            // of some output object
1037            if gas_summary.storage_cost != total_output_rebate {
1038                return Err(ExecutionError::invariant_violation(format!(
1039                    "SUI conservation failed -- {} SUI charged for storage, \
1040                        {} SUI in storage rebate field of output objects",
1041                    gas_summary.storage_cost, total_output_rebate
1042                )));
1043            }
1044        }
1045        Ok(())
1046    }
1047
1048    /// Check that this transaction neither creates nor destroys SUI.
1049    /// This more expensive check will check a third invariant on top of the 2 performed
1050    /// by `check_sui_conserved` above:
1051    ///
1052    /// * all SUI in input objects (including coins etc in the Move part of an object) should flow
1053    ///   either to an output object, or be burned as part of computation fees or non-refundable
1054    ///   storage rebate
1055    ///
1056    /// This function is intended to be called *after* we have charged for gas + applied the
1057    /// storage rebate to the gas object, but *before* we have updated object versions. The
1058    /// advance epoch transaction would mint `epoch_fees` amount of SUI, and burn `epoch_rebates`
1059    /// amount of SUI. We need these information for this check.
1060    pub fn check_sui_conserved_expensive(
1061        &self,
1062        gas_summary: &GasCostSummary,
1063        advance_epoch_gas_summary: Option<(u64, u64)>,
1064        layout_resolver: &mut impl LayoutResolver,
1065    ) -> Result<(), ExecutionError> {
1066        // total amount of SUI in input objects, including both coins and storage rebates
1067        let mut total_input_sui = 0;
1068        // total amount of SUI in output objects, including both coins and storage rebates
1069        let mut total_output_sui = 0;
1070
1071        // settlement input/output sui is used by the settlement transactions to account for
1072        // Sui that has been gathered from the accumulator writes of transactions which it is
1073        // settling.
1074        total_input_sui += self.execution_results.settlement_input_sui;
1075        total_output_sui += self.execution_results.settlement_output_sui;
1076
1077        for (id, input, output) in self.get_modified_objects() {
1078            if let Some(input) = input {
1079                total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1080            }
1081            if let Some(object) = output {
1082                total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1083                    make_invariant_violation!(
1084                        "Failed looking up output SUI in SUI conservation checking for \
1085                         mutated type {:?}: {e:#?}",
1086                        object.struct_tag(),
1087                    )
1088                })?;
1089            }
1090        }
1091
1092        for event in &self.execution_results.accumulator_events {
1093            let (input, output) = event.total_sui_in_event();
1094            total_input_sui += input;
1095            total_output_sui += output;
1096        }
1097
1098        // note: storage_cost flows into the storage_rebate field of the output objects, which is
1099        // why it is not accounted for here.
1100        // similarly, all of the storage_rebate *except* the storage_fund_rebate_inflow
1101        // gets credited to the gas coin both computation costs and storage rebate inflow are
1102        total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1103        if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1104            total_input_sui += epoch_fees;
1105            total_output_sui += epoch_rebates;
1106        }
1107        if total_input_sui != total_output_sui {
1108            return Err(ExecutionError::invariant_violation(format!(
1109                "SUI conservation failed: input={}, output={}, \
1110                    this transaction either mints or burns SUI",
1111                total_input_sui, total_output_sui,
1112            )));
1113        }
1114        Ok(())
1115    }
1116}
1117
1118impl ChildObjectResolver for TemporaryStore<'_> {
1119    fn read_child_object(
1120        &self,
1121        parent: &ObjectID,
1122        child: &ObjectID,
1123        child_version_upper_bound: SequenceNumber,
1124    ) -> SuiResult<Option<Object>> {
1125        let obj_opt = self.execution_results.written_objects.get(child);
1126        if obj_opt.is_some() {
1127            Ok(obj_opt.cloned())
1128        } else {
1129            let _scope = monitored_scope("Execution::read_child_object");
1130            self.store
1131                .read_child_object(parent, child, child_version_upper_bound)
1132        }
1133    }
1134
1135    fn get_object_received_at_version(
1136        &self,
1137        owner: &ObjectID,
1138        receiving_object_id: &ObjectID,
1139        receive_object_at_version: SequenceNumber,
1140        epoch_id: EpochId,
1141    ) -> SuiResult<Option<Object>> {
1142        // You should never be able to try and receive an object after deleting it or writing it in the same
1143        // transaction since `Receiving` doesn't have copy.
1144        debug_assert!(
1145            !self
1146                .execution_results
1147                .written_objects
1148                .contains_key(receiving_object_id)
1149        );
1150        debug_assert!(
1151            !self
1152                .execution_results
1153                .deleted_object_ids
1154                .contains(receiving_object_id)
1155        );
1156        self.store.get_object_received_at_version(
1157            owner,
1158            receiving_object_id,
1159            receive_object_at_version,
1160            epoch_id,
1161        )
1162    }
1163}
1164
1165/// Compares the owner and payload of an object.
1166/// This is used to detect illegal writes to non-exclusive write objects.
1167fn was_object_mutated(object: &Object, original: &Object) -> bool {
1168    let data_equal = match (&object.data, &original.data) {
1169        (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1170        // We don't have a use for package content-equality, so we remain as strict as
1171        // possible for now.
1172        (Data::Package(a), Data::Package(b)) => a == b,
1173        _ => false,
1174    };
1175
1176    let owner_equal = match (&object.owner, &original.owner) {
1177        // We don't compare initial shared versions, because re-shared objects do not have the
1178        // correct initial shared version at this point in time, and this field is not something
1179        // that can be modified by a single transaction anyway.
1180        (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1181        (
1182            Owner::ConsensusAddressOwner { owner: a, .. },
1183            Owner::ConsensusAddressOwner { owner: b, .. },
1184        ) => a == b,
1185        (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1186        (Owner::Immutable, Owner::Immutable) => true,
1187        (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1188
1189        // Keep the left hand side of the match exhaustive to catch future
1190        // changes to Owner
1191        (Owner::AddressOwner(_), _)
1192        | (Owner::Immutable, _)
1193        | (Owner::ObjectOwner(_), _)
1194        | (Owner::Shared { .. }, _)
1195        | (Owner::ConsensusAddressOwner { .. }, _) => false,
1196    };
1197
1198    !data_equal || !owner_equal
1199}
1200
1201impl Storage for TemporaryStore<'_> {
1202    fn reset(&mut self) {
1203        self.drop_writes();
1204    }
1205
1206    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1207        TemporaryStore::read_object(self, id)
1208    }
1209
1210    /// Take execution results v2, and translate it back to be compatible with effects v1.
1211    fn record_execution_results(
1212        &mut self,
1213        results: ExecutionResults,
1214    ) -> Result<(), ExecutionError> {
1215        let ExecutionResults::V2(mut results) = results else {
1216            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1217        };
1218
1219        // for all non-exclusive write inputs, remove them from written objects
1220        let mut to_remove = Vec::new();
1221        for (id, original) in &self.non_exclusive_input_original_versions {
1222            // Object must be present in `written_objects` and identical
1223            if results
1224                .written_objects
1225                .get(id)
1226                .map(|obj| was_object_mutated(obj, original))
1227                .unwrap_or(true)
1228            {
1229                return Err(ExecutionError::new_with_source(
1230                    ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1231                    "Non-exclusive write input object has been modified or deleted",
1232                ));
1233            }
1234            to_remove.push(*id);
1235        }
1236
1237        for id in to_remove {
1238            results.written_objects.remove(&id);
1239            results.modified_objects.remove(&id);
1240        }
1241
1242        // It's important to merge instead of override results because it's
1243        // possible to execute PT more than once during tx execution.
1244        self.execution_results.merge_results(results);
1245
1246        Ok(())
1247    }
1248
1249    fn save_loaded_runtime_objects(
1250        &mut self,
1251        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1252    ) {
1253        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1254    }
1255
1256    fn save_wrapped_object_containers(
1257        &mut self,
1258        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1259    ) {
1260        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1261    }
1262
1263    fn check_coin_deny_list(
1264        &self,
1265        receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1266    ) -> DenyListResult {
1267        let result = check_coin_deny_list_v2_during_execution(
1268            receiving_funds_type_and_owners,
1269            self.cur_epoch,
1270            self.store.as_object_store(),
1271        );
1272        // The denylist object is only loaded if there are regulated transfers.
1273        // And also if we already have it in the input there is no need to commit it again in the effects.
1274        if result.num_non_gas_coin_owners > 0
1275            && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1276        {
1277            self.loaded_per_epoch_config_objects
1278                .write()
1279                .insert(SUI_DENY_LIST_OBJECT_ID);
1280        }
1281        result
1282    }
1283
1284    fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1285        TemporaryStore::save_generated_object_ids(self, generated_ids)
1286    }
1287}
1288
1289impl BackingPackageStore for TemporaryStore<'_> {
1290    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1291        // We first check the objects in the temporary store because in non-production code path,
1292        // it is possible to read packages that are just written in the same transaction.
1293        // This can happen for example when we run the expensive conservation checks, where we may
1294        // look into the types of each written object in the output, and some of them need the
1295        // newly written packages for type checking.
1296        // In production path though, this should never happen.
1297        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1298            Ok(Some(PackageObject::new(obj.clone())))
1299        } else {
1300            self.store.get_package_object(package_id).inspect(|obj| {
1301                // Track object but leave unchanged
1302                if let Some(v) = obj
1303                    && !self
1304                        .runtime_packages_loaded_from_db
1305                        .read()
1306                        .contains_key(package_id)
1307                {
1308                    // TODO: Can this lock ever block execution?
1309                    // TODO: Another way to avoid the cost of maintaining this map is to not
1310                    // enable it in normal runs, and if a fork is detected, rerun it with a flag
1311                    // turned on and start populating this field.
1312                    self.runtime_packages_loaded_from_db
1313                        .write()
1314                        .insert(*package_id, v.clone());
1315                }
1316            })
1317        }
1318    }
1319}
1320
1321impl ParentSync for TemporaryStore<'_> {
1322    fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1323        unreachable!("Never called in newer protocol versions")
1324    }
1325}