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