sui_adapter_latest/
temporary_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::execution_mode::ExecutionMode;
5use crate::gas_charger::GasCharger;
6use move_binary_format::errors::{PartialVMError, PartialVMResult};
7use move_core_types::vm_status::StatusCode;
8use move_vm_runtime::runtime::MoveRuntime;
9use mysten_common::{ZipDebugEqIteratorExt, debug_fatal};
10use mysten_metrics::monitored_scope;
11use parking_lot::RwLock;
12use std::cell::{OnceCell, RefCell};
13use std::collections::{BTreeMap, BTreeSet, HashSet};
14use std::sync::Arc;
15use sui_protocol_config::ProtocolConfig;
16use sui_types::accumulator_event::AccumulatorEvent;
17use sui_types::accumulator_root::AccumulatorObjId;
18use sui_types::base_types::VersionDigest;
19use sui_types::committee::EpochId;
20use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
21use sui_types::effects::{
22    AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
23    TransactionEffectsV2, TransactionEvents,
24};
25use sui_types::execution::{
26    DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, ExecutionRetryError,
27    SharedInput,
28};
29use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
30use sui_types::inner_temporary_store::InnerTemporaryStore;
31use sui_types::object::Data;
32use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
33use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
34use sui_types::transaction::{GasData, TransactionKind};
35use sui_types::{
36    SUI_DENY_LIST_OBJECT_ID,
37    base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
38    digests::ObjectDigest,
39    effects::EffectsObjectChange,
40    error::{ExecutionError, SuiResult},
41    gas::GasCostSummary,
42    object::Object,
43    object::Owner,
44    storage::{BackingPackageStore, RuntimeObjectResolver, Storage},
45    transaction::InputObjects,
46};
47use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
48
49pub(crate) mod invariants;
50use invariants::InvariantChecker;
51
52pub struct TemporaryStore<'backing> {
53    // The backing store for retrieving Move packages onchain.
54    // When executing a Move call, the dependent packages are not going to be
55    // in the input objects. They will be fetched from the backing store.
56    // Also used for fetching the backing parent_sync to get the last known version for wrapped
57    // objects
58    store: &'backing dyn BackingStore,
59    tx_digest: TransactionDigest,
60    input_objects: BTreeMap<ObjectID, Object>,
61
62    /// Store the original versions of the non-exclusive write inputs, in order to detect
63    /// mutations (which are illegal, but not prevented by the type system).
64    non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
65
66    stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber /* start_version */>,
67    /// The version to assign to all objects written by the transaction using this store.
68    lamport_timestamp: SequenceNumber,
69    /// Inputs that will be mutated by the transaction. Does not include NonExclusiveWrite inputs,
70    /// which can be taken as `&mut T` but cannot be directly mutated.
71    mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
72    execution_results: ExecutionResultsV2,
73    /// Objects that were loaded during execution (dynamic fields + received objects).
74    loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
75    /// A map from wrapped object to its container. Used during expensive invariant checks.
76    wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
77    protocol_config: &'backing ProtocolConfig,
78
79    /// Every package that was loaded from DB store during execution.
80    /// These packages were not previously loaded into the temporary store.
81    runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
82
83    /// The set of objects that we may receive during execution. Not guaranteed to receive all, or
84    /// any of the objects referenced in this set.
85    receiving_objects: Vec<ObjectRef>,
86
87    /// The set of all generated object IDs from the object runtime during the transaction. This includes any
88    /// created-and-then-deleted objects in addition to any `new_ids` which contains only the set
89    /// of created (but not deleted) IDs in the transaction.
90    generated_runtime_ids: BTreeSet<ObjectID>,
91
92    // TODO: Now that we track epoch here, there are a few places we don't need to pass it around.
93    /// The current epoch.
94    cur_epoch: EpochId,
95
96    /// The set of per-epoch config objects that were loaded during execution, and are not in the
97    /// input objects. This allows us to commit them to the effects.
98    loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
99
100    /// Transaction-derived inputs and bookkeeping for the post-execution system-invariant checks
101    /// (SUI conservation, balance-accumulator authorization, object ownership). See
102    /// [`invariants::InvariantChecker`].
103    invariants: InvariantChecker,
104
105    /// Versions of system objects this transaction is allowed to read, keyed by object ID. A
106    /// system object is considered "available" once its latest committed version has reached the
107    /// recorded version; `check_system_object_available` consults this map. Every system object read
108    /// during execution must appear here — querying one that is absent is an invariant violation
109    /// (the transaction was not sequenced against it), so the check errors rather than allowing it.
110    system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
111
112    /// System objects read during execution that are not through input objects, keyed by object ID, with the version (and its
113    /// digest) at which they were read. Recorded by `check_system_object_available` and
114    /// emitted into the transaction effects as read-only consensus objects so the read can be
115    /// reproduced on replay. Interior-mutable because reads happen behind `&self`
116    /// (`RuntimeObjectResolver`).
117    loaded_system_objects: RefCell<BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>>,
118
119    /// Recorded when execution determines the transaction must be retried later rather than
120    /// committed. Set only by `check_system_object_available`, in the same expression that returns
121    /// the `PartialVMError` unwinding the VM, so the two signals cannot drift apart. Execution
122    /// still runs to completion and produces effects; this signal is carried out on
123    /// `InnerTemporaryStore` so the authority can discard those effects and re-enqueue. A
124    /// `OnceCell` rather than a lock: execution is single-threaded, and the condition is detected
125    /// behind `&self` (`RuntimeObjectResolver`), so the field must be interior-mutable; it is
126    /// recorded at most once (the first detection, after which execution unwinds), which
127    /// `OnceCell` enforces.
128    retry_request: OnceCell<ExecutionRetryError>,
129}
130
131impl<'backing> TemporaryStore<'backing> {
132    /// Creates a new store associated with an authority store, and populates it with
133    /// initial objects.
134    pub fn new(
135        store: &'backing dyn BackingStore,
136        input_objects: InputObjects,
137        receiving_objects: Vec<ObjectRef>,
138        tx_digest: TransactionDigest,
139        protocol_config: &'backing ProtocolConfig,
140        cur_epoch: EpochId,
141        system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
142    ) -> Self {
143        let mutable_input_refs = input_objects.exclusive_mutable_inputs();
144        let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
145
146        let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
147        let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
148        let objects = input_objects.into_object_map();
149        #[cfg(debug_assertions)]
150        {
151            // Ensure that input objects and receiving objects must not overlap.
152            assert!(
153                objects
154                    .keys()
155                    .collect::<HashSet<_>>()
156                    .intersection(
157                        &receiving_objects
158                            .iter()
159                            .map(|oref| &oref.0)
160                            .collect::<HashSet<_>>()
161                    )
162                    .next()
163                    .is_none()
164            );
165        }
166        Self {
167            store,
168            tx_digest,
169            input_objects: objects,
170            non_exclusive_input_original_versions,
171            stream_ended_consensus_objects,
172            lamport_timestamp,
173            mutable_input_refs,
174            execution_results: ExecutionResultsV2::default(),
175            protocol_config,
176            loaded_runtime_objects: BTreeMap::new(),
177            wrapped_object_containers: BTreeMap::new(),
178            runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
179            receiving_objects,
180            generated_runtime_ids: BTreeSet::new(),
181            cur_epoch,
182            loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
183            invariants: InvariantChecker::new(),
184            system_object_versions,
185            loaded_system_objects: RefCell::new(BTreeMap::new()),
186            retry_request: OnceCell::new(),
187        }
188    }
189
190    /// Checks that the system object `object_id` is available at the version this transaction
191    /// requires, i.e. its latest committed version has caught up to that version, and records the
192    /// read so it can be emitted into effects and reproduced on replay. When the object has not
193    /// caught up, this method records the retry request on the store and returns the
194    /// `PartialVMError` that unwinds the VM as one atomic act: callers must propagate the error
195    /// (`?`) unmodified rather than observe unavailability as data. This is the only place allowed
196    /// to construct that error, which is what keeps the error and `retry_request` in lockstep —
197    /// neither can appear without the other.
198    pub fn check_system_object_available(&self, object_id: &ObjectID) -> PartialVMResult<()> {
199        // Every system object read during execution must have an assigned version. Its absence
200        // here means the transaction is reading a system object it was not sequenced against,
201        // which is an invariant violation.
202        let Some(required_version) = self.system_object_versions.get(object_id).copied() else {
203            debug_fatal!("system object {object_id} read without an assigned version");
204            return Err(
205                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
206                    format!("system object {object_id} read without an assigned version"),
207                ),
208            );
209        };
210        // Load the object at exactly the version this transaction was sequenced against.
211        // `required_version` is the freshly-assigned version at the frontier, so it is never pruned:
212        // its absence means the local node has not yet committed that version.
213        let Some(object_at_required) = self.store.get_object_by_key(object_id, required_version)
214        else {
215            // Not yet caught up to the version this transaction requires: record the retry request
216            // and return the error that unwinds the VM, together. The retry payload is carried
217            // out-of-band via this interior-mutable state (the Move error can't hold it) and
218            // surfaces as `ExecutionRetryError` on the inner temporary store. The authority then
219            // waits for `object_id` to reach `required_version` and re-enqueues; it recovers the
220            // object's initial shared version from the epoch start config, so the id and version
221            // carried here are enough.
222            // First detection wins; a second would only be recorded if execution continued past the
223            // returned error, which it does not.
224            let retry = ExecutionRetryError::SystemObjectUnavailable {
225                object_id: *object_id,
226                version: required_version,
227            };
228            let message = retry.to_string();
229            let _ = self.retry_request.set(retry);
230            // `SYSTEM_OBJECT_NOT_AVAILABLE_LOCALLY` is minted nowhere else.
231            return Err(
232                PartialVMError::new(StatusCode::SYSTEM_OBJECT_NOT_AVAILABLE_LOCALLY)
233                    .with_message(message),
234            );
235        };
236
237        // Available: record the read at `required_version` (which is what the transaction depends
238        // on and reads) so it can be emitted into effects as a read-only consensus object and
239        // reproduced on replay. The version and digest are taken at `required_version` — not the
240        // latest — so the recorded value is deterministic across nodes regardless of how far the
241        // object has since advanced.
242        self.loaded_system_objects
243            .borrow_mut()
244            .insert(*object_id, (required_version, object_at_required.digest()));
245        Ok(())
246    }
247
248    // Helpers to access private fields
249    pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
250        &self.input_objects
251    }
252
253    pub fn update_object_version_and_prev_tx(&mut self) {
254        self.execution_results.update_version_and_previous_tx(
255            self.lamport_timestamp,
256            self.tx_digest,
257            &self.input_objects,
258            self.protocol_config.reshare_at_same_initial_version(),
259        );
260
261        #[cfg(debug_assertions)]
262        {
263            self.check_invariants();
264        }
265    }
266
267    fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
268        let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
269        let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
270        for event in &self.execution_results.accumulator_events {
271            match &event.write.value {
272                AccumulatorValue::Integer(amount) => match event.write.operation {
273                    AccumulatorOperation::Split => {
274                        let entry = running_net_withdraws
275                            .entry(event.accumulator_obj)
276                            .or_default();
277                        *entry += *amount as i128;
278                        if *entry > 0 {
279                            let max_entry = running_max_withdraws
280                                .entry(event.accumulator_obj)
281                                .or_default();
282                            *max_entry = (*max_entry).max(*entry as u128);
283                        }
284                    }
285                    AccumulatorOperation::Merge => {
286                        let entry = running_net_withdraws
287                            .entry(event.accumulator_obj)
288                            .or_default();
289                        *entry -= *amount as i128;
290                    }
291                },
292                AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
293            }
294        }
295        running_max_withdraws
296    }
297
298    /// Ensure that, per accumulator object, the gross Merge total and gross Split total are
299    /// representable: bounded by the total SUI supply for `Balance<SUI>` keys, and by `u64::MAX`
300    /// otherwise.
301    ///
302    /// `AccumulatorWriteV1::merge` folds all writes for a key by summing Merge amounts and Split
303    /// amounts separately into `u64`s. The object runtime caps Move-native merges per key at
304    /// `u64::MAX`, but the gas charger emits additional, uncapped SUI deposit/withdraw events during
305    /// gas smashing and gas charging (e.g. a refund Merge to an address balance), so a per-key SUI
306    /// total could be pushed past `u64::MAX`, overflowing that fold (and the SUI-conservation sum).
307    /// Reaching such a total requires SUI from an object-sourced withdrawal whose backing is only
308    /// verified at settlement.
309    ///
310    /// Bounding SUI to `TOTAL_SUPPLY_MIST` rejects any such amount here, *before* gas is charged, so
311    /// the rejected PTB-emitted writes are dropped on gas reset and only the (bounded) gas events
312    /// remain. Crucially, `TOTAL_SUPPLY_MIST` is ~8.4B SUI below `u64::MAX`, so the gas events emitted
313    /// after this check (which move only real SUI) cannot push any per-key total past `u64::MAX` —
314    /// hence they need not be re-checked. Non-SUI balances have no uncapped gas path, so the
315    /// object-runtime per-key `u64::MAX` cap is the binding guard there and we only backstop u64
316    /// representability.
317    ///
318    /// The per-key limits are not sufficient on their own: withdrawn SUI can be spread across several
319    /// object keys (each withdrawal `<= TOTAL_SUPPLY_MIST`) and then recombined *outside* the
320    /// accumulator — e.g. each withdrawal redeemed to a `Coin<SUI>` and merged into the PTB gas coin
321    /// via `MergeCoins`, which is an object mutation, not an accumulator event. The recombined coin
322    /// can then reach `u64::MAX` and overflow `deduct_gas` on a refund. So we also bound the
323    /// *cross-key* total SUI withdrawn (gross Split) to the supply, capping the total SUI a single
324    /// transaction can withdraw regardless of how it is later recombined.
325    pub(crate) fn check_accumulator_amounts_representable(&self) -> Result<(), ExecutionError> {
326        let supply = sui_types::gas_coin::TOTAL_SUPPLY_MIST as u128;
327        let mut merge_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
328        let mut split_totals: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
329        // Cross-key total of SUI withdrawn (gross Split), bounded to the supply (see above).
330        let mut total_sui_split: u128 = 0;
331        for event in &self.execution_results.accumulator_events {
332            let AccumulatorValue::Integer(amount) = event.write.value else {
333                continue;
334            };
335            let amount = amount as u128;
336            // SUI cannot exceed its total supply through any single balance. Bounding to the supply
337            // (rather than u64::MAX) leaves headroom for the not-yet-emitted gas events.
338            let is_sui = sui_types::gas_coin::GasCoin::is_gas_balance_type(&event.write.address.ty);
339            let limit = if is_sui { supply } else { u64::MAX as u128 };
340            let total = match event.write.operation {
341                AccumulatorOperation::Merge => {
342                    merge_totals.entry(event.accumulator_obj).or_default()
343                }
344                AccumulatorOperation::Split => {
345                    split_totals.entry(event.accumulator_obj).or_default()
346                }
347            };
348            *total += amount;
349            if *total > limit {
350                return Err(ExecutionError::new_with_source(
351                    ExecutionErrorKind::CoinBalanceOverflow,
352                    format!(
353                        "accumulator balance change for {:?} exceeds the representable limit \
354                         (gross total {}, limit {})",
355                        event.accumulator_obj, *total, limit
356                    ),
357                ));
358            }
359            if is_sui && matches!(event.write.operation, AccumulatorOperation::Split) {
360                total_sui_split += amount;
361                if total_sui_split > supply {
362                    return Err(ExecutionError::new_with_source(
363                        ExecutionErrorKind::CoinBalanceOverflow,
364                        format!(
365                            "total SUI withdrawn across all accumulators ({total_sui_split}) \
366                             exceeds the total supply ({supply})"
367                        ),
368                    ));
369                }
370            }
371        }
372        Ok(())
373    }
374
375    /// Ensure that there is one entry for each accumulator object in the accumulator events.
376    fn merge_accumulator_events(&mut self) {
377        self.execution_results.accumulator_events = self
378            .execution_results
379            .accumulator_events
380            .iter()
381            .fold(
382                BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
383                |mut map, event| {
384                    map.entry(event.accumulator_obj)
385                        .or_default()
386                        .push(event.write.clone());
387                    map
388                },
389            )
390            .into_iter()
391            .map(|(obj_id, writes)| {
392                AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
393            })
394            .collect();
395    }
396
397    /// Break up the structure and return its internal stores (objects, active_inputs, written, deleted)
398    pub fn into_inner(
399        self,
400        accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
401    ) -> InnerTemporaryStore {
402        let results = self.execution_results;
403        InnerTemporaryStore {
404            input_objects: self.input_objects,
405            stream_ended_consensus_objects: self.stream_ended_consensus_objects,
406            mutable_inputs: self.mutable_input_refs,
407            written: results.written_objects,
408            events: TransactionEvents {
409                data: results.user_events,
410            },
411            accumulator_events: results.accumulator_events,
412            loaded_runtime_objects: self.loaded_runtime_objects,
413            runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
414            lamport_version: self.lamport_timestamp,
415            binary_config: self.protocol_config.binary_config(None),
416            accumulator_running_max_withdraws,
417            retry_request: self.retry_request.into_inner(),
418        }
419    }
420
421    /// For every object from active_inputs (i.e. all mutable objects), if they are not
422    /// mutated during the transaction execution, force mutating them by incrementing the
423    /// sequence number. This is required to achieve safety.
424    pub(crate) fn ensure_active_inputs_mutated(&mut self) {
425        let mut to_be_updated = vec![];
426        // Note: we do not mutate input objects if they are non-exclusive write
427        for id in self.mutable_input_refs.keys() {
428            if !self.execution_results.modified_objects.contains(id) {
429                // We cannot update here but have to push to `to_be_updated` and update later
430                // because the for loop is holding a reference to `self`, and calling
431                // `self.mutate_input_object` requires a mutable reference to `self`.
432                to_be_updated.push(self.input_objects[id].clone());
433            }
434        }
435        for object in to_be_updated {
436            // The object must be mutated as it was present in the input objects
437            self.mutate_input_object(object.clone());
438        }
439    }
440
441    fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
442        let results = &self.execution_results;
443        let all_ids = results
444            .created_object_ids
445            .iter()
446            .chain(&results.deleted_object_ids)
447            .chain(&results.modified_objects)
448            .chain(results.written_objects.keys())
449            .collect::<BTreeSet<_>>();
450        all_ids
451            .into_iter()
452            .map(|id| {
453                (
454                    *id,
455                    EffectsObjectChange::new(
456                        self.get_object_modified_at(id)
457                            .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
458                        results.written_objects.get(id),
459                        results.created_object_ids.contains(id),
460                        results.deleted_object_ids.contains(id),
461                    ),
462                )
463            })
464            .chain(results.accumulator_events.iter().cloned().map(
465                |AccumulatorEvent {
466                     accumulator_obj,
467                     write,
468                 }| {
469                    (
470                        *accumulator_obj.inner(),
471                        EffectsObjectChange::new_from_accumulator_write(write),
472                    )
473                },
474            ))
475            .collect()
476    }
477
478    pub fn into_effects(
479        mut self,
480        shared_object_refs: Vec<SharedInput>,
481        transaction_digest: &TransactionDigest,
482        mut transaction_dependencies: BTreeSet<TransactionDigest>,
483        gas_cost_summary: GasCostSummary,
484        status: ExecutionStatus,
485        gas_coin: Option<ObjectID>,
486        epoch: EpochId,
487    ) -> (InnerTemporaryStore, TransactionEffects) {
488        // Defense-in-depth: Owner::Party is not yet supported as an effect output. There are
489        // no constructions of `Owner::Party` yet so a hard assert should be safe.
490        for (id, obj) in &self.execution_results.written_objects {
491            assert!(
492                !matches!(obj.owner, Owner::Party { .. }),
493                "Party-owned objects are not yet supported (object {id})"
494            );
495        }
496
497        self.update_object_version_and_prev_tx();
498        // This must happens before merge_accumulator_events.
499        let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
500        self.merge_accumulator_events();
501
502        // Regardless of execution status (including aborts), we insert the previous transaction
503        // for any successfully received objects during the transaction.
504        for (id, expected_version, expected_digest) in &self.receiving_objects {
505            // If the receiving object is in the loaded runtime objects, then that means that it
506            // was actually successfully loaded (so existed, and there was authenticated mutable
507            // access to it). So we insert the previous transaction as a dependency.
508            if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
509                // Check that the expected version, digest, and owner match the loaded version,
510                // digest, and owner. If they don't then don't register a dependency.
511                // This is because this could be "spoofed" by loading a dynamic object field.
512                let loaded_via_receive = obj_meta.version == *expected_version
513                    && obj_meta.digest == *expected_digest
514                    && obj_meta.owner.is_address_owned();
515                if loaded_via_receive {
516                    transaction_dependencies.insert(obj_meta.previous_transaction);
517                }
518            }
519        }
520
521        assert!(self.protocol_config.enable_effects_v2());
522
523        let object_changes = self.get_object_changes();
524
525        let lamport_version = self.lamport_timestamp;
526        // TODO: Cleanup this clone. Potentially add unchanged_shraed_objects directly to InnerTempStore.
527        let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
528        let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
529            shared_object_refs,
530            loaded_per_epoch_config_objects,
531            &object_changes,
532        );
533        let inner = self.into_inner(accumulator_running_max_withdraws);
534
535        let effects = TransactionEffects::new_from_execution_v2(
536            status,
537            epoch,
538            gas_cost_summary,
539            unchanged_consensus_objects,
540            *transaction_digest,
541            lamport_version,
542            object_changes,
543            gas_coin,
544            if inner.events.data.is_empty() {
545                None
546            } else {
547                Some(inner.events.digest())
548            },
549            transaction_dependencies.into_iter().collect(),
550        );
551
552        (inner, effects)
553    }
554
555    /// An internal check of the invariants (will only fire in debug)
556    #[cfg(debug_assertions)]
557    fn check_invariants(&self) {
558        // Check not both deleted and written
559        debug_assert!(
560            {
561                self.execution_results
562                    .written_objects
563                    .keys()
564                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
565            },
566            "Object both written and deleted."
567        );
568
569        // Check all mutable inputs are modified
570        debug_assert!(
571            {
572                self.mutable_input_refs
573                    .keys()
574                    .all(|id| self.execution_results.modified_objects.contains(id))
575            },
576            "Mutable input not modified."
577        );
578
579        debug_assert!(
580            {
581                self.execution_results
582                    .written_objects
583                    .values()
584                    .all(|obj| obj.previous_transaction == self.tx_digest)
585            },
586            "Object previous transaction not properly set",
587        );
588    }
589
590    /// Mutate a mutable input object. This is used to mutate input objects outside of PT execution.
591    pub fn mutate_input_object(&mut self, object: Object) {
592        let id = object.id();
593        debug_assert!(self.input_objects.contains_key(&id));
594        debug_assert!(!object.is_immutable());
595        self.execution_results.modified_objects.insert(id);
596        self.execution_results.written_objects.insert(id, object);
597    }
598
599    pub fn mutate_new_or_input_object(&mut self, object: Object) {
600        let id = object.id();
601        debug_assert!(!object.is_immutable());
602        if self.input_objects.contains_key(&id) {
603            self.execution_results.modified_objects.insert(id);
604        }
605        self.execution_results.written_objects.insert(id, object);
606    }
607
608    /// Mutate a child object outside of PT. This should be used extremely rarely.
609    /// Currently it's only used by advance_epoch_safe_mode because it's all native
610    /// without PT. This should almost never be used otherwise.
611    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
612        let id = new_object.id();
613        let old_ref = old_object.compute_object_reference();
614        debug_assert_eq!(old_ref.0, id);
615        self.loaded_runtime_objects.insert(
616            id,
617            DynamicallyLoadedObjectMetadata {
618                version: old_ref.1,
619                digest: old_ref.2,
620                owner: old_object.owner.clone(),
621                storage_rebate: old_object.storage_rebate,
622                previous_transaction: old_object.previous_transaction,
623            },
624        );
625        self.execution_results.modified_objects.insert(id);
626        self.execution_results
627            .written_objects
628            .insert(id, new_object);
629    }
630
631    /// Upgrade system package during epoch change. This requires special treatment
632    /// since the system package to be upgraded is not in the input objects.
633    /// We could probably fix above to make it less special.
634    pub fn upgrade_system_package(&mut self, package: Object) {
635        let id = package.id();
636        assert!(package.is_package() && is_system_package(id));
637        self.execution_results.modified_objects.insert(id);
638        self.execution_results.written_objects.insert(id, package);
639    }
640
641    /// Crate a new objcet. This is used to create objects outside of PT execution.
642    pub fn create_object(&mut self, object: Object) {
643        // Created mutable objects' versions are set to the store's lamport timestamp when it is
644        // committed to effects. Creating an object at a non-zero version risks violating the
645        // lamport timestamp invariant (that a transaction's lamport timestamp is strictly greater
646        // than all versions witnessed by the transaction).
647        debug_assert!(
648            object.is_immutable() || object.version() == SequenceNumber::MIN,
649            "Created mutable objects should not have a version set",
650        );
651        let id = object.id();
652        self.execution_results.created_object_ids.insert(id);
653        self.execution_results.written_objects.insert(id, object);
654    }
655
656    /// Delete a mutable input object. This is used to delete input objects outside of PT execution.
657    pub fn delete_input_object(&mut self, id: &ObjectID) {
658        // there should be no deletion after write
659        debug_assert!(!self.execution_results.written_objects.contains_key(id));
660        debug_assert!(self.input_objects.contains_key(id));
661        self.execution_results.modified_objects.insert(*id);
662        self.execution_results.deleted_object_ids.insert(*id);
663    }
664
665    pub fn drop_writes(&mut self) {
666        self.execution_results.drop_writes();
667        // The PTB-emitted ranges pointed into the now-cleared accumulator_events vec.
668        self.invariants.clear();
669    }
670
671    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
672        // there should be no read after delete
673        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
674        self.execution_results
675            .written_objects
676            .get(id)
677            .or_else(|| self.input_objects.get(id))
678    }
679
680    pub fn save_loaded_runtime_objects(
681        &mut self,
682        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
683    ) {
684        #[cfg(debug_assertions)]
685        {
686            for (id, v1) in &loaded_runtime_objects {
687                if let Some(v2) = self.loaded_runtime_objects.get(id) {
688                    assert_eq!(v1, v2);
689                }
690            }
691            for (id, v1) in &self.loaded_runtime_objects {
692                if let Some(v2) = loaded_runtime_objects.get(id) {
693                    assert_eq!(v1, v2);
694                }
695            }
696        }
697        // Merge the two maps because we may be calling the execution engine more than once
698        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
699        self.loaded_runtime_objects.extend(loaded_runtime_objects);
700    }
701
702    pub fn save_wrapped_object_containers(
703        &mut self,
704        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
705    ) {
706        #[cfg(debug_assertions)]
707        {
708            for (id, container1) in &wrapped_object_containers {
709                if let Some(container2) = self.wrapped_object_containers.get(id) {
710                    assert_eq!(container1, container2);
711                }
712            }
713            for (id, container1) in &self.wrapped_object_containers {
714                if let Some(container2) = wrapped_object_containers.get(id) {
715                    assert_eq!(container1, container2);
716                }
717            }
718        }
719        // Merge the two maps because we may be calling the execution engine more than once
720        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
721        self.wrapped_object_containers
722            .extend(wrapped_object_containers);
723    }
724
725    pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
726        #[cfg(debug_assertions)]
727        {
728            for id in &self.generated_runtime_ids {
729                assert!(!generated_ids.contains(id))
730            }
731            for id in &generated_ids {
732                assert!(!self.generated_runtime_ids.contains(id));
733            }
734        }
735        self.generated_runtime_ids.extend(generated_ids);
736    }
737
738    pub fn estimate_effects_size_upperbound(&self) -> usize {
739        TransactionEffects::estimate_effects_size_upperbound_v2(
740            self.execution_results.written_objects.len(),
741            self.execution_results.modified_objects.len(),
742            self.input_objects.len(),
743        )
744    }
745
746    pub fn written_objects_size(&self) -> usize {
747        self.execution_results
748            .written_objects
749            .values()
750            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
751    }
752
753    /// Validates gasless post-execution invariants:
754    /// - No new objects were created or existing objects mutated (written_objects is empty)
755    /// - The set of deleted objects exactly equals the set of input Coin objects
756    /// - Each recipient receives at least the minimum transfer amount per token type
757    /// - Unused withdrawal reservation (reservation - actual split) is 0 or >= min_amount
758    pub fn check_gasless_execution_requirements(
759        &self,
760        withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
761    ) -> Result<(), String> {
762        if !self.execution_results.written_objects.is_empty() {
763            return Err("Gasless transactions cannot create or mutate objects".to_string());
764        }
765
766        let input_coin_ids: BTreeSet<ObjectID> = self
767            .input_objects
768            .iter()
769            .filter(|(_, obj)| obj.coin_type_maybe().is_some())
770            .map(|(id, _)| *id)
771            .collect();
772        if self.execution_results.deleted_object_ids != input_coin_ids {
773            return Err(format!(
774                "Gasless transaction must destroy exactly its input Coins. \
775                 Expected: {input_coin_ids:?}, deleted: {:?}",
776                self.execution_results.deleted_object_ids
777            ));
778        }
779
780        let allowed_types =
781            sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
782
783        // Aggregate signed balance changes per (address, token_type).
784        // Positive nets are recipient deposits that must meet the minimum transfer amount.
785        let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
786            &self.execution_results.accumulator_events,
787        )
788        .fold(
789            BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
790            |mut totals, (address, token_type, signed_amount)| {
791                *totals.entry((address, token_type)).or_default() += signed_amount;
792                totals
793            },
794        );
795
796        for ((recipient, token_type), net_amount) in &net_totals {
797            if *net_amount <= 0 {
798                continue;
799            }
800            if let Some(&min_amount) = allowed_types.get(token_type)
801                && *net_amount < i128::from(min_amount)
802            {
803                return Err(format!(
804                    "Gasless transfer of {net_amount} to {recipient} is below \
805                     minimum {min_amount} for token type {token_type}"
806                ));
807            }
808        }
809
810        if let Some(reservations) = withdrawal_reservations {
811            for ((owner, token_type), &reserved) in reservations {
812                let net = net_totals
813                    .get(&(*owner, token_type.clone()))
814                    .copied()
815                    .unwrap_or(0);
816                let remaining = (reserved as i128).saturating_add(net);
817                if remaining > 0
818                    && let Some(&min_balance_remaining) = allowed_types.get(token_type)
819                    && min_balance_remaining > 0
820                    && remaining < min_balance_remaining as i128
821                {
822                    return Err(format!(
823                        "Gasless withdrawal leaves {remaining} unused for {owner}, \
824                         below minimum {min_balance_remaining} for token type {token_type}"
825                    ));
826                }
827            }
828        }
829
830        Ok(())
831    }
832
833    /// If there are unmetered storage rebate (due to system transaction), we put them into
834    /// the storage rebate of 0x5 object.
835    /// TODO: This will not work for potential future new system transactions if 0x5 is not in the input.
836    /// We should fix this.
837    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
838        if unmetered_storage_rebate == 0 {
839            // If unmetered_storage_rebate is 0, we are most likely executing the genesis transaction.
840            // And in that case we cannot mutate the 0x5 object because it's newly created.
841            // And there is no storage rebate that needs distribution anyway.
842            return;
843        }
844        tracing::debug!(
845            "Amount of unmetered storage rebate from system tx: {:?}",
846            unmetered_storage_rebate
847        );
848        let mut system_state_wrapper = self
849            .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
850            .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
851            .clone();
852        // In unmetered execution, storage_rebate field of mutated object must be 0.
853        // If not, we would be dropping SUI on the floor by overriding it.
854        assert_eq!(system_state_wrapper.storage_rebate, 0);
855        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
856        self.mutate_input_object(system_state_wrapper);
857    }
858
859    /// Add an accumulator event to the execution results.
860    pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
861        self.execution_results.accumulator_events.push(event);
862    }
863
864    /// Given an object ID, if it's not modified, returns None.
865    /// Otherwise returns its metadata, including version, digest, owner and storage rebate.
866    /// A modified object must be either a mutable input, or a loaded child object.
867    /// The only exception is when we upgrade system packages, in which case the upgraded
868    /// system packages are not part of input, but are modified.
869    fn get_object_modified_at(
870        &self,
871        object_id: &ObjectID,
872    ) -> Option<DynamicallyLoadedObjectMetadata> {
873        if self.execution_results.modified_objects.contains(object_id) {
874            Some(
875                self.mutable_input_refs
876                    .get(object_id)
877                    .map(
878                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
879                            version: *version,
880                            digest: *digest,
881                            owner: owner.clone(),
882                            // It's guaranteed that a mutable input object is an input object.
883                            storage_rebate: self.input_objects[object_id].storage_rebate,
884                            previous_transaction: self.input_objects[object_id]
885                                .previous_transaction,
886                        },
887                    )
888                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
889                    .unwrap_or_else(|| {
890                        debug_assert!(is_system_package(*object_id));
891                        let package_obj =
892                            self.store.get_package_object(object_id).unwrap().unwrap();
893                        let obj = package_obj.object();
894                        DynamicallyLoadedObjectMetadata {
895                            version: obj.version(),
896                            digest: obj.digest(),
897                            owner: obj.owner.clone(),
898                            storage_rebate: obj.storage_rebate,
899                            previous_transaction: obj.previous_transaction,
900                        }
901                    }),
902            )
903        } else {
904            None
905        }
906    }
907
908    pub fn protocol_config(&self) -> &'backing ProtocolConfig {
909        self.protocol_config
910    }
911
912    /// Cache the transaction-derived inputs the system-invariant checks need (consumed by both the
913    /// conservation checks and the ownership-invariant check). Must be called once, before
914    /// execution, after any gas-smash filtering of `gas_data`.
915    /// See [`invariants::InvariantChecker::set_transaction_inputs`].
916    pub(crate) fn set_invariant_inputs(
917        &mut self,
918        transaction_kind: &TransactionKind,
919        gas_data: &GasData,
920        transaction_signer: SuiAddress,
921    ) {
922        self.invariants
923            .set_transaction_inputs(transaction_kind, gas_data, transaction_signer);
924    }
925
926    /// Run the (read-only) SUI-conservation and balance-accumulator invariant checks.
927    /// See [`invariants::InvariantChecker::check_conservation_invariants`].
928    pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
929        &self,
930        move_vm: &Arc<MoveRuntime>,
931        enable_expensive_checks: bool,
932        cost_summary: &GasCostSummary,
933    ) -> Result<(), ExecutionError> {
934        self.invariants.check_conservation_invariants::<Mode>(
935            self,
936            move_vm,
937            enable_expensive_checks,
938            cost_summary,
939        )
940    }
941
942    /// Check that every modified object traces back to an authenticated owner.
943    /// See [`invariants::InvariantChecker::check_ownership_invariants`].
944    pub(crate) fn check_ownership_invariants(
945        &self,
946        sender: &SuiAddress,
947        sponsor: &Option<SuiAddress>,
948        gas_charger: &GasCharger,
949        mutable_inputs: &HashSet<ObjectID>,
950        is_epoch_change: bool,
951    ) -> SuiResult<()> {
952        self.invariants.check_ownership_invariants(
953            self,
954            sender,
955            sponsor,
956            gas_charger,
957            mutable_inputs,
958            is_epoch_change,
959        )
960    }
961}
962
963impl TemporaryStore<'_> {
964    /// Track storage gas for each mutable input object (including the gas coin)
965    /// and each created object. Compute storage refunds for each deleted object.
966    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
967    /// All objects will be updated with their new (current) storage rebate/cost.
968    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
969    /// overall storage rebate and cost.
970    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
971        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
972        let old_storage_rebates: Vec<_> = self
973            .execution_results
974            .written_objects
975            .keys()
976            .map(|object_id| {
977                self.get_object_modified_at(object_id)
978                    .map(|metadata| metadata.storage_rebate)
979                    .unwrap_or_default()
980            })
981            .collect();
982        for (object, old_storage_rebate) in self
983            .execution_results
984            .written_objects
985            .values_mut()
986            .zip_debug_eq(old_storage_rebates)
987        {
988            // new object size
989            let new_object_size = object.object_size_for_gas_metering();
990            // track changes and compute the new object `storage_rebate`
991            let new_storage_rebate = gas_charger.track_storage_mutation(
992                object.id(),
993                new_object_size,
994                old_storage_rebate,
995            );
996            object.storage_rebate = new_storage_rebate;
997        }
998
999        self.collect_rebate(gas_charger);
1000    }
1001
1002    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
1003        for object_id in &self.execution_results.modified_objects {
1004            if self
1005                .execution_results
1006                .written_objects
1007                .contains_key(object_id)
1008            {
1009                continue;
1010            }
1011            // get and track the deleted object `storage_rebate`
1012            let storage_rebate = self
1013                .get_object_modified_at(object_id)
1014                // Unwrap is safe because this loop iterates through all modified objects.
1015                .unwrap()
1016                .storage_rebate;
1017            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
1018        }
1019    }
1020
1021    pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1022        &self,
1023    ) -> Result<(), Mode::Error> {
1024        assert_invariant!(
1025            self.execution_results
1026                .created_object_ids
1027                .iter()
1028                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1029                    && !self.execution_results.modified_objects.contains(id)),
1030            "Created object IDs cannot also be deleted or modified"
1031        );
1032        assert_invariant!(
1033            self.execution_results.modified_objects.iter().all(|id| {
1034                self.mutable_input_refs.contains_key(id)
1035                    || self.loaded_runtime_objects.contains_key(id)
1036                    || is_system_package(*id)
1037            }),
1038            "A modified object must be either a mutable input, a loaded child object, or a system package"
1039        );
1040        Ok(())
1041    }
1042}
1043//==============================================================================
1044// Charge gas current - end
1045//==============================================================================
1046
1047impl TemporaryStore<'_> {
1048    pub fn advance_epoch_safe_mode(
1049        &mut self,
1050        params: &AdvanceEpochParams,
1051        protocol_config: &ProtocolConfig,
1052    ) {
1053        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
1054            .expect("System state wrapper object must exist");
1055        let (old_object, new_object) =
1056            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
1057        self.mutate_child_object(old_object, new_object);
1058    }
1059}
1060
1061impl RuntimeObjectResolver for TemporaryStore<'_> {
1062    fn read_child_object(
1063        &self,
1064        parent: &ObjectID,
1065        child: &ObjectID,
1066        child_version_upper_bound: SequenceNumber,
1067    ) -> SuiResult<Option<Object>> {
1068        let obj_opt = self.execution_results.written_objects.get(child);
1069        if obj_opt.is_some() {
1070            Ok(obj_opt.cloned())
1071        } else {
1072            let _scope = monitored_scope("Execution::read_child_object");
1073            self.store
1074                .read_child_object(parent, child, child_version_upper_bound)
1075        }
1076    }
1077
1078    fn get_object_received_at_version(
1079        &self,
1080        owner: &ObjectID,
1081        receiving_object_id: &ObjectID,
1082        receive_object_at_version: SequenceNumber,
1083        epoch_id: EpochId,
1084    ) -> SuiResult<Option<Object>> {
1085        // You should never be able to try and receive an object after deleting it or writing it in the same
1086        // transaction since `Receiving` doesn't have copy.
1087        debug_assert!(
1088            !self
1089                .execution_results
1090                .written_objects
1091                .contains_key(receiving_object_id)
1092        );
1093        debug_assert!(
1094            !self
1095                .execution_results
1096                .deleted_object_ids
1097                .contains(receiving_object_id)
1098        );
1099        self.store.get_object_received_at_version(
1100            owner,
1101            receiving_object_id,
1102            receive_object_at_version,
1103            epoch_id,
1104        )
1105    }
1106}
1107
1108/// Compares the owner and payload of an object.
1109/// This is used to detect illegal writes to non-exclusive write objects.
1110fn was_object_mutated(object: &Object, original: &Object) -> bool {
1111    let data_equal = match (&object.data, &original.data) {
1112        (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1113        // We don't have a use for package content-equality, so we remain as strict as
1114        // possible for now.
1115        (Data::Package(a), Data::Package(b)) => a == b,
1116        _ => false,
1117    };
1118
1119    let owner_equal = match (&object.owner, &original.owner) {
1120        // We don't compare initial shared versions, because re-shared objects do not have the
1121        // correct initial shared version at this point in time, and this field is not something
1122        // that can be modified by a single transaction anyway.
1123        (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1124        (
1125            Owner::ConsensusAddressOwner { owner: a, .. },
1126            Owner::ConsensusAddressOwner { owner: b, .. },
1127        ) => a == b,
1128        (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1129        (Owner::Immutable, Owner::Immutable) => true,
1130        (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1131        (
1132            Owner::Party {
1133                permissions: a,
1134                start_version: _,
1135            },
1136            Owner::Party {
1137                permissions: b,
1138                start_version: _,
1139            },
1140        ) => a == b,
1141
1142        // Keep the left hand side of the match exhaustive to catch future
1143        // changes to Owner
1144        (Owner::AddressOwner(_), _)
1145        | (Owner::Immutable, _)
1146        | (Owner::ObjectOwner(_), _)
1147        | (Owner::Shared { .. }, _)
1148        | (Owner::ConsensusAddressOwner { .. }, _)
1149        | (Owner::Party { .. }, _) => false,
1150    };
1151
1152    !data_equal || !owner_equal
1153}
1154
1155impl Storage for TemporaryStore<'_> {
1156    fn reset(&mut self) {
1157        self.drop_writes();
1158    }
1159
1160    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1161        TemporaryStore::read_object(self, id)
1162    }
1163
1164    /// Take execution results v2, and translate it back to be compatible with effects v1.
1165    fn record_execution_results(
1166        &mut self,
1167        results: ExecutionResults,
1168    ) -> Result<(), ExecutionError> {
1169        let ExecutionResults::V2(mut results) = results else {
1170            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1171        };
1172
1173        // for all non-exclusive write inputs, remove them from written objects
1174        let mut to_remove = Vec::new();
1175        for (id, original) in &self.non_exclusive_input_original_versions {
1176            // Object must be present in `written_objects` and identical
1177            if results
1178                .written_objects
1179                .get(id)
1180                .map(|obj| was_object_mutated(obj, original))
1181                .unwrap_or(true)
1182            {
1183                return Err(ExecutionError::new_with_source(
1184                    ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1185                    "Non-exclusive write input object has been modified or deleted",
1186                ));
1187            }
1188            to_remove.push(*id);
1189        }
1190
1191        for id in to_remove {
1192            results.written_objects.remove(&id);
1193            results.modified_objects.remove(&id);
1194        }
1195
1196        // It's important to merge instead of override results because it's
1197        // possible to execute PT more than once during tx execution.
1198        // Track the index range of accumulator events brought in here as PTB-emitted; the
1199        // address-balance change invariant (run inside `run_conservation_checks`) uses this
1200        // set to distinguish trusted PTB-emitted events from runtime-emitted ones.
1201        let event_start = self.execution_results.accumulator_events.len();
1202        self.execution_results.merge_results(
1203            results, /* consistent_merge */ true, /* invariant_checks */ true,
1204        )?;
1205        let event_end = self.execution_results.accumulator_events.len();
1206        self.invariants
1207            .record_ptb_event_range(event_start, event_end);
1208
1209        Ok(())
1210    }
1211
1212    fn save_loaded_runtime_objects(
1213        &mut self,
1214        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1215    ) {
1216        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1217    }
1218
1219    fn save_wrapped_object_containers(
1220        &mut self,
1221        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1222    ) {
1223        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1224    }
1225
1226    fn check_coin_deny_list(
1227        &self,
1228        receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1229    ) -> DenyListResult {
1230        let result = check_coin_deny_list_v2_during_execution(
1231            receiving_funds_type_and_owners,
1232            self.cur_epoch,
1233            self.store.as_object_store(),
1234        );
1235        // The denylist object is only loaded if there are regulated transfers.
1236        // And also if we already have it in the input there is no need to commit it again in the effects.
1237        if result.num_non_gas_coin_owners > 0
1238            && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1239        {
1240            self.loaded_per_epoch_config_objects
1241                .write()
1242                .insert(SUI_DENY_LIST_OBJECT_ID);
1243        }
1244        result
1245    }
1246
1247    fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1248        TemporaryStore::save_generated_object_ids(self, generated_ids)
1249    }
1250}
1251
1252impl BackingPackageStore for TemporaryStore<'_> {
1253    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1254        // We first check the objects in the temporary store because in non-production code path,
1255        // it is possible to read packages that are just written in the same transaction.
1256        // This can happen for example when we run the expensive conservation checks, where we may
1257        // look into the types of each written object in the output, and some of them need the
1258        // newly written packages for type checking.
1259        // In production path though, this should never happen.
1260        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1261            Ok(Some(PackageObject::new(obj.clone())))
1262        } else {
1263            self.store.get_package_object(package_id).inspect(|obj| {
1264                // Track object but leave unchanged
1265                if let Some(v) = obj
1266                    && !self
1267                        .runtime_packages_loaded_from_db
1268                        .read()
1269                        .contains_key(package_id)
1270                {
1271                    // TODO: Can this lock ever block execution?
1272                    // TODO: Another way to avoid the cost of maintaining this map is to not
1273                    // enable it in normal runs, and if a fork is detected, rerun it with a flag
1274                    // turned on and start populating this field.
1275                    self.runtime_packages_loaded_from_db
1276                        .write()
1277                        .insert(*package_id, v.clone());
1278                }
1279            })
1280        }
1281    }
1282}