sui_adapter_latest/
temporary_store.rs

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