Skip to main content

sui_adapter_latest/
temporary_store.rs

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