Skip to main content

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