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