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