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        // Regardless of execution status (including aborts), we insert the previous transaction
549        // for any successfully received objects during the transaction.
550        for (id, expected_version, expected_digest) in &self.receiving_objects {
551            // If the receiving object is in the loaded runtime objects, then that means that it
552            // was actually successfully loaded (so existed, and there was authenticated mutable
553            // access to it). So we insert the previous transaction as a dependency.
554            if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
555                // Check that the expected version, digest, and owner match the loaded version,
556                // digest, and owner. If they don't then don't register a dependency.
557                // This is because this could be "spoofed" by loading a dynamic object field.
558                let loaded_via_receive = obj_meta.version == *expected_version
559                    && obj_meta.digest == *expected_digest
560                    && obj_meta.owner.is_address_owned();
561                if loaded_via_receive {
562                    transaction_dependencies.insert(obj_meta.previous_transaction);
563                }
564            }
565        }
566
567        assert!(self.protocol_config.enable_effects_v2());
568
569        let object_changes = self.get_object_changes();
570
571        let lamport_version = self.lamport_timestamp;
572        // TODO: Cleanup this clone. Potentially add unchanged_shraed_objects directly to InnerTempStore.
573        let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
574        let loaded_system_objects = self.loaded_system_objects.borrow().clone();
575        let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
576            shared_object_refs,
577            loaded_per_epoch_config_objects,
578            &object_changes,
579            loaded_system_objects,
580        );
581        let inner = self.into_inner(accumulator_running_max_withdraws);
582
583        let effects = TransactionEffects::new_from_execution_v2(
584            status,
585            epoch,
586            gas_cost_summary,
587            unchanged_consensus_objects,
588            *transaction_digest,
589            lamport_version,
590            object_changes,
591            gas_coin,
592            if inner.events.data.is_empty() {
593                None
594            } else {
595                Some(inner.events.digest())
596            },
597            transaction_dependencies.into_iter().collect(),
598        );
599
600        (inner, effects)
601    }
602
603    /// An internal check of the invariants (will only fire in debug)
604    #[cfg(debug_assertions)]
605    fn check_invariants(&self) {
606        // Check not both deleted and written
607        debug_assert!(
608            {
609                self.execution_results
610                    .written_objects
611                    .keys()
612                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
613            },
614            "Object both written and deleted."
615        );
616
617        // Check all mutable inputs are modified
618        debug_assert!(
619            {
620                self.mutable_input_refs
621                    .keys()
622                    .all(|id| self.execution_results.modified_objects.contains(id))
623            },
624            "Mutable input not modified."
625        );
626
627        debug_assert!(
628            {
629                self.execution_results
630                    .written_objects
631                    .values()
632                    .all(|obj| obj.previous_transaction == self.tx_digest)
633            },
634            "Object previous transaction not properly set",
635        );
636    }
637
638    /// Mutate a mutable input object. This is used to mutate input objects outside of PT execution.
639    pub fn mutate_input_object(&mut self, object: Object) {
640        let id = object.id();
641        debug_assert!(self.input_objects.contains_key(&id));
642        debug_assert!(!object.is_immutable());
643        self.execution_results.modified_objects.insert(id);
644        self.execution_results.written_objects.insert(id, object);
645    }
646
647    pub fn mutate_new_or_input_object(&mut self, object: Object) {
648        let id = object.id();
649        debug_assert!(!object.is_immutable());
650        if self.input_objects.contains_key(&id) {
651            self.execution_results.modified_objects.insert(id);
652        }
653        self.execution_results.written_objects.insert(id, object);
654    }
655
656    /// Mutate a child object outside of PT. This should be used extremely rarely.
657    /// Currently it's only used by advance_epoch_safe_mode because it's all native
658    /// without PT. This should almost never be used otherwise.
659    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
660        let id = new_object.id();
661        let old_ref = old_object.compute_object_reference();
662        debug_assert_eq!(old_ref.0, id);
663        self.loaded_runtime_objects.insert(
664            id,
665            DynamicallyLoadedObjectMetadata {
666                version: old_ref.1,
667                digest: old_ref.2,
668                owner: old_object.owner.clone(),
669                storage_rebate: old_object.storage_rebate,
670                previous_transaction: old_object.previous_transaction,
671            },
672        );
673        self.execution_results.modified_objects.insert(id);
674        self.execution_results
675            .written_objects
676            .insert(id, new_object);
677    }
678
679    /// Upgrade system package during epoch change. This requires special treatment
680    /// since the system package to be upgraded is not in the input objects.
681    /// We could probably fix above to make it less special.
682    pub fn upgrade_system_package(&mut self, package: Object) {
683        let id = package.id();
684        assert!(package.is_package() && is_system_package(id));
685        self.execution_results.modified_objects.insert(id);
686        self.execution_results.written_objects.insert(id, package);
687    }
688
689    /// Crate a new objcet. This is used to create objects outside of PT execution.
690    pub fn create_object(&mut self, object: Object) {
691        // Created mutable objects' versions are set to the store's lamport timestamp when it is
692        // committed to effects. Creating an object at a non-zero version risks violating the
693        // lamport timestamp invariant (that a transaction's lamport timestamp is strictly greater
694        // than all versions witnessed by the transaction).
695        debug_assert!(
696            object.is_immutable() || object.version() == SequenceNumber::MIN,
697            "Created mutable objects should not have a version set",
698        );
699        let id = object.id();
700        self.execution_results.created_object_ids.insert(id);
701        self.execution_results.written_objects.insert(id, object);
702    }
703
704    /// Delete a mutable input object. This is used to delete input objects outside of PT execution.
705    pub fn delete_input_object(&mut self, id: &ObjectID) {
706        // there should be no deletion after write
707        debug_assert!(!self.execution_results.written_objects.contains_key(id));
708        debug_assert!(self.input_objects.contains_key(id));
709        self.execution_results.modified_objects.insert(*id);
710        self.execution_results.deleted_object_ids.insert(*id);
711    }
712
713    pub fn drop_writes(&mut self) {
714        self.execution_results.drop_writes();
715        self.invariants = InvariantChecker::default();
716    }
717
718    /// Consume this (post-execution) store and return the store used by a `BumpOnly` exit: keep
719    /// the input-derived state as well as information about the execution needed for replay,
720    /// discard everything related to the execution results, then bump the mutable
721    /// inputs. Its effects record only those version bumps and the input dependencies.
722    pub(crate) fn into_bump_only(self) -> Self {
723        let Self {
724            // Input-derived - reused verbatim.
725            store,
726            tx_digest,
727            input_objects,
728            non_exclusive_input_original_versions,
729            stream_ended_consensus_objects,
730            lamport_timestamp,
731            mutable_input_refs,
732            receiving_objects,
733            cur_epoch,
734            protocol_config,
735            post_execution_check_inputs,
736            system_object_versions,
737            // Represents what happened during execution, which needs to be kept.
738            loaded_runtime_objects,
739            runtime_packages_loaded_from_db,
740            loaded_per_epoch_config_objects,
741            loaded_system_objects,
742            unsettled_object_funds,
743            // Execution outcomes can be discarded.
744            execution_results: _,
745            invariants: _,
746        } = self;
747        let mut bump_only = Self {
748            store,
749            tx_digest,
750            input_objects,
751            non_exclusive_input_original_versions,
752            stream_ended_consensus_objects,
753            lamport_timestamp,
754            mutable_input_refs,
755            receiving_objects,
756            cur_epoch,
757            protocol_config,
758            loaded_runtime_objects,
759            runtime_packages_loaded_from_db,
760            loaded_per_epoch_config_objects,
761            post_execution_check_inputs,
762            system_object_versions,
763            loaded_system_objects,
764            unsettled_object_funds,
765            execution_results: ExecutionResultsV2::default(),
766            invariants: InvariantChecker::default(),
767        };
768        // The only writes a BumpOnly exit records: bump the versions of the mutable inputs it locked.
769        bump_only.ensure_active_inputs_mutated();
770        bump_only
771    }
772
773    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
774        // there should be no read after delete
775        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
776        self.execution_results
777            .written_objects
778            .get(id)
779            .or_else(|| self.input_objects.get(id))
780    }
781
782    pub fn save_loaded_runtime_objects(
783        &mut self,
784        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
785    ) {
786        #[cfg(debug_assertions)]
787        {
788            for (id, v1) in &loaded_runtime_objects {
789                if let Some(v2) = self.loaded_runtime_objects.get(id) {
790                    assert_eq!(v1, v2);
791                }
792            }
793            for (id, v1) in &self.loaded_runtime_objects {
794                if let Some(v2) = loaded_runtime_objects.get(id) {
795                    assert_eq!(v1, v2);
796                }
797            }
798        }
799        // Merge the two maps because we may be calling the execution engine more than once
800        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
801        self.loaded_runtime_objects.extend(loaded_runtime_objects);
802    }
803
804    pub fn save_wrapped_object_containers(
805        &mut self,
806        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
807    ) {
808        self.invariants
809            .save_wrapped_object_containers(wrapped_object_containers);
810    }
811
812    pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
813        self.invariants.save_generated_object_ids(generated_ids);
814    }
815
816    pub fn estimate_effects_size_upperbound(&self) -> usize {
817        TransactionEffects::estimate_effects_size_upperbound_v2(
818            self.execution_results.written_objects.len(),
819            self.execution_results.modified_objects.len(),
820            self.input_objects.len(),
821        )
822    }
823
824    pub fn written_objects_size(&self) -> usize {
825        self.execution_results
826            .written_objects
827            .values()
828            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
829    }
830
831    /// Validates gasless post-execution requirements using the reservations cached when the store
832    /// is constructed.
833    pub(crate) fn check_gasless_execution_requirements(&self) -> Result<(), String> {
834        use sui_types::balance::Balance;
835
836        // Gasless requirements are expressed in coin types `T`, while the shared input reservation
837        // budget is keyed by accumulator types `Balance<T>`.
838        let withdrawal_reservations = self
839            .post_execution_check_inputs
840            .input_reservations
841            .iter()
842            .filter_map(|((owner, ty), amount)| {
843                Balance::maybe_get_balance_type_param(ty)
844                    .map(|coin_type| ((*owner, coin_type), *amount))
845            })
846            .collect();
847        self.check_gasless_execution_requirements_with_reservations(Some(&withdrawal_reservations))
848    }
849
850    /// Validates gasless post-execution requirements:
851    /// - No new objects were created or existing objects mutated (written_objects is empty)
852    /// - The set of deleted objects exactly equals the set of input Coin objects
853    /// - Each recipient receives at least the minimum transfer amount per token type
854    /// - Unused withdrawal reservation (reservation - actual split) is 0 or >= min_amount
855    ///
856    /// Parameterized entry for the legacy path, which computes its (flag-gated) reservations
857    /// out-of-band. Deleted with legacy at the next execution cut.
858    // TODO: This is kept as pub due to legacy callers. Once the legacy path is deleted in a
859    // newer executor, we can fold this into `check_gasless_execution_requirements`.
860    pub(crate) fn check_gasless_execution_requirements_with_reservations(
861        &self,
862        withdrawal_reservations: Option<&BTreeMap<(SuiAddress, TypeTag), u64>>,
863    ) -> Result<(), String> {
864        if !self.execution_results.written_objects.is_empty() {
865            return Err("Gasless transactions cannot create or mutate objects".to_string());
866        }
867
868        let input_coin_ids: BTreeSet<ObjectID> = self
869            .input_objects
870            .iter()
871            .filter(|(_, obj)| obj.coin_type_maybe().is_some())
872            .map(|(id, _)| *id)
873            .collect();
874        if self.execution_results.deleted_object_ids != input_coin_ids {
875            return Err(format!(
876                "Gasless transaction must destroy exactly its input Coins. \
877                 Expected: {input_coin_ids:?}, deleted: {:?}",
878                self.execution_results.deleted_object_ids
879            ));
880        }
881
882        let allowed_types =
883            sui_types::transaction::get_gasless_allowed_token_types(self.protocol_config);
884
885        // Aggregate signed balance changes per (address, token_type).
886        // Positive nets are recipient deposits that must meet the minimum transfer amount.
887        let net_totals = sui_types::balance_change::signed_balance_changes_from_events(
888            &self.execution_results.accumulator_events,
889        )
890        .fold(
891            BTreeMap::<(SuiAddress, TypeTag), i128>::new(),
892            |mut totals, (address, token_type, signed_amount)| {
893                *totals.entry((address, token_type)).or_default() += signed_amount;
894                totals
895            },
896        );
897
898        for ((recipient, token_type), net_amount) in &net_totals {
899            if *net_amount <= 0 {
900                continue;
901            }
902            if let Some(&min_amount) = allowed_types.get(token_type)
903                && *net_amount < i128::from(min_amount)
904            {
905                return Err(format!(
906                    "Gasless transfer of {net_amount} to {recipient} is below \
907                     minimum {min_amount} for token type {token_type}"
908                ));
909            }
910        }
911
912        if let Some(reservations) = withdrawal_reservations {
913            for ((owner, token_type), &reserved) in reservations {
914                let net = net_totals
915                    .get(&(*owner, token_type.clone()))
916                    .copied()
917                    .unwrap_or(0);
918                let remaining = (reserved as i128).saturating_add(net);
919                if remaining > 0
920                    && let Some(&min_balance_remaining) = allowed_types.get(token_type)
921                    && min_balance_remaining > 0
922                    && remaining < min_balance_remaining as i128
923                {
924                    return Err(format!(
925                        "Gasless withdrawal leaves {remaining} unused for {owner}, \
926                         below minimum {min_balance_remaining} for token type {token_type}"
927                    ));
928                }
929            }
930        }
931
932        Ok(())
933    }
934
935    /// If there are unmetered storage rebate (due to system transaction), we put them into
936    /// the storage rebate of 0x5 object.
937    /// TODO: This will not work for potential future new system transactions if 0x5 is not in the input.
938    /// We should fix this.
939    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
940        if unmetered_storage_rebate == 0 {
941            // If unmetered_storage_rebate is 0, we are most likely executing the genesis transaction.
942            // And in that case we cannot mutate the 0x5 object because it's newly created.
943            // And there is no storage rebate that needs distribution anyway.
944            return;
945        }
946        tracing::debug!(
947            "Amount of unmetered storage rebate from system tx: {:?}",
948            unmetered_storage_rebate
949        );
950        let mut system_state_wrapper = self
951            .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
952            .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
953            .clone();
954        // In unmetered execution, storage_rebate field of mutated object must be 0.
955        // If not, we would be dropping SUI on the floor by overriding it.
956        assert_eq!(system_state_wrapper.storage_rebate, 0);
957        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
958        self.mutate_input_object(system_state_wrapper);
959    }
960
961    /// Add an accumulator event to the execution results.
962    pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
963        self.execution_results.accumulator_events.push(event);
964    }
965
966    /// Given an object ID, if it's not modified, returns None.
967    /// Otherwise returns its metadata, including version, digest, owner and storage rebate.
968    /// A modified object must be either a mutable input, or a loaded child object.
969    /// The only exception is when we upgrade system packages, in which case the upgraded
970    /// system packages are not part of input, but are modified.
971    fn get_object_modified_at(
972        &self,
973        object_id: &ObjectID,
974    ) -> Option<DynamicallyLoadedObjectMetadata> {
975        if self.execution_results.modified_objects.contains(object_id) {
976            Some(
977                self.mutable_input_refs
978                    .get(object_id)
979                    .map(
980                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
981                            version: *version,
982                            digest: *digest,
983                            owner: owner.clone(),
984                            // It's guaranteed that a mutable input object is an input object.
985                            storage_rebate: self.input_objects[object_id].storage_rebate,
986                            previous_transaction: self.input_objects[object_id]
987                                .previous_transaction,
988                        },
989                    )
990                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
991                    .unwrap_or_else(|| {
992                        debug_assert!(is_system_package(*object_id));
993                        let package_obj =
994                            self.store.get_package_object(object_id).unwrap().unwrap();
995                        let obj = package_obj.object();
996                        DynamicallyLoadedObjectMetadata {
997                            version: obj.version(),
998                            digest: obj.digest(),
999                            owner: obj.owner.clone(),
1000                            storage_rebate: obj.storage_rebate,
1001                            previous_transaction: obj.previous_transaction,
1002                        }
1003                    }),
1004            )
1005        } else {
1006            None
1007        }
1008    }
1009
1010    pub fn protocol_config(&self) -> &'backing ProtocolConfig {
1011        self.protocol_config
1012    }
1013
1014    /// Run the (read-only) SUI-conservation and balance-accumulator invariant checks.
1015    /// See [`invariants::InvariantChecker::check_conservation_invariants`].
1016    pub(crate) fn check_conservation_invariants<Mode: ExecutionMode>(
1017        &self,
1018        move_vm: &Arc<MoveRuntime>,
1019        enable_expensive_checks: bool,
1020        cost_summary: &GasCostSummary,
1021    ) -> Result<(), ExecutionError> {
1022        self.invariants.check_conservation_invariants::<Mode>(
1023            self,
1024            move_vm,
1025            enable_expensive_checks,
1026            cost_summary,
1027        )
1028    }
1029
1030    /// Check that every modified object traces back to an authenticated owner.
1031    /// See [`invariants::InvariantChecker::check_ownership_invariants`].
1032    /// See [`invariants::InvariantChecker::check_published_packages`].
1033    pub(crate) fn check_published_packages(&self) -> Result<(), ExecutionError> {
1034        self.invariants.check_published_packages(self)
1035    }
1036
1037    pub(crate) fn check_ownership_invariants(
1038        &self,
1039        sender: &SuiAddress,
1040        sponsor: &Option<SuiAddress>,
1041        gas_charger: &GasCharger,
1042        is_epoch_change: bool,
1043    ) -> SuiResult<()> {
1044        self.invariants.check_ownership_invariants(
1045            self,
1046            sender,
1047            sponsor,
1048            gas_charger,
1049            is_epoch_change,
1050        )
1051    }
1052}
1053
1054impl TemporaryStore<'_> {
1055    /// Track storage gas for each mutable input object (including the gas coin)
1056    /// and each created object. Compute storage refunds for each deleted object.
1057    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
1058    /// All objects will be updated with their new (current) storage rebate/cost.
1059    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
1060    /// overall storage rebate and cost.
1061    pub(crate) fn collect_storage_and_rebate(
1062        &mut self,
1063        gas_charger: &mut GasCharger,
1064    ) -> Result<(), ExecutionError> {
1065        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
1066        let old_storage_rebates: Vec<_> = self
1067            .execution_results
1068            .written_objects
1069            .keys()
1070            .map(|object_id| {
1071                self.get_object_modified_at(object_id)
1072                    .map(|metadata| metadata.storage_rebate)
1073                    .unwrap_or_default()
1074            })
1075            .collect();
1076        for (object, old_storage_rebate) in self
1077            .execution_results
1078            .written_objects
1079            .values_mut()
1080            .zip_debug_eq(old_storage_rebates)
1081        {
1082            // new object size
1083            let new_object_size = object.object_size_for_gas_metering();
1084            // track changes and compute the new object `storage_rebate`
1085            let new_storage_rebate = gas_charger
1086                .track_storage_mutation(object.id(), new_object_size, old_storage_rebate)
1087                .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1088            object.storage_rebate = new_storage_rebate;
1089        }
1090
1091        self.collect_rebate(gas_charger)
1092    }
1093
1094    pub(crate) fn collect_rebate(
1095        &self,
1096        gas_charger: &mut GasCharger,
1097    ) -> Result<(), ExecutionError> {
1098        for object_id in &self.execution_results.modified_objects {
1099            if self
1100                .execution_results
1101                .written_objects
1102                .contains_key(object_id)
1103            {
1104                continue;
1105            }
1106            // get and track the deleted object `storage_rebate`
1107            let storage_rebate = self
1108                .get_object_modified_at(object_id)
1109                // Unwrap is safe because this loop iterates through all modified objects.
1110                .unwrap()
1111                .storage_rebate;
1112            gas_charger
1113                .track_storage_mutation(*object_id, 0, storage_rebate)
1114                .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation))?;
1115        }
1116        Ok(())
1117    }
1118
1119    pub fn check_execution_results_consistency<Mode: ExecutionMode>(
1120        &self,
1121    ) -> Result<(), Mode::Error> {
1122        assert_invariant!(
1123            self.execution_results
1124                .created_object_ids
1125                .iter()
1126                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
1127                    && !self.execution_results.modified_objects.contains(id)),
1128            "Created object IDs cannot also be deleted or modified"
1129        );
1130        assert_invariant!(
1131            self.execution_results.modified_objects.iter().all(|id| {
1132                self.mutable_input_refs.contains_key(id)
1133                    || self.loaded_runtime_objects.contains_key(id)
1134                    || is_system_package(*id)
1135            }),
1136            "A modified object must be either a mutable input, a loaded child object, or a system package"
1137        );
1138        Ok(())
1139    }
1140}
1141//==============================================================================
1142// Charge gas current - end
1143//==============================================================================
1144
1145impl TemporaryStore<'_> {
1146    pub fn advance_epoch_safe_mode(
1147        &mut self,
1148        params: &AdvanceEpochParams,
1149        protocol_config: &ProtocolConfig,
1150    ) {
1151        let wrapper = get_sui_system_state_wrapper(self.store)
1152            .expect("System state wrapper object must exist");
1153        let (old_object, new_object) =
1154            wrapper.advance_epoch_safe_mode(params, self.store, protocol_config);
1155        self.mutate_child_object(old_object, new_object);
1156    }
1157}
1158
1159impl RuntimeObjectResolver for TemporaryStore<'_> {
1160    fn read_child_object(
1161        &self,
1162        parent: &ObjectID,
1163        child: &ObjectID,
1164        child_version_upper_bound: SequenceNumber,
1165    ) -> SuiResult<Option<Object>> {
1166        let obj_opt = self.execution_results.written_objects.get(child);
1167        if obj_opt.is_some() {
1168            Ok(obj_opt.cloned())
1169        } else {
1170            let _scope = monitored_scope("Execution::read_child_object");
1171            self.store
1172                .read_child_object(parent, child, child_version_upper_bound)
1173        }
1174    }
1175
1176    fn get_object_received_at_version(
1177        &self,
1178        owner: &ObjectID,
1179        receiving_object_id: &ObjectID,
1180        receive_object_at_version: SequenceNumber,
1181        epoch_id: EpochId,
1182    ) -> SuiResult<Option<Object>> {
1183        // You should never be able to try and receive an object after deleting it or writing it in the same
1184        // transaction since `Receiving` doesn't have copy.
1185        debug_assert!(
1186            !self
1187                .execution_results
1188                .written_objects
1189                .contains_key(receiving_object_id)
1190        );
1191        debug_assert!(
1192            !self
1193                .execution_results
1194                .deleted_object_ids
1195                .contains(receiving_object_id)
1196        );
1197        self.store.get_object_received_at_version(
1198            owner,
1199            receiving_object_id,
1200            receive_object_at_version,
1201            epoch_id,
1202        )
1203    }
1204}
1205
1206impl ObjectFundsResolver for TemporaryStore<'_> {
1207    /// Loads the object balance at the required version and subtracts withdrawals from the same
1208    /// checkpoint that have not settled yet.
1209    /// This function is expected never to fail; an error indicates an invariant violation.
1210    fn object_available_balance(&self, owner: SuiAddress, type_: &TypeTag) -> SuiResult<u128> {
1211        let required_version = self
1212            .load_implicitly_read_system_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1213            .ok_or(SuiErrorKind::ExecutionInvariantViolation)?
1214            .version();
1215
1216        let settled = AccumulatorRootValue::load(self, Some(required_version), owner, type_)?
1217            .and_then(|value| value.as_u128())
1218            .unwrap_or(0);
1219
1220        let unsettled = self.unsettled_object_funds.get_unsettled_object_withdraw(
1221            &AccumulatorRootValue::get_field_id(owner, type_)?,
1222            required_version,
1223        );
1224        settled
1225            .checked_sub(unsettled)
1226            .ok_or_else(|| SuiErrorKind::ExecutionInvariantViolation.into())
1227    }
1228}
1229
1230/// Compute the per-`(address, type)` funds-accumulator reservation budget authorized by the
1231/// transaction, and the allowance ids declared per key. Today every funds accumulator is a
1232/// `Balance<T>`, but the `(address, TypeTag)` keying lets this generalize as more accumulator
1233/// types are added. Budget sources:
1234/// - PTB `FundsWithdrawalArg`s for any supported accumulator type (sender, sponsor, or
1235///   allowance funder as owner).
1236/// - Gas paid entirely from address balance (credits `(gas_owner, Balance<SUI>)`).
1237/// - Gas-data entries with coin-reservation digests (also credit `(gas_owner, Balance<SUI>)`).
1238fn compute_input_reservations(
1239    transaction_kind: &TransactionKind,
1240    gas_data: &GasData,
1241    transaction_signer: SuiAddress,
1242    enable_gasless: bool,
1243) -> (BTreeMap<(SuiAddress, TypeTag), u64>, AllowanceIds) {
1244    use sui_types::balance::Balance;
1245    use sui_types::gas_coin::GAS;
1246    use sui_types::transaction::{Reservation, WithdrawFrom, is_gas_paid_from_address_balance};
1247
1248    let is_gasless = enable_gasless && is_gasless_transaction(gas_data, transaction_kind);
1249    let mut reservations: BTreeMap<(SuiAddress, TypeTag), u64> = BTreeMap::new();
1250    let mut allowance_ids = AllowanceIds::new();
1251    let sui_balance_type = Balance::type_tag(GAS::type_tag());
1252
1253    for arg in transaction_kind.get_funds_withdrawals() {
1254        let ty = arg.type_arg.to_type_tag();
1255        let owner = match arg.withdraw_from {
1256            WithdrawFrom::Sender => transaction_signer,
1257            WithdrawFrom::Sponsor => gas_data.owner,
1258            // The funder will differ from the signer/sponsor, but permission
1259            // is verified at signing
1260            WithdrawFrom::SenderAllowance { funder, allowance } => {
1261                allowance_ids
1262                    .entry((funder, ty.clone()))
1263                    .or_default()
1264                    .push(allowance);
1265                funder
1266            }
1267        };
1268        let Reservation::MaxAmountU64(reservation) = arg.reservation;
1269        let entry = reservations.entry((owner, ty)).or_insert(0);
1270        *entry = entry.saturating_add(reservation);
1271    }
1272
1273    // Gasless transactions charge no gas, so gas sources grant no reservation (their budget is
1274    // validated to be 0 anyway; skipping keeps the map free of a phantom zero entry).
1275    if !is_gasless && is_gas_paid_from_address_balance(gas_data, transaction_kind) {
1276        let entry = reservations
1277            .entry((gas_data.owner, sui_balance_type.clone()))
1278            .or_insert(0);
1279        *entry = entry.saturating_add(gas_data.budget);
1280    }
1281
1282    for entry in &gas_data.payment {
1283        if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
1284            let entry = reservations
1285                .entry((gas_data.owner, sui_balance_type.clone()))
1286                .or_insert(0);
1287            *entry = entry.saturating_add(parsed.reservation_amount());
1288        }
1289    }
1290
1291    (reservations, allowance_ids)
1292}
1293
1294/// What each `Publish`/`Upgrade` command declares about the package it writes, in command order.
1295/// `None` for transaction kinds that are not PTBs.
1296fn declared_packages(
1297    transaction_kind: &TransactionKind,
1298) -> Option<Vec<(usize, BTreeSet<ObjectID>)>> {
1299    let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1300        return None;
1301    };
1302    Some(
1303        pt.commands
1304            .iter()
1305            .filter_map(|command| match command {
1306                Command::Publish(modules, dep_ids) | Command::Upgrade(modules, dep_ids, _, _) => {
1307                    Some((modules.len(), dep_ids.iter().copied().collect()))
1308                }
1309                _ => None,
1310            })
1311            .collect(),
1312    )
1313}
1314
1315/// Compares the owner and payload of an object.
1316/// This is used to detect illegal writes to non-exclusive write objects.
1317fn was_object_mutated(object: &Object, original: &Object) -> bool {
1318    let data_equal = match (&object.data, &original.data) {
1319        (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1320        // We don't have a use for package content-equality, so we remain as strict as
1321        // possible for now.
1322        (Data::Package(a), Data::Package(b)) => a == b,
1323        _ => false,
1324    };
1325
1326    let owner_equal = match (&object.owner, &original.owner) {
1327        // We don't compare initial shared versions, because re-shared objects do not have the
1328        // correct initial shared version at this point in time, and this field is not something
1329        // that can be modified by a single transaction anyway.
1330        (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1331        (
1332            Owner::ConsensusAddressOwner { owner: a, .. },
1333            Owner::ConsensusAddressOwner { owner: b, .. },
1334        ) => a == b,
1335        (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1336        (Owner::Immutable, Owner::Immutable) => true,
1337        (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1338        (
1339            Owner::Party {
1340                permissions: a,
1341                start_version: _,
1342            },
1343            Owner::Party {
1344                permissions: b,
1345                start_version: _,
1346            },
1347        ) => a == b,
1348
1349        // Keep the left hand side of the match exhaustive to catch future
1350        // changes to Owner
1351        (Owner::AddressOwner(_), _)
1352        | (Owner::Immutable, _)
1353        | (Owner::ObjectOwner(_), _)
1354        | (Owner::Shared { .. }, _)
1355        | (Owner::ConsensusAddressOwner { .. }, _)
1356        | (Owner::Party { .. }, _) => false,
1357    };
1358
1359    !data_equal || !owner_equal
1360}
1361
1362impl Storage for TemporaryStore<'_> {
1363    fn reset(&mut self) {
1364        self.drop_writes();
1365    }
1366
1367    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1368        TemporaryStore::read_object(self, id)
1369    }
1370
1371    /// Take execution results v2, and translate it back to be compatible with effects v1.
1372    fn record_execution_results(
1373        &mut self,
1374        results: ExecutionResults,
1375    ) -> Result<(), ExecutionError> {
1376        let ExecutionResults::V2(mut results) = results else {
1377            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1378        };
1379
1380        // for all non-exclusive write inputs, remove them from written objects
1381        let mut to_remove = Vec::new();
1382        for (id, original) in &self.non_exclusive_input_original_versions {
1383            // Object must be present in `written_objects` and identical
1384            if results
1385                .written_objects
1386                .get(id)
1387                .map(|obj| was_object_mutated(obj, original))
1388                .unwrap_or(true)
1389            {
1390                return Err(ExecutionError::new_with_source(
1391                    ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1392                    "Non-exclusive write input object has been modified or deleted",
1393                ));
1394            }
1395            to_remove.push(*id);
1396        }
1397
1398        for id in to_remove {
1399            results.written_objects.remove(&id);
1400            results.modified_objects.remove(&id);
1401        }
1402
1403        // It's important to merge instead of override results because it's
1404        // possible to execute PT more than once during tx execution.
1405        // Track the index range of accumulator events brought in here as PTB-emitted; the
1406        // address-balance change invariant (run inside `run_conservation_checks`) uses this
1407        // set to distinguish trusted PTB-emitted events from runtime-emitted ones.
1408        let event_start = self.execution_results.accumulator_events.len();
1409        self.execution_results.merge_results(
1410            results, /* consistent_merge */ true, /* invariant_checks */ true,
1411        )?;
1412        let event_end = self.execution_results.accumulator_events.len();
1413        self.invariants
1414            .record_ptb_event_range(event_start, event_end);
1415
1416        Ok(())
1417    }
1418
1419    fn save_loaded_runtime_objects(
1420        &mut self,
1421        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1422    ) {
1423        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1424    }
1425
1426    fn save_wrapped_object_containers(
1427        &mut self,
1428        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1429    ) {
1430        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1431    }
1432
1433    fn check_coin_deny_list(
1434        &self,
1435        receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1436    ) -> DenyListResult {
1437        let result = check_coin_deny_list_v2_during_execution(
1438            receiving_funds_type_and_owners,
1439            self.cur_epoch,
1440            self.store,
1441        );
1442        // The denylist object is only loaded if there are regulated transfers.
1443        // And also if we already have it in the input there is no need to commit it again in the effects.
1444        if result.num_non_gas_coin_owners > 0
1445            && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1446        {
1447            self.loaded_per_epoch_config_objects
1448                .write()
1449                .insert(SUI_DENY_LIST_OBJECT_ID);
1450        }
1451        result
1452    }
1453
1454    fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1455        TemporaryStore::save_generated_object_ids(self, generated_ids)
1456    }
1457}
1458
1459impl BackingPackageStore for TemporaryStore<'_> {
1460    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1461        // We first check the objects in the temporary store because in non-production code path,
1462        // it is possible to read packages that are just written in the same transaction.
1463        // This can happen for example when we run the expensive conservation checks, where we may
1464        // look into the types of each written object in the output, and some of them need the
1465        // newly written packages for type checking.
1466        // In production path though, this should never happen.
1467        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1468            Ok(Some(PackageObject::new(obj.clone())))
1469        } else {
1470            self.store.get_package_object(package_id).inspect(|obj| {
1471                // Track object but leave unchanged
1472                if let Some(v) = obj
1473                    && !self
1474                        .runtime_packages_loaded_from_db
1475                        .read()
1476                        .contains_key(package_id)
1477                {
1478                    // TODO: Can this lock ever block execution?
1479                    // TODO: Another way to avoid the cost of maintaining this map is to not
1480                    // enable it in normal runs, and if a fork is detected, rerun it with a flag
1481                    // turned on and start populating this field.
1482                    self.runtime_packages_loaded_from_db
1483                        .write()
1484                        .insert(*package_id, v.clone());
1485                }
1486            })
1487        }
1488    }
1489}