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, PaymentLocation};
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_charger: &mut GasCharger,
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        // In the case of special transactions that don't require a gas object,
431        // we don't really care about the effects to gas, just use the input for it.
432        // Gas coins are guaranteed to be at least size 1 and if more than 1
433        // the first coin is where all the others are merged.
434        let gas_coin = gas_charger
435            .gas_payment_amount()
436            .and_then(|gp| match gp.location {
437                PaymentLocation::Coin(coin_id) => Some(coin_id),
438                PaymentLocation::AddressBalance(_) => None,
439            });
440
441        let object_changes = self.get_object_changes();
442
443        let lamport_version = self.lamport_timestamp;
444        // TODO: Cleanup this clone. Potentially add unchanged_shraed_objects directly to InnerTempStore.
445        let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
446        let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
447            shared_object_refs,
448            loaded_per_epoch_config_objects,
449            &object_changes,
450        );
451        let inner = self.into_inner(accumulator_running_max_withdraws);
452
453        let effects = TransactionEffects::new_from_execution_v2(
454            status,
455            epoch,
456            gas_cost_summary,
457            unchanged_consensus_objects,
458            *transaction_digest,
459            lamport_version,
460            object_changes,
461            gas_coin,
462            if inner.events.data.is_empty() {
463                None
464            } else {
465                Some(inner.events.digest())
466            },
467            transaction_dependencies.into_iter().collect(),
468        );
469
470        (inner, effects)
471    }
472
473    /// An internal check of the invariants (will only fire in debug)
474    #[cfg(debug_assertions)]
475    fn check_invariants(&self) {
476        // Check not both deleted and written
477        debug_assert!(
478            {
479                self.execution_results
480                    .written_objects
481                    .keys()
482                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
483            },
484            "Object both written and deleted."
485        );
486
487        // Check all mutable inputs are modified
488        debug_assert!(
489            {
490                self.mutable_input_refs
491                    .keys()
492                    .all(|id| self.execution_results.modified_objects.contains(id))
493            },
494            "Mutable input not modified."
495        );
496
497        debug_assert!(
498            {
499                self.execution_results
500                    .written_objects
501                    .values()
502                    .all(|obj| obj.previous_transaction == self.tx_digest)
503            },
504            "Object previous transaction not properly set",
505        );
506    }
507
508    /// Mutate a mutable input object. This is used to mutate input objects outside of PT execution.
509    pub fn mutate_input_object(&mut self, object: Object) {
510        let id = object.id();
511        debug_assert!(self.input_objects.contains_key(&id));
512        debug_assert!(!object.is_immutable());
513        self.execution_results.modified_objects.insert(id);
514        self.execution_results.written_objects.insert(id, object);
515    }
516
517    pub fn mutate_new_or_input_object(&mut self, object: Object) {
518        let id = object.id();
519        debug_assert!(!object.is_immutable());
520        if self.input_objects.contains_key(&id) {
521            self.execution_results.modified_objects.insert(id);
522        }
523        self.execution_results.written_objects.insert(id, object);
524    }
525
526    /// Mutate a child object outside of PT. This should be used extremely rarely.
527    /// Currently it's only used by advance_epoch_safe_mode because it's all native
528    /// without PT. This should almost never be used otherwise.
529    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
530        let id = new_object.id();
531        let old_ref = old_object.compute_object_reference();
532        debug_assert_eq!(old_ref.0, id);
533        self.loaded_runtime_objects.insert(
534            id,
535            DynamicallyLoadedObjectMetadata {
536                version: old_ref.1,
537                digest: old_ref.2,
538                owner: old_object.owner.clone(),
539                storage_rebate: old_object.storage_rebate,
540                previous_transaction: old_object.previous_transaction,
541            },
542        );
543        self.execution_results.modified_objects.insert(id);
544        self.execution_results
545            .written_objects
546            .insert(id, new_object);
547    }
548
549    /// Upgrade system package during epoch change. This requires special treatment
550    /// since the system package to be upgraded is not in the input objects.
551    /// We could probably fix above to make it less special.
552    pub fn upgrade_system_package(&mut self, package: Object) {
553        let id = package.id();
554        assert!(package.is_package() && is_system_package(id));
555        self.execution_results.modified_objects.insert(id);
556        self.execution_results.written_objects.insert(id, package);
557    }
558
559    /// Crate a new objcet. This is used to create objects outside of PT execution.
560    pub fn create_object(&mut self, object: Object) {
561        // Created mutable objects' versions are set to the store's lamport timestamp when it is
562        // committed to effects. Creating an object at a non-zero version risks violating the
563        // lamport timestamp invariant (that a transaction's lamport timestamp is strictly greater
564        // than all versions witnessed by the transaction).
565        debug_assert!(
566            object.is_immutable() || object.version() == SequenceNumber::MIN,
567            "Created mutable objects should not have a version set",
568        );
569        let id = object.id();
570        self.execution_results.created_object_ids.insert(id);
571        self.execution_results.written_objects.insert(id, object);
572    }
573
574    /// Delete a mutable input object. This is used to delete input objects outside of PT execution.
575    pub fn delete_input_object(&mut self, id: &ObjectID) {
576        // there should be no deletion after write
577        debug_assert!(!self.execution_results.written_objects.contains_key(id));
578        debug_assert!(self.input_objects.contains_key(id));
579        self.execution_results.modified_objects.insert(*id);
580        self.execution_results.deleted_object_ids.insert(*id);
581    }
582
583    pub fn drop_writes(&mut self) {
584        self.execution_results.drop_writes();
585        // The PTB-emitted ranges pointed into the now-cleared accumulator_events vec.
586        self.invariants.clear();
587    }
588
589    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
590        // there should be no read after delete
591        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
592        self.execution_results
593            .written_objects
594            .get(id)
595            .or_else(|| self.input_objects.get(id))
596    }
597
598    pub fn save_loaded_runtime_objects(
599        &mut self,
600        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
601    ) {
602        #[cfg(debug_assertions)]
603        {
604            for (id, v1) in &loaded_runtime_objects {
605                if let Some(v2) = self.loaded_runtime_objects.get(id) {
606                    assert_eq!(v1, v2);
607                }
608            }
609            for (id, v1) in &self.loaded_runtime_objects {
610                if let Some(v2) = loaded_runtime_objects.get(id) {
611                    assert_eq!(v1, v2);
612                }
613            }
614        }
615        // Merge the two maps because we may be calling the execution engine more than once
616        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
617        self.loaded_runtime_objects.extend(loaded_runtime_objects);
618    }
619
620    pub fn save_wrapped_object_containers(
621        &mut self,
622        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
623    ) {
624        #[cfg(debug_assertions)]
625        {
626            for (id, container1) in &wrapped_object_containers {
627                if let Some(container2) = self.wrapped_object_containers.get(id) {
628                    assert_eq!(container1, container2);
629                }
630            }
631            for (id, container1) in &self.wrapped_object_containers {
632                if let Some(container2) = wrapped_object_containers.get(id) {
633                    assert_eq!(container1, container2);
634                }
635            }
636        }
637        // Merge the two maps because we may be calling the execution engine more than once
638        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
639        self.wrapped_object_containers
640            .extend(wrapped_object_containers);
641    }
642
643    pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
644        #[cfg(debug_assertions)]
645        {
646            for id in &self.generated_runtime_ids {
647                assert!(!generated_ids.contains(id))
648            }
649            for id in &generated_ids {
650                assert!(!self.generated_runtime_ids.contains(id));
651            }
652        }
653        self.generated_runtime_ids.extend(generated_ids);
654    }
655
656    pub fn estimate_effects_size_upperbound(&self) -> usize {
657        TransactionEffects::estimate_effects_size_upperbound_v2(
658            self.execution_results.written_objects.len(),
659            self.execution_results.modified_objects.len(),
660            self.input_objects.len(),
661        )
662    }
663
664    pub fn written_objects_size(&self) -> usize {
665        self.execution_results
666            .written_objects
667            .values()
668            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
669    }
670
671    /// Validates gasless post-execution invariants:
672    /// - No new objects were created or existing objects mutated (written_objects is empty)
673    /// - The set of deleted objects exactly equals the set of input Coin objects
674    /// - Each recipient receives at least the minimum transfer amount per token type
675    /// - Unused withdrawal reservation (reservation - actual split) is 0 or >= min_amount
676    pub fn check_gasless_execution_requirements(
677        &self,
678        withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
679    ) -> Result<(), String> {
680        if !self.execution_results.written_objects.is_empty() {
681            return Err("Gasless transactions cannot create or mutate objects".to_string());
682        }
683
684        let input_coin_ids: BTreeSet<ObjectID> = self
685            .input_objects
686            .iter()
687            .filter(|(_, obj)| obj.coin_type_maybe().is_some())
688            .map(|(id, _)| *id)
689            .collect();
690        if self.execution_results.deleted_object_ids != input_coin_ids {
691            return Err(format!(
692                "Gasless transaction must destroy exactly its input Coins. \
693                 Expected: {input_coin_ids:?}, deleted: {:?}",
694                self.execution_results.deleted_object_ids
695            ));
696        }
697
698        let allowed_types =
699            sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
700
701        // Aggregate signed balance changes per (address, token_type).
702        // Positive nets are recipient deposits that must meet the minimum transfer amount.
703        let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
704            &self.execution_results.accumulator_events,
705        )
706        .fold(
707            BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
708            |mut totals, (address, token_type, signed_amount)| {
709                *totals.entry((address, token_type)).or_default() += signed_amount;
710                totals
711            },
712        );
713
714        for ((recipient, token_type), net_amount) in &net_totals {
715            if *net_amount <= 0 {
716                continue;
717            }
718            if let Some(&min_amount) = allowed_types.get(token_type)
719                && *net_amount < i128::from(min_amount)
720            {
721                return Err(format!(
722                    "Gasless transfer of {net_amount} to {recipient} is below \
723                     minimum {min_amount} for token type {token_type}"
724                ));
725            }
726        }
727
728        if let Some(reservations) = withdrawal_reservations {
729            for ((owner, token_type), &reserved) in reservations {
730                let net = net_totals
731                    .get(&(*owner, token_type.clone()))
732                    .copied()
733                    .unwrap_or(0);
734                let remaining = (reserved as i128).saturating_add(net);
735                if remaining > 0
736                    && let Some(&min_balance_remaining) = allowed_types.get(token_type)
737                    && min_balance_remaining > 0
738                    && remaining < min_balance_remaining as i128
739                {
740                    return Err(format!(
741                        "Gasless withdrawal leaves {remaining} unused for {owner}, \
742                         below minimum {min_balance_remaining} for token type {token_type}"
743                    ));
744                }
745            }
746        }
747
748        Ok(())
749    }
750
751    /// If there are unmetered storage rebate (due to system transaction), we put them into
752    /// the storage rebate of 0x5 object.
753    /// TODO: This will not work for potential future new system transactions if 0x5 is not in the input.
754    /// We should fix this.
755    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
756        if unmetered_storage_rebate == 0 {
757            // If unmetered_storage_rebate is 0, we are most likely executing the genesis transaction.
758            // And in that case we cannot mutate the 0x5 object because it's newly created.
759            // And there is no storage rebate that needs distribution anyway.
760            return;
761        }
762        tracing::debug!(
763            "Amount of unmetered storage rebate from system tx: {:?}",
764            unmetered_storage_rebate
765        );
766        let mut system_state_wrapper = self
767            .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
768            .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
769            .clone();
770        // In unmetered execution, storage_rebate field of mutated object must be 0.
771        // If not, we would be dropping SUI on the floor by overriding it.
772        assert_eq!(system_state_wrapper.storage_rebate, 0);
773        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
774        self.mutate_input_object(system_state_wrapper);
775    }
776
777    /// Add an accumulator event to the execution results.
778    pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
779        self.execution_results.accumulator_events.push(event);
780    }
781
782    /// Given an object ID, if it's not modified, returns None.
783    /// Otherwise returns its metadata, including version, digest, owner and storage rebate.
784    /// A modified object must be either a mutable input, or a loaded child object.
785    /// The only exception is when we upgrade system packages, in which case the upgraded
786    /// system packages are not part of input, but are modified.
787    fn get_object_modified_at(
788        &self,
789        object_id: &ObjectID,
790    ) -> Option<DynamicallyLoadedObjectMetadata> {
791        if self.execution_results.modified_objects.contains(object_id) {
792            Some(
793                self.mutable_input_refs
794                    .get(object_id)
795                    .map(
796                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
797                            version: *version,
798                            digest: *digest,
799                            owner: owner.clone(),
800                            // It's guaranteed that a mutable input object is an input object.
801                            storage_rebate: self.input_objects[object_id].storage_rebate,
802                            previous_transaction: self.input_objects[object_id]
803                                .previous_transaction,
804                        },
805                    )
806                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
807                    .unwrap_or_else(|| {
808                        debug_assert!(is_system_package(*object_id));
809                        let package_obj =
810                            self.store.get_package_object(object_id).unwrap().unwrap();
811                        let obj = package_obj.object();
812                        DynamicallyLoadedObjectMetadata {
813                            version: obj.version(),
814                            digest: obj.digest(),
815                            owner: obj.owner.clone(),
816                            storage_rebate: obj.storage_rebate,
817                            previous_transaction: obj.previous_transaction,
818                        }
819                    }),
820            )
821        } else {
822            None
823        }
824    }
825
826    pub fn protocol_config(&self) -> &'backing ProtocolConfig {
827        self.protocol_config
828    }
829
830    /// Cache the transaction-derived inputs the system-invariant checks need (consumed by both the
831    /// conservation checks and the ownership-invariant check). Must be called once, before
832    /// execution, after any gas-smash filtering of `gas_data`.
833    /// See [`invariants::InvariantChecker::set_transaction_inputs`].
834    pub(crate) fn set_invariant_inputs(
835        &mut self,
836        transaction_kind: &TransactionKind,
837        gas_data: &GasData,
838        transaction_signer: SuiAddress,
839    ) {
840        self.invariants
841            .set_transaction_inputs(transaction_kind, gas_data, transaction_signer);
842    }
843
844    /// Run the (read-only) SUI-conservation and balance-accumulator invariant checks.
845    /// See [`invariants::InvariantChecker::check_conservation_invariants`].
846    pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
847        &self,
848        move_vm: &Arc<MoveRuntime>,
849        enable_expensive_checks: bool,
850        cost_summary: &GasCostSummary,
851    ) -> Result<(), ExecutionError> {
852        self.invariants.check_conservation_invariants::<Mode>(
853            self,
854            move_vm,
855            enable_expensive_checks,
856            cost_summary,
857        )
858    }
859
860    /// Check that every modified object traces back to an authenticated owner.
861    /// See [`invariants::InvariantChecker::check_ownership_invariants`].
862    pub(crate) fn check_ownership_invariants(
863        &self,
864        sender: &SuiAddress,
865        sponsor: &Option<SuiAddress>,
866        gas_charger: &GasCharger,
867        mutable_inputs: &HashSet<ObjectID>,
868        is_epoch_change: bool,
869    ) -> SuiResult<()> {
870        self.invariants.check_ownership_invariants(
871            self,
872            sender,
873            sponsor,
874            gas_charger,
875            mutable_inputs,
876            is_epoch_change,
877        )
878    }
879}
880
881impl TemporaryStore<'_> {
882    /// Track storage gas for each mutable input object (including the gas coin)
883    /// and each created object. Compute storage refunds for each deleted object.
884    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
885    /// All objects will be updated with their new (current) storage rebate/cost.
886    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
887    /// overall storage rebate and cost.
888    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
889        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
890        let old_storage_rebates: Vec<_> = self
891            .execution_results
892            .written_objects
893            .keys()
894            .map(|object_id| {
895                self.get_object_modified_at(object_id)
896                    .map(|metadata| metadata.storage_rebate)
897                    .unwrap_or_default()
898            })
899            .collect();
900        for (object, old_storage_rebate) in self
901            .execution_results
902            .written_objects
903            .values_mut()
904            .zip_debug_eq(old_storage_rebates)
905        {
906            // new object size
907            let new_object_size = object.object_size_for_gas_metering();
908            // track changes and compute the new object `storage_rebate`
909            let new_storage_rebate = gas_charger.track_storage_mutation(
910                object.id(),
911                new_object_size,
912                old_storage_rebate,
913            );
914            object.storage_rebate = new_storage_rebate;
915        }
916
917        self.collect_rebate(gas_charger);
918    }
919
920    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
921        for object_id in &self.execution_results.modified_objects {
922            if self
923                .execution_results
924                .written_objects
925                .contains_key(object_id)
926            {
927                continue;
928            }
929            // get and track the deleted object `storage_rebate`
930            let storage_rebate = self
931                .get_object_modified_at(object_id)
932                // Unwrap is safe because this loop iterates through all modified objects.
933                .unwrap()
934                .storage_rebate;
935            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
936        }
937    }
938
939    pub fn check_execution_results_consistency<Mode: ExecutionMode>(
940        &self,
941    ) -> Result<(), Mode::Error> {
942        assert_invariant!(
943            self.execution_results
944                .created_object_ids
945                .iter()
946                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
947                    && !self.execution_results.modified_objects.contains(id)),
948            "Created object IDs cannot also be deleted or modified"
949        );
950        assert_invariant!(
951            self.execution_results.modified_objects.iter().all(|id| {
952                self.mutable_input_refs.contains_key(id)
953                    || self.loaded_runtime_objects.contains_key(id)
954                    || is_system_package(*id)
955            }),
956            "A modified object must be either a mutable input, a loaded child object, or a system package"
957        );
958        Ok(())
959    }
960}
961//==============================================================================
962// Charge gas current - end
963//==============================================================================
964
965impl TemporaryStore<'_> {
966    pub fn advance_epoch_safe_mode(
967        &mut self,
968        params: &AdvanceEpochParams,
969        protocol_config: &ProtocolConfig,
970    ) {
971        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
972            .expect("System state wrapper object must exist");
973        let (old_object, new_object) =
974            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
975        self.mutate_child_object(old_object, new_object);
976    }
977}
978
979impl ChildObjectResolver for TemporaryStore<'_> {
980    fn read_child_object(
981        &self,
982        parent: &ObjectID,
983        child: &ObjectID,
984        child_version_upper_bound: SequenceNumber,
985    ) -> SuiResult<Option<Object>> {
986        let obj_opt = self.execution_results.written_objects.get(child);
987        if obj_opt.is_some() {
988            Ok(obj_opt.cloned())
989        } else {
990            let _scope = monitored_scope("Execution::read_child_object");
991            self.store
992                .read_child_object(parent, child, child_version_upper_bound)
993        }
994    }
995
996    fn get_object_received_at_version(
997        &self,
998        owner: &ObjectID,
999        receiving_object_id: &ObjectID,
1000        receive_object_at_version: SequenceNumber,
1001        epoch_id: EpochId,
1002    ) -> SuiResult<Option<Object>> {
1003        // You should never be able to try and receive an object after deleting it or writing it in the same
1004        // transaction since `Receiving` doesn't have copy.
1005        debug_assert!(
1006            !self
1007                .execution_results
1008                .written_objects
1009                .contains_key(receiving_object_id)
1010        );
1011        debug_assert!(
1012            !self
1013                .execution_results
1014                .deleted_object_ids
1015                .contains(receiving_object_id)
1016        );
1017        self.store.get_object_received_at_version(
1018            owner,
1019            receiving_object_id,
1020            receive_object_at_version,
1021            epoch_id,
1022        )
1023    }
1024}
1025
1026/// Compares the owner and payload of an object.
1027/// This is used to detect illegal writes to non-exclusive write objects.
1028fn was_object_mutated(object: &Object, original: &Object) -> bool {
1029    let data_equal = match (&object.data, &original.data) {
1030        (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1031        // We don't have a use for package content-equality, so we remain as strict as
1032        // possible for now.
1033        (Data::Package(a), Data::Package(b)) => a == b,
1034        _ => false,
1035    };
1036
1037    let owner_equal = match (&object.owner, &original.owner) {
1038        // We don't compare initial shared versions, because re-shared objects do not have the
1039        // correct initial shared version at this point in time, and this field is not something
1040        // that can be modified by a single transaction anyway.
1041        (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1042        (
1043            Owner::ConsensusAddressOwner { owner: a, .. },
1044            Owner::ConsensusAddressOwner { owner: b, .. },
1045        ) => a == b,
1046        (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1047        (Owner::Immutable, Owner::Immutable) => true,
1048        (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1049        (
1050            Owner::Party {
1051                permissions: a,
1052                start_version: _,
1053            },
1054            Owner::Party {
1055                permissions: b,
1056                start_version: _,
1057            },
1058        ) => a == b,
1059
1060        // Keep the left hand side of the match exhaustive to catch future
1061        // changes to Owner
1062        (Owner::AddressOwner(_), _)
1063        | (Owner::Immutable, _)
1064        | (Owner::ObjectOwner(_), _)
1065        | (Owner::Shared { .. }, _)
1066        | (Owner::ConsensusAddressOwner { .. }, _)
1067        | (Owner::Party { .. }, _) => false,
1068    };
1069
1070    !data_equal || !owner_equal
1071}
1072
1073impl Storage for TemporaryStore<'_> {
1074    fn reset(&mut self) {
1075        self.drop_writes();
1076    }
1077
1078    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1079        TemporaryStore::read_object(self, id)
1080    }
1081
1082    /// Take execution results v2, and translate it back to be compatible with effects v1.
1083    fn record_execution_results(
1084        &mut self,
1085        results: ExecutionResults,
1086    ) -> Result<(), ExecutionError> {
1087        let ExecutionResults::V2(mut results) = results else {
1088            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1089        };
1090
1091        // for all non-exclusive write inputs, remove them from written objects
1092        let mut to_remove = Vec::new();
1093        for (id, original) in &self.non_exclusive_input_original_versions {
1094            // Object must be present in `written_objects` and identical
1095            if results
1096                .written_objects
1097                .get(id)
1098                .map(|obj| was_object_mutated(obj, original))
1099                .unwrap_or(true)
1100            {
1101                return Err(ExecutionError::new_with_source(
1102                    ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1103                    "Non-exclusive write input object has been modified or deleted",
1104                ));
1105            }
1106            to_remove.push(*id);
1107        }
1108
1109        for id in to_remove {
1110            results.written_objects.remove(&id);
1111            results.modified_objects.remove(&id);
1112        }
1113
1114        // It's important to merge instead of override results because it's
1115        // possible to execute PT more than once during tx execution.
1116        // Track the index range of accumulator events brought in here as PTB-emitted; the
1117        // address-balance change invariant (run inside `run_conservation_checks`) uses this
1118        // set to distinguish trusted PTB-emitted events from runtime-emitted ones.
1119        let event_start = self.execution_results.accumulator_events.len();
1120        self.execution_results.merge_results(
1121            results, /* consistent_merge */ true, /* invariant_checks */ true,
1122        )?;
1123        let event_end = self.execution_results.accumulator_events.len();
1124        self.invariants
1125            .record_ptb_event_range(event_start, event_end);
1126
1127        Ok(())
1128    }
1129
1130    fn save_loaded_runtime_objects(
1131        &mut self,
1132        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1133    ) {
1134        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1135    }
1136
1137    fn save_wrapped_object_containers(
1138        &mut self,
1139        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1140    ) {
1141        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1142    }
1143
1144    fn check_coin_deny_list(
1145        &self,
1146        receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1147    ) -> DenyListResult {
1148        let result = check_coin_deny_list_v2_during_execution(
1149            receiving_funds_type_and_owners,
1150            self.cur_epoch,
1151            self.store.as_object_store(),
1152        );
1153        // The denylist object is only loaded if there are regulated transfers.
1154        // And also if we already have it in the input there is no need to commit it again in the effects.
1155        if result.num_non_gas_coin_owners > 0
1156            && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1157        {
1158            self.loaded_per_epoch_config_objects
1159                .write()
1160                .insert(SUI_DENY_LIST_OBJECT_ID);
1161        }
1162        result
1163    }
1164
1165    fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1166        TemporaryStore::save_generated_object_ids(self, generated_ids)
1167    }
1168}
1169
1170impl BackingPackageStore for TemporaryStore<'_> {
1171    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1172        // We first check the objects in the temporary store because in non-production code path,
1173        // it is possible to read packages that are just written in the same transaction.
1174        // This can happen for example when we run the expensive conservation checks, where we may
1175        // look into the types of each written object in the output, and some of them need the
1176        // newly written packages for type checking.
1177        // In production path though, this should never happen.
1178        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1179            Ok(Some(PackageObject::new(obj.clone())))
1180        } else {
1181            self.store.get_package_object(package_id).inspect(|obj| {
1182                // Track object but leave unchanged
1183                if let Some(v) = obj
1184                    && !self
1185                        .runtime_packages_loaded_from_db
1186                        .read()
1187                        .contains_key(package_id)
1188                {
1189                    // TODO: Can this lock ever block execution?
1190                    // TODO: Another way to avoid the cost of maintaining this map is to not
1191                    // enable it in normal runs, and if a fork is detected, rerun it with a flag
1192                    // turned on and start populating this field.
1193                    self.runtime_packages_loaded_from_db
1194                        .write()
1195                        .insert(*package_id, v.clone());
1196                }
1197            })
1198        }
1199    }
1200}