Skip to main content

sui_adapter_latest/
temporary_store.rs

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