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