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