Skip to main content

sui_adapter_latest/
execution_engine.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub use checked::*;
5
6#[sui_macros::with_checked_arithmetic]
7pub(crate) mod checked {
8
9    use crate::adapter::new_move_runtime;
10    use crate::execution_mode::{self, ExecutionMode};
11    use crate::gas_charger::{PaymentKind, PaymentMethod};
12    use move_binary_format::CompiledModule;
13    use move_trace_format::format::MoveTraceBuilder;
14    use move_vm_runtime::runtime::MoveRuntime;
15    use mysten_common::{
16        assert_reachable, debug_fatal, debug_fatal_with_metric, in_test_configuration,
17    };
18    use std::collections::{BTreeMap, BTreeSet};
19    use std::{cell::RefCell, rc::Rc, sync::Arc};
20    use sui_types::accumulator_root::{
21        ACCUMULATOR_ROOT_CREATE_FUNC, ACCUMULATOR_ROOT_MODULE, UnsettledObjectFundsRead,
22    };
23    use sui_types::balance::{
24        BALANCE_CREATE_REWARDS_FUNCTION_NAME, BALANCE_DESTROY_REBATES_FUNCTION_NAME,
25        BALANCE_MODULE_NAME,
26    };
27    use sui_types::coin_reservation::ParsedDigest;
28    use sui_types::execution_params::ExecutionOrEarlyError;
29    use sui_types::gas_coin::GAS;
30    use sui_types::gas_model::gas_predicates::bump_only_enabled;
31    use sui_types::messages_checkpoint::CheckpointTimestamp;
32    use sui_types::metrics::ExecutionMetrics;
33    use sui_types::object::OBJECT_START_VERSION;
34    use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
35    use sui_types::randomness_state::{
36        RANDOMNESS_MODULE_NAME, RANDOMNESS_STATE_CREATE_FUNCTION_NAME,
37        RANDOMNESS_STATE_UPDATE_FUNCTION_NAME,
38    };
39    use sui_types::{BRIDGE_ADDRESS, SUI_BRIDGE_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
40    use tracing::{info, instrument, trace, warn};
41
42    use crate::static_programmable_transactions as SPT;
43    use crate::sui_types::gas::SuiGasStatusAPI;
44    use crate::{gas_charger::GasCharger, temporary_store::TemporaryStore};
45    use move_core_types::ident_str;
46    use move_core_types::language_storage::TypeTag;
47    use sui_move_natives::all_natives;
48    use sui_protocol_config::{
49        Chain, LimitThresholdCrossed, PerObjectCongestionControlMode, ProtocolConfig,
50        check_limit_by_meter,
51    };
52    use sui_types::authenticator_state::{
53        AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME, AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME,
54        AUTHENTICATOR_STATE_MODULE_NAME, AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME,
55    };
56    use sui_types::base_types::{ObjectID, SequenceNumber, SystemObjectVersions};
57    use sui_types::bridge::BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER;
58    use sui_types::bridge::{
59        BRIDGE_CREATE_FUNCTION_NAME, BRIDGE_INIT_COMMITTEE_FUNCTION_NAME, BRIDGE_MODULE_NAME,
60        BridgeChainId,
61    };
62    use sui_types::clock::{CLOCK_MODULE_NAME, CONSENSUS_COMMIT_PROLOGUE_FUNCTION_NAME};
63    use sui_types::committee::EpochId;
64    use sui_types::deny_list_v1::{DENY_LIST_CREATE_FUNC, DENY_LIST_MODULE};
65    use sui_types::digests::{
66        ChainIdentifier, get_mainnet_chain_identifier, get_testnet_chain_identifier,
67    };
68    use sui_types::effects::TransactionEffects;
69    use sui_types::error::{ExecutionError, ExecutionErrorTrait};
70    use sui_types::execution::{ExecutionTiming, ResultWithTimings, SharedInput};
71    use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
72    use sui_types::gas::GasCostSummary;
73    use sui_types::gas::SuiGasStatus;
74    use sui_types::id::UID;
75    use sui_types::inner_temporary_store::InnerTemporaryStore;
76    use sui_types::storage::BackingStore;
77    #[cfg(msim)]
78    use sui_types::sui_system_state::advance_epoch_result_injection::maybe_modify_result_for;
79    use sui_types::sui_system_state::{ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME, AdvanceEpochParams};
80    use sui_types::transaction::{
81        Argument, AuthenticatorStateExpire, AuthenticatorStateUpdate, CallArg, ChangeEpoch,
82        Command, EndOfEpochTransactionKind, GasData, GenesisTransaction, ObjectArg,
83        ProgrammableTransaction, Reservation, StoredExecutionTimeObservations, TransactionKind,
84        WithdrawFrom, WriteAccumulatorStorageCost, is_gasless_transaction,
85    };
86    use sui_types::transaction::{CheckedInputObjects, RandomnessStateUpdate};
87    use sui_types::{
88        SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID,
89        SUI_SYSTEM_PACKAGE_ID,
90        base_types::{SuiAddress, TransactionDigest, TxContext},
91        object::{Object, ObjectInner},
92        sui_system_state::{ADVANCE_EPOCH_FUNCTION_NAME, SUI_SYSTEM_MODULE_NAME},
93    };
94
95    /// Whether the *head* early error is `InsufficientFundsForWithdraw`.
96    fn head_error_is_insufficient_funds_for_withdraw(
97        execution_params: &ExecutionOrEarlyError,
98    ) -> bool {
99        execution_params.early_errors().is_some_and(|errors| {
100            matches!(
101                errors.head,
102                ExecutionErrorKind::InsufficientFundsForWithdraw
103            )
104        })
105    }
106
107    /// Whether `InsufficientFundsForWithdraw` appears anywhere in the early-error list.
108    fn any_error_is_insufficient_funds_for_withdraw(
109        execution_params: &ExecutionOrEarlyError,
110    ) -> bool {
111        execution_params.early_errors().is_some_and(|errors| {
112            errors
113                .iter()
114                .any(|e| matches!(e, ExecutionErrorKind::InsufficientFundsForWithdraw))
115        })
116    }
117
118    /// Whether to short-circuit an IFFW transaction. Matches the legacy short-circuit once
119    /// `early_exit_on_iffw` is on (constant at gas model v15+): any IFFW among the early errors
120    /// short-circuits, even when it is not the head error.
121    fn should_short_circuit_insufficient_funds(execution_params: &ExecutionOrEarlyError) -> bool {
122        any_error_is_insufficient_funds_for_withdraw(execution_params)
123    }
124
125    fn payment_kind(
126        gas_data: &GasData,
127        transaction_kind: &TransactionKind,
128    ) -> Result<PaymentKind, ExecutionError> {
129        Ok(
130            if gas_data.is_unmetered() || transaction_kind.is_system_tx() {
131                PaymentKind::unmetered()
132            } else if is_gasless_transaction(gas_data, transaction_kind) {
133                PaymentKind::gasless()
134            } else if gas_data.payment.is_empty() {
135                PaymentKind::smash(vec![PaymentMethod::AddressBalance(
136                    gas_data.owner,
137                    gas_data.budget,
138                )])
139                .ok_or_else(|| {
140                    ExecutionError::invariant_violation(
141                        "unable to create a payment kind with a single address balance",
142                    )
143                })?
144            } else {
145                let payment_methods = gas_data
146                    .payment
147                    .iter()
148                    .map(|entry| {
149                        if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
150                            PaymentMethod::AddressBalance(
151                                gas_data.owner,
152                                parsed.reservation_amount(),
153                            )
154                        } else {
155                            PaymentMethod::Coin(*entry)
156                        }
157                    })
158                    .collect();
159                PaymentKind::smash(payment_methods).ok_or_else(|| {
160                    ExecutionError::invariant_violation(
161                        "unable to create a payment kind from the gas payment: \
162                     duplicate gas coin or reservation overflow",
163                    )
164                })?
165            },
166        )
167    }
168
169    // Legacy (gas_model < 15) payment classification (delete at execution version cut)
170    fn legacy_payment_kind(
171        gas_data: &GasData,
172        transaction_kind: &TransactionKind,
173        protocol_config: &ProtocolConfig,
174    ) -> PaymentKind {
175        if gas_data.is_unmetered() || transaction_kind.is_system_tx() {
176            PaymentKind::unmetered()
177        } else if protocol_config.enable_gasless()
178            && is_gasless_transaction(gas_data, transaction_kind)
179        {
180            PaymentKind::gasless()
181        } else if gas_data.payment.is_empty() {
182            PaymentKind::smash(vec![PaymentMethod::AddressBalance(
183                gas_data.owner,
184                gas_data.budget,
185            )])
186            .expect("unable to create a payment kind with a single address balance")
187        } else {
188            let payment_methods = gas_data
189                .payment
190                .iter()
191                .map(|entry| {
192                    if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
193                        PaymentMethod::AddressBalance(gas_data.owner, parsed.reservation_amount())
194                    } else {
195                        PaymentMethod::Coin(*entry)
196                    }
197                })
198                .collect();
199            PaymentKind::smash(payment_methods).expect(
200                "unable to create a payment kind from payment methods. \
201                 Should not be possible wit ha non-empty vector",
202            )
203        }
204    }
205
206    /// Everything `execute_transaction_to_effects` hands back to the executor layer.
207    pub struct ExecutionOutput<Mode: ExecutionMode> {
208        pub inner_store: InnerTemporaryStore,
209        pub gas_status: SuiGasStatus,
210        pub effects: TransactionEffects,
211        pub timings: Vec<ExecutionTiming>,
212        pub execution_result: Result<Mode::ExecutionResults, Mode::Error>,
213    }
214
215    /// Gas summary, execution result, and timings produced by `execute_transaction`.
216    struct ExecutionOutcome<Mode: ExecutionMode> {
217        cost_summary: GasCostSummary,
218        execution_result: Result<Mode::ExecutionResults, Mode::Error>,
219        timings: Vec<ExecutionTiming>,
220    }
221    #[instrument(name = "tx_execute_to_effects", level = "debug", skip_all)]
222    pub fn execute_transaction_to_effects<Mode: ExecutionMode>(
223        store: &dyn BackingStore,
224        input_objects: CheckedInputObjects,
225        system_object_versions: SystemObjectVersions,
226        unsettled_object_funds: &dyn UnsettledObjectFundsRead,
227        mut gas_data: GasData,
228        gas_status: SuiGasStatus,
229        transaction_kind: TransactionKind,
230        rewritten_inputs: Option<Vec<bool>>,
231        transaction_signer: SuiAddress,
232        transaction_digest: TransactionDigest,
233        move_vm: &Arc<MoveRuntime>,
234        epoch_id: &EpochId,
235        epoch_timestamp_ms: u64,
236        protocol_config: &ProtocolConfig,
237        metrics: Arc<ExecutionMetrics>,
238        enable_expensive_checks: bool,
239        execution_params: ExecutionOrEarlyError,
240        trace_builder_opt: &mut Option<MoveTraceBuilder>,
241    ) -> ExecutionOutput<Mode> {
242        let input_objects = input_objects.into_inner();
243        let shared_object_refs = input_objects.filter_shared_objects();
244        let receiving_objects = transaction_kind.receiving_objects();
245        let mut transaction_dependencies = if protocol_config.disable_effects_tx_dependencies() {
246            BTreeSet::new()
247        } else {
248            input_objects.transaction_dependencies()
249        };
250
251        // Apply the legacy gas-payment recovery before constructing the store so the gas charger
252        // and transaction-derived reservation inputs see the same final payment list.
253        if !bump_only_enabled(protocol_config.gas_model_version()) {
254            legacy::iffw_filter_address_balance_gas_payments(
255                &mut gas_data,
256                &execution_params,
257                protocol_config,
258            );
259        }
260
261        let mut temporary_store = TemporaryStore::new(
262            store,
263            input_objects,
264            receiving_objects,
265            transaction_digest,
266            protocol_config,
267            *epoch_id,
268            system_object_versions,
269            (&transaction_kind, &gas_data, transaction_signer),
270            unsettled_object_funds,
271        );
272
273        if bump_only_enabled(protocol_config.gas_model_version()) {
274            let Finalized {
275                gas,
276                status,
277                timings,
278                execution_result,
279            }: Finalized<Mode> = match execute_transaction_to_outcome::<Mode>(
280                store,
281                &mut temporary_store,
282                gas_data,
283                gas_status,
284                transaction_kind,
285                rewritten_inputs,
286                transaction_signer,
287                transaction_digest,
288                move_vm,
289                epoch_id,
290                epoch_timestamp_ms,
291                protocol_config,
292                metrics.clone(),
293                enable_expensive_checks,
294                execution_params,
295                trace_builder_opt,
296            ) {
297                Outcome::Proceed {
298                    gas_charger,
299                    gas_cost_summary,
300                    execution_result,
301                    timings,
302                } => {
303                    let status = if let Err(error) = &execution_result {
304                        ExecutionStatus::new_failure(error.to_execution_failure())
305                    } else {
306                        ExecutionStatus::Success
307                    };
308                    let coin = gas_charger.gas_coin();
309                    Finalized {
310                        gas: GasOutcome {
311                            cost_summary: gas_cost_summary,
312                            coin,
313                            status: gas_charger.into_gas_status(),
314                        },
315                        status,
316                        timings,
317                        execution_result,
318                    }
319                }
320                Outcome::BumpOnly {
321                    gas_status,
322                    error,
323                    reason,
324                } => {
325                    report_bump_only::<Mode>(reason, &transaction_digest, &error);
326                    // Rebuild the store from its inputs, keeping only the input version bumps.
327                    temporary_store = temporary_store.into_bump_only();
328                    Finalized {
329                        gas: GasOutcome {
330                            cost_summary: GasCostSummary::default(),
331                            coin: None,
332                            status: gas_status,
333                        },
334                        status: ExecutionStatus::new_failure(error.to_execution_failure()),
335                        timings: vec![],
336                        execution_result: Err(error),
337                    }
338                }
339            };
340
341            // Shared infallible tail: trim the genesis dependency, build effects, telemetry.
342            let GasOutcome {
343                cost_summary,
344                coin,
345                status: gas_status,
346            } = gas;
347            #[skip_checked_arithmetic]
348            trace!(
349                tx_digest = ?transaction_digest,
350                computation_gas_cost = cost_summary.computation_cost,
351                storage_gas_cost = cost_summary.storage_cost,
352                storage_gas_rebate = cost_summary.storage_rebate,
353                "Finished execution of transaction with status {:?}",
354                status
355            );
356            transaction_dependencies.remove(&TransactionDigest::genesis_marker());
357            let (inner, effects) = temporary_store.into_effects(
358                shared_object_refs,
359                &transaction_digest,
360                transaction_dependencies,
361                cost_summary,
362                status,
363                coin,
364                *epoch_id,
365            );
366            // Skip VM telemetry on simulation paths (dev-inspect / dry-run) since a new runtime is
367            // spun-up each time.
368            if !Mode::TRACK_EXECUTION {
369                update_vm_telemetry_metrics(&metrics, move_vm);
370            }
371            ExecutionOutput {
372                inner_store: inner,
373                gas_status,
374                effects,
375                timings,
376                execution_result,
377            }
378        } else {
379            // TODO: remove all `legacy` code on the next execution version cut
380            legacy::execute_transaction_inner::<Mode>(
381                store,
382                temporary_store,
383                gas_data,
384                gas_status,
385                transaction_kind,
386                rewritten_inputs,
387                transaction_signer,
388                transaction_digest,
389                move_vm,
390                epoch_id,
391                epoch_timestamp_ms,
392                protocol_config,
393                metrics,
394                enable_expensive_checks,
395                execution_params,
396                trace_builder_opt,
397                shared_object_refs,
398                transaction_dependencies,
399            )
400        }
401    }
402
403    /// Post-execution consistency: SUI conservation, the expensive ownership invariants, and (on
404    /// successful execution) the published-packages invariant. `Err` means an invariant was
405    /// violated unrecoverably - no panic, no recovery; the caller bails to `BumpOnly` reporting
406    /// the error.
407    #[allow(clippy::too_many_arguments)]
408    fn check_consistency<Mode: ExecutionMode>(
409        temporary_store: &mut TemporaryStore<'_>,
410        gas_charger: &GasCharger,
411        gas_cost_summary: &GasCostSummary,
412        move_vm: &Arc<MoveRuntime>,
413        enable_expensive_checks: bool,
414        transaction_signer: SuiAddress,
415        sponsor: Option<SuiAddress>,
416        is_epoch_change: bool,
417        transaction_digest: TransactionDigest,
418        execution_succeeded: bool,
419    ) -> Result<(), (Mode::Error, BumpOnlyReason)> {
420        // FIXME: we cannot fail the transaction if this is an epoch change transaction.
421        run_conservation_checks::<Mode>(
422            temporary_store,
423            gas_charger,
424            transaction_digest,
425            move_vm,
426            enable_expensive_checks,
427            gas_cost_summary,
428        )
429        .map_err(|error| (error, BumpOnlyReason::Conservation))?;
430
431        // Ownership invariants - only under expensive checks + non-arbitrary mode; a violation is a
432        // real bug that should never fire.
433        if enable_expensive_checks
434            && !Mode::allow_arbitrary_function_calls()
435            && let Err(err) = temporary_store.check_ownership_invariants(
436                &transaction_signer,
437                &sponsor,
438                gas_charger,
439                is_epoch_change,
440            )
441        {
442            #[skip_checked_arithmetic]
443            tracing::error!(
444                tx_digest = ?transaction_digest,
445                error = %err,
446                "ownership invariants violated; falling back to the no-op exit (State 2): \
447                 dropping all writes and charging nothing",
448            );
449            return Err((
450                ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation).into(),
451                BumpOnlyReason::Ownership,
452            ));
453        }
454
455        // Written packages must match the PTB's publish/upgrade commands; only meaningful when
456        // execution succeeded (on failure the writes were dropped).
457        if execution_succeeded {
458            temporary_store
459                .check_published_packages()
460                .map_err(|error| (error.into(), BumpOnlyReason::PublishedPackages))?;
461        }
462
463        Ok(())
464    }
465
466    fn update_vm_telemetry_metrics(metrics: &ExecutionMetrics, move_vm: &MoveRuntime) {
467        metrics.vm_telemetry_metrics.try_update(|vm_metrics| {
468            let t = move_vm.get_telemetry_report();
469            vm_metrics
470                .move_vm_package_cache_count
471                .set(t.package_cache_count as i64);
472            vm_metrics
473                .move_vm_total_arena_size_bytes
474                .set(t.total_arena_size as i64);
475            vm_metrics.move_vm_module_count.set(t.module_count as i64);
476            vm_metrics
477                .move_vm_function_count
478                .set(t.function_count as i64);
479            vm_metrics.move_vm_type_count.set(t.type_count as i64);
480            vm_metrics.move_vm_interner_size.set(t.interner_size as i64);
481            vm_metrics
482                .move_vm_vtable_cache_count
483                .set(t.vtable_cache_count as i64);
484            vm_metrics
485                .move_vm_vtable_cache_hits
486                .set(t.vtable_cache_hits as i64);
487            vm_metrics
488                .move_vm_vtable_cache_misses
489                .set(t.vtable_cache_misses as i64);
490            vm_metrics
491                .move_vm_load_time_ms
492                .set(t.total_load_time as i64);
493            vm_metrics.move_vm_load_count.set(t.load_count as i64);
494            vm_metrics
495                .move_vm_validation_time_ms
496                .set(t.total_validation_time as i64);
497            vm_metrics
498                .move_vm_validation_count
499                .set(t.validation_count as i64);
500            vm_metrics.move_vm_jit_time_ms.set(t.total_jit_time as i64);
501            vm_metrics.move_vm_jit_count.set(t.jit_count as i64);
502            vm_metrics
503                .move_vm_execution_time_ms
504                .set(t.total_execution_time as i64);
505            vm_metrics
506                .move_vm_execution_count
507                .set(t.execution_count as i64);
508            vm_metrics
509                .move_vm_interpreter_time_ms
510                .set(t.total_interpreter_time as i64);
511            vm_metrics
512                .move_vm_interpreter_count
513                .set(t.interpreter_count as i64);
514            vm_metrics
515                .move_vm_max_callstack_size
516                .set(t.max_callstack_size as i64);
517            vm_metrics
518                .move_vm_max_valuestack_size
519                .set(t.max_valuestack_size as i64);
520            vm_metrics.move_vm_total_time_ms.set(t.total_time as i64);
521            vm_metrics.move_vm_total_count.set(t.total_count as i64);
522        });
523    }
524
525    pub fn execute_genesis_state_update(
526        store: &dyn BackingStore,
527        protocol_config: &ProtocolConfig,
528        metrics: Arc<ExecutionMetrics>,
529        move_vm: &Arc<MoveRuntime>,
530        tx_context: Rc<RefCell<TxContext>>,
531        pt: ProgrammableTransaction,
532    ) -> Result<InnerTemporaryStore, ExecutionError> {
533        let mut temporary_store = TemporaryStore::new_for_genesis_state_update(
534            store,
535            tx_context.borrow().digest(),
536            protocol_config,
537        );
538        let mut gas_charger =
539            GasCharger::new_unmetered(tx_context.borrow().digest(), protocol_config);
540        SPT::execute::<execution_mode::Genesis>(
541            protocol_config,
542            metrics,
543            move_vm,
544            &mut temporary_store,
545            store,
546            tx_context,
547            &mut gas_charger,
548            None,
549            pt,
550            &mut None,
551        )
552        .map_err(|(e, _)| e)?;
553        temporary_store.update_object_version_and_prev_tx();
554        Ok(temporary_store.into_inner(BTreeMap::new()))
555    }
556
557    #[allow(clippy::large_enum_variant)]
558    enum Outcome<Mode: ExecutionMode> {
559        Proceed {
560            gas_charger: GasCharger,
561            gas_cost_summary: GasCostSummary,
562            execution_result: Result<Mode::ExecutionResults, Mode::Error>,
563            timings: Vec<ExecutionTiming>,
564        },
565        BumpOnly {
566            gas_status: SuiGasStatus,
567            error: Mode::Error,
568            reason: BumpOnlyReason,
569        },
570    }
571
572    /// Which stage bailed to the `BumpOnly` exit. `InsufficientFundsForWithdraw` is
573    /// the one expected reason; every other variant is an execution bug and is reported to
574    /// `execution_bump_only_exits`, whose `reason` label is `Self::label`.
575    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
576    enum BumpOnlyReason {
577        InsufficientFundsForWithdraw,
578        GasSmash,
579        WriteReset,
580        Conservation,
581        Ownership,
582        PublishedPackages,
583    }
584
585    impl BumpOnlyReason {
586        /// Metric label. Alerts select on these, so keep the values stable.
587        fn label(self) -> &'static str {
588            match self {
589                Self::InsufficientFundsForWithdraw => "insufficient_funds_for_withdraw",
590                Self::GasSmash => "gas_smash",
591                Self::WriteReset => "write_reset",
592                Self::Conservation => "conservation",
593                Self::Ownership => "ownership",
594                Self::PublishedPackages => "published_packages",
595            }
596        }
597
598        fn is_expected(self) -> bool {
599            matches!(self, Self::InsufficientFundsForWithdraw)
600        }
601    }
602
603    struct GasOutcome {
604        cost_summary: GasCostSummary,
605        coin: Option<ObjectID>,
606        status: SuiGasStatus,
607    }
608
609    struct Finalized<Mode: ExecutionMode> {
610        gas: GasOutcome,
611        status: ExecutionStatus,
612        timings: Vec<ExecutionTiming>,
613        execution_result: Result<Mode::ExecutionResults, Mode::Error>,
614    }
615
616    /// Report an unexpected `BumpOnly` exit: a transaction whose writes were all dropped and which
617    /// was charged nothing because a stage of the pipeline failed. `debug_fatal` semantics - panics
618    /// under `crash_on_debug()`, counts + logs in production.
619    ///
620    /// Skipped for the expected IFFW short-circuit, and for the simulation paths
621    /// (`Mode::TRACK_EXECUTION`: dev-inspect / dry-run / simulate), where an arbitrary user-supplied
622    /// transaction must not be able to crash a debug node or raise an alert.
623    fn report_bump_only<Mode: ExecutionMode>(
624        reason: BumpOnlyReason,
625        transaction_digest: &TransactionDigest,
626        error: &Mode::Error,
627    ) {
628        if reason.is_expected() || Mode::TRACK_EXECUTION {
629            return;
630        }
631        debug_fatal_with_metric!(
632            |metrics: &mysten_metrics::Metrics| {
633                metrics
634                    .execution_bump_only_exits
635                    .with_label_values(&[reason.label()])
636                    .inc();
637            },
638            "BumpOnly exit: all writes dropped, no gas charged. \
639             reason={}, tx_digest={:?}, error={:?}",
640            reason.label(),
641            transaction_digest,
642            error
643        );
644    }
645
646    fn execute_transaction_to_outcome<Mode: ExecutionMode>(
647        store: &dyn BackingStore,
648        temporary_store: &mut TemporaryStore<'_>,
649        gas_data: GasData,
650        gas_status: SuiGasStatus,
651        transaction_kind: TransactionKind,
652        rewritten_inputs: Option<Vec<bool>>,
653        transaction_signer: SuiAddress,
654        transaction_digest: TransactionDigest,
655        move_vm: &Arc<MoveRuntime>,
656        epoch_id: &EpochId,
657        epoch_timestamp_ms: u64,
658        protocol_config: &ProtocolConfig,
659        metrics: Arc<ExecutionMetrics>,
660        enable_expensive_checks: bool,
661        execution_params: ExecutionOrEarlyError,
662        trace_builder_opt: &mut Option<MoveTraceBuilder>,
663    ) -> Outcome<Mode> {
664        // Short-circuit insufficient_funds. No execution, `Outcome::BumpOnly`
665        if should_short_circuit_insufficient_funds(&execution_params) {
666            assert_reachable!("IFFW short-circuit fired");
667            let iffw: Mode::Error =
668                ExecutionError::from_kind(ExecutionErrorKind::InsufficientFundsForWithdraw).into();
669            return Outcome::BumpOnly {
670                gas_status,
671                error: iffw,
672                reason: BumpOnlyReason::InsufficientFundsForWithdraw,
673            };
674        }
675
676        let sponsor = {
677            let gas_owner = gas_data.owner;
678            if gas_owner == transaction_signer {
679                None
680            } else {
681                Some(gas_owner)
682            }
683        };
684        let gas_price = gas_status.gas_price();
685        let rgp = gas_status.reference_gas_price();
686        let is_epoch_change = transaction_kind.is_end_of_epoch_tx();
687
688        let tx_ctx = TxContext::new_from_components(
689            &transaction_signer,
690            &transaction_digest,
691            epoch_id,
692            epoch_timestamp_ms,
693            rgp,
694            gas_price,
695            gas_data.budget,
696            sponsor,
697            protocol_config,
698        );
699        let tx_ctx = Rc::new(RefCell::new(tx_ctx));
700
701        let payment_kind = match payment_kind(&gas_data, &transaction_kind) {
702            Ok(payment_kind) => payment_kind,
703            Err(error) => {
704                return Outcome::BumpOnly {
705                    gas_status,
706                    error: error.into(),
707                    reason: BumpOnlyReason::GasSmash,
708                };
709            }
710        };
711        let mut gas_charger = GasCharger::new(
712            transaction_digest,
713            payment_kind,
714            gas_status,
715            temporary_store,
716            protocol_config,
717        );
718        let ExecutionOutcome {
719            cost_summary: gas_cost_summary,
720            execution_result,
721            timings,
722        } = match execute_transaction::<Mode>(
723            store,
724            temporary_store,
725            transaction_kind,
726            rewritten_inputs,
727            &mut gas_charger,
728            tx_ctx,
729            move_vm,
730            protocol_config,
731            metrics,
732            execution_params,
733            trace_builder_opt,
734        ) {
735            Err((error, reason)) => {
736                return Outcome::BumpOnly {
737                    gas_status: gas_charger.into_gas_status(),
738                    error,
739                    reason,
740                };
741            }
742            Ok(outcome) => outcome,
743        };
744
745        // Post-execution consistency (conservation + ownership + published packages): on violation
746        // bail to `BumpOnly` with the gas_status recovered from the charger
747        match check_consistency::<Mode>(
748            temporary_store,
749            &gas_charger,
750            &gas_cost_summary,
751            move_vm,
752            enable_expensive_checks,
753            transaction_signer,
754            sponsor,
755            is_epoch_change,
756            transaction_digest,
757            execution_result.is_ok(),
758        ) {
759            Ok(()) => Outcome::Proceed {
760                gas_charger,
761                gas_cost_summary,
762                execution_result,
763                timings,
764            },
765            Err((error, reason)) => Outcome::BumpOnly {
766                gas_status: gas_charger.into_gas_status(),
767                error,
768                reason,
769            },
770        }
771    }
772
773    #[instrument(name = "tx_execute", level = "debug", skip_all)]
774    fn execute_transaction<Mode: ExecutionMode>(
775        store: &dyn BackingStore,
776        temporary_store: &mut TemporaryStore<'_>,
777        transaction_kind: TransactionKind,
778        rewritten_inputs: Option<Vec<bool>>,
779        gas_charger: &mut GasCharger,
780        tx_ctx: Rc<RefCell<TxContext>>,
781        move_vm: &Arc<MoveRuntime>,
782        protocol_config: &ProtocolConfig,
783        metrics: Arc<ExecutionMetrics>,
784        execution_params: ExecutionOrEarlyError,
785        trace_builder_opt: &mut Option<MoveTraceBuilder>,
786    ) -> Result<ExecutionOutcome<Mode>, (Mode::Error, BumpOnlyReason)> {
787        debug_assert!(
788            gas_charger.no_charges(),
789            "No gas charges must be applied yet"
790        );
791
792        let mut timings: Vec<ExecutionTiming> = vec![];
793
794        let result = gas_charger
795            .charge_input_objects(temporary_store)
796            .map_err(Into::into)
797            // Early errors fail without running the VM
798            .and_then(|()| match execution_params.into_early_errors() {
799                Some(early_execution_errors) => {
800                    Err(ExecutionError::new(early_execution_errors.head, None).into())
801                }
802                None => execute_ptb::<Mode>(
803                    store,
804                    temporary_store,
805                    transaction_kind,
806                    rewritten_inputs,
807                    tx_ctx,
808                    move_vm,
809                    gas_charger,
810                    protocol_config,
811                    metrics.clone(),
812                    trace_builder_opt,
813                    &mut timings,
814                ),
815            })
816            .and_then(|v| {
817                gas_charger
818                    .meter_storage(temporary_store)
819                    .map_err(Into::into)
820                    .map(|_| v)
821            });
822
823        let checks = check_effects::<Mode>(temporary_store, gas_charger, protocol_config, metrics);
824        // Execution error wins; otherwise a failed effects check fails the tx.
825        let result = result.and_then(|v| checks.map(|()| v));
826
827        if result.is_err() {
828            gas_charger
829                .handle_error(temporary_store)
830                .map_err(|error| (error.into(), BumpOnlyReason::WriteReset))?;
831        }
832        let cost_summary = gas_charger.charge(temporary_store, &result);
833        Ok(ExecutionOutcome {
834            cost_summary,
835            execution_result: result,
836            timings,
837        })
838    }
839
840    /// Execute the PTB, then bucketize computation via `round_computation`. Timings are written to
841    /// `timings_out` regardless of Ok/Err.
842    fn execute_ptb<Mode: ExecutionMode>(
843        store: &dyn BackingStore,
844        temporary_store: &mut TemporaryStore<'_>,
845        transaction_kind: TransactionKind,
846        rewritten_inputs: Option<Vec<bool>>,
847        tx_ctx: Rc<RefCell<TxContext>>,
848        move_vm: &Arc<MoveRuntime>,
849        gas_charger: &mut GasCharger,
850        protocol_config: &ProtocolConfig,
851        metrics: Arc<ExecutionMetrics>,
852        trace_builder_opt: &mut Option<MoveTraceBuilder>,
853        timings_out: &mut Vec<ExecutionTiming>,
854    ) -> Result<Mode::ExecutionResults, Mode::Error> {
855        let result = match execution_loop::<Mode>(
856            store,
857            temporary_store,
858            transaction_kind,
859            rewritten_inputs,
860            tx_ctx,
861            move_vm,
862            gas_charger,
863            protocol_config,
864            metrics,
865            trace_builder_opt,
866        ) {
867            Ok((v, t)) => {
868                *timings_out = t;
869                Ok(v)
870            }
871            Err((e, t)) => {
872                *timings_out = t;
873                Err(e)
874            }
875        };
876        gas_charger.round_computation(result)
877    }
878
879    fn check_effects<Mode: ExecutionMode>(
880        temporary_store: &mut TemporaryStore<'_>,
881        gas_charger: &mut GasCharger,
882        protocol_config: &ProtocolConfig,
883        metrics: Arc<ExecutionMetrics>,
884    ) -> Result<(), Mode::Error> {
885        let meter = check_meter_limit::<Mode>(
886            temporary_store,
887            gas_charger,
888            protocol_config,
889            metrics.clone(),
890        );
891        let written = check_written_objects_limit::<Mode>(
892            temporary_store,
893            gas_charger,
894            protocol_config,
895            metrics,
896        );
897        let representable = temporary_store
898            .check_accumulator_amounts_representable()
899            .map_err(Into::into);
900        meter.and(written).and(representable)
901    }
902
903    #[instrument(name = "run_conservation_checks", level = "debug", skip_all)]
904    fn run_conservation_checks<Mode: ExecutionMode>(
905        temporary_store: &mut TemporaryStore<'_>,
906        gas_charger: &GasCharger,
907        tx_digest: TransactionDigest,
908        move_vm: &Arc<MoveRuntime>,
909        enable_expensive_checks: bool,
910        cost_summary: &GasCostSummary,
911    ) -> Result<(), Mode::Error> {
912        if let Err(conservation_err) = temporary_store.check_conservation_invariants::<Mode>(
913            move_vm,
914            enable_expensive_checks,
915            cost_summary,
916        ) {
917            #[skip_checked_arithmetic]
918            tracing::error!(
919                tx_digest = ?tx_digest,
920                conservation_error = %conservation_err,
921                gas_status = %gas_charger.summary(),
922                "SUI conservation check failed; falling back to the no-op exit (State 2): \
923                 dropping all writes and charging nothing",
924            );
925            return Err(conservation_err.into());
926        }
927
928        Ok(())
929    }
930
931    /// Frozen pre-v15 (`gas_model_version < 15`) execution, mirroring `origin/main`. Removed at the
932    /// next execution-version cut.
933    pub(crate) mod legacy {
934        use super::*;
935
936        // MAGIC CONSTANTS -- these are all mainnet-only hardcoded constants and should not be
937        // changed (but can be removed in future execution cuts).
938
939        /// Mainnet recovery point: the fix replays for transactions at/above this accumulator root
940        /// version and keeps the old behavior below it. A compiled constant (not a protocol flag)
941        /// because it had to take effect mid-epoch during recovery, when the network can't reconfigure.
942        pub(crate) const ADDRESS_BALANCE_SMASH_FIX_MIN_ACCUMULATOR_VERSION: SequenceNumber =
943            SequenceNumber::from_u64(692949576);
944
945        /// Mainnet settlement version at/above which an `InsufficientFundsForWithdraw` transaction
946        /// short-circuits execution entirely (zero-gas effects, mutable-input version bumps only),
947        /// superseding the address-balance gas-payment pruning hotfix. A compiled constant, not a
948        /// protocol flag, because it must take effect mid-epoch during recovery when the network cannot
949        /// reconfigure. Only consulted when an accumulator version is assigned (mainnet committed
950        /// execution); everywhere else the short-circuit is protocol gated (see
951        /// `should_short_circuit_insufficient_funds`).
952        ///
953        /// Value is the mainnet accumulator root version where the new binary was activated on the network.
954        pub(crate) const ADDRESS_BALANCE_SMASH_SHORT_CIRCUIT_MIN_ACCUMULATOR_VERSION:
955            SequenceNumber = SequenceNumber::from_u64(693531074);
956
957        /// Whether to prune the address-balance leg of gas smashing for an IFFW transaction. This is
958        /// the mainnet-only accumulator backfill that replays the pre-flag incident hotfix below the
959        /// short-circuit rollout point; once `early_exit_on_iffw` is set the short-circuit handles IFFW
960        /// upstream, so reaching here implies the flag is off (asserted below).
961        pub(crate) fn should_filter_address_balance_gas_smash(
962            execution_params: &ExecutionOrEarlyError,
963            protocol_config: &ProtocolConfig,
964        ) -> bool {
965            if !head_error_is_insufficient_funds_for_withdraw(execution_params) {
966                return false;
967            }
968            debug_assert!(
969                !protocol_config.early_exit_on_iffw(),
970                "Should not reach gas smashing filtering address balances if IFFW early exit is enabled"
971            );
972            // In test/debug builds, always apply the fix unconditionally to match the behaviour of
973            // the 1.72 mainnet release (where it was deployed as an ungated hotfix).
974            in_test_configuration()
975                || protocol_config.early_exit_on_iffw()
976                || (protocol_config.chain() == Chain::Mainnet
977                    && execution_params
978                        .accumulator_version()
979                        .is_some_and(|v| v >= ADDRESS_BALANCE_SMASH_FIX_MIN_ACCUMULATOR_VERSION))
980        }
981
982        /// Whether to short-circuit an IFFW transaction. When an accumulator version is assigned
983        /// (mainnet committed execution) it gates on the settlement-version rollout point; otherwise
984        /// (every other chain and non-committed paths, where no accumulator version is assigned) the
985        /// short-circuit applies based on `early_exit_on_iffw`.
986        pub(crate) fn should_short_circuit_insufficient_funds(
987            execution_params: &ExecutionOrEarlyError,
988            protocol_config: &ProtocolConfig,
989        ) -> bool {
990            // If no IFWWs, then does not apply
991            if !execution_params.early_errors().is_some_and(|errors| {
992                errors
993                    .iter()
994                    .any(|e| matches!(e, ExecutionErrorKind::InsufficientFundsForWithdraw))
995            }) {
996                return false;
997            }
998
999            // In test/debug builds, always short-circuit unconditionally to match the behaviour of
1000            // the 1.72 mainnet release (where it was deployed as an ungated hotfix).
1001            if in_test_configuration() {
1002                return true;
1003            }
1004
1005            // otherwise gate by accumulator version (if present) or protocol flag
1006            protocol_config.early_exit_on_iffw()
1007                || (protocol_config.chain() == Chain::Mainnet
1008                    && execution_params.accumulator_version().is_some_and(|v| {
1009                        v >= ADDRESS_BALANCE_SMASH_SHORT_CIRCUIT_MIN_ACCUMULATOR_VERSION
1010                    }))
1011        }
1012
1013        /// On the legacy IFFW recovery path, discard address-balance payments while keeping real
1014        /// gas coins. The caller runs this before constructing the temporary store so its input
1015        /// reservations are derived from the same payment list used for gas smashing.
1016        pub(crate) fn iffw_filter_address_balance_gas_payments(
1017            gas_data: &mut GasData,
1018            execution_params: &ExecutionOrEarlyError,
1019            protocol_config: &ProtocolConfig,
1020        ) {
1021            if should_short_circuit_insufficient_funds(execution_params, protocol_config) {
1022                return;
1023            }
1024            if should_filter_address_balance_gas_smash(execution_params, protocol_config)
1025                && gas_data.payment.len() > 1
1026                && ParsedDigest::try_from(gas_data.payment[0].2).is_err()
1027            {
1028                gas_data
1029                    .payment
1030                    .retain(|entry| ParsedDigest::try_from(entry.2).is_err());
1031            }
1032        }
1033
1034        /// Frozen pre-v15 (`gas_model_version < 15`) execution; mirrors `origin/main`.
1035        #[allow(clippy::too_many_arguments)]
1036        pub(super) fn execute_transaction_inner<Mode: ExecutionMode>(
1037            store: &dyn BackingStore,
1038            mut temporary_store: TemporaryStore<'_>,
1039            gas_data: GasData,
1040            gas_status: SuiGasStatus,
1041            transaction_kind: TransactionKind,
1042            rewritten_inputs: Option<Vec<bool>>,
1043            transaction_signer: SuiAddress,
1044            transaction_digest: TransactionDigest,
1045            move_vm: &Arc<MoveRuntime>,
1046            epoch_id: &EpochId,
1047            epoch_timestamp_ms: u64,
1048            protocol_config: &ProtocolConfig,
1049            metrics: Arc<ExecutionMetrics>,
1050            enable_expensive_checks: bool,
1051            execution_params: ExecutionOrEarlyError,
1052            trace_builder_opt: &mut Option<MoveTraceBuilder>,
1053            shared_object_refs: Vec<SharedInput>,
1054            mut transaction_dependencies: BTreeSet<TransactionDigest>,
1055        ) -> ExecutionOutput<Mode> {
1056            // Short-circuit on InsufficientFundsForWithdraw: the transaction is guaranteed to fail
1057            // and has nothing to execute, so skip the executor pipeline. Bump versions of mutable
1058            // inputs (so locks advance) and emit effects with a zero gas cost summary. On mainnet
1059            // committed execution this is gated on the settlement-version rollout point (below it we
1060            // fall through to the address-balance gas-payment pruning hotfix instead); everywhere else
1061            // it applies based on `early_exit_on_iffw`.
1062            if should_short_circuit_insufficient_funds(&execution_params, protocol_config) {
1063                assert_reachable!("IFFW short-circuit fired");
1064                temporary_store.ensure_active_inputs_mutated();
1065                transaction_dependencies.remove(&TransactionDigest::genesis_marker());
1066
1067                let execution_error: Mode::Error =
1068                    ExecutionError::from_kind(ExecutionErrorKind::InsufficientFundsForWithdraw)
1069                        .into();
1070                let status = ExecutionStatus::new_failure(execution_error.to_execution_failure());
1071                let gas_meter = GasCharger::new(
1072                    transaction_digest,
1073                    PaymentKind::gasless(),
1074                    gas_status,
1075                    &mut temporary_store,
1076                    protocol_config,
1077                );
1078
1079                let gas_coin = gas_meter.gas_coin();
1080                let (inner, effects) = temporary_store.into_effects(
1081                    shared_object_refs,
1082                    &transaction_digest,
1083                    transaction_dependencies,
1084                    GasCostSummary::default(),
1085                    status,
1086                    gas_coin,
1087                    *epoch_id,
1088                );
1089
1090                return ExecutionOutput {
1091                    inner_store: inner,
1092                    gas_status: gas_meter.into_gas_status(),
1093                    effects,
1094                    timings: vec![],
1095                    execution_result: Err(execution_error),
1096                };
1097            }
1098
1099            let sponsor = {
1100                let gas_owner = gas_data.owner;
1101                if gas_owner == transaction_signer {
1102                    None
1103                } else {
1104                    Some(gas_owner)
1105                }
1106            };
1107            let gas_price = gas_status.gas_price();
1108            let rgp = gas_status.reference_gas_price();
1109
1110            let mut gas_charger = GasCharger::new(
1111                transaction_digest,
1112                legacy_payment_kind(&gas_data, &transaction_kind, protocol_config),
1113                gas_status,
1114                &mut temporary_store,
1115                protocol_config,
1116            );
1117
1118            let tx_ctx = TxContext::new_from_components(
1119                &transaction_signer,
1120                &transaction_digest,
1121                epoch_id,
1122                epoch_timestamp_ms,
1123                rgp,
1124                gas_price,
1125                gas_data.budget,
1126                sponsor,
1127                protocol_config,
1128            );
1129            let tx_ctx = Rc::new(RefCell::new(tx_ctx));
1130
1131            let is_gasless = protocol_config.enable_gasless()
1132                && is_gasless_transaction(&gas_data, &transaction_kind);
1133            let is_epoch_change = transaction_kind.is_end_of_epoch_tx();
1134
1135            let ExecutionOutcome {
1136                cost_summary: gas_cost_summary,
1137                mut execution_result,
1138                timings,
1139            } = execute_transaction::<Mode>(
1140                store,
1141                &mut temporary_store,
1142                transaction_kind,
1143                rewritten_inputs,
1144                &mut gas_charger,
1145                tx_ctx,
1146                move_vm,
1147                protocol_config,
1148                metrics.clone(),
1149                execution_params,
1150                trace_builder_opt,
1151                is_gasless,
1152            );
1153
1154            // Post-execution system-invariant checks, run after gas charging: SUI conservation
1155            // (recoverable) followed by object-ownership authentication (panics on violation).
1156            if let Err(e) = run_invariant_checks::<Mode>(
1157                &mut temporary_store,
1158                &mut gas_charger,
1159                transaction_digest,
1160                move_vm,
1161                protocol_config,
1162                enable_expensive_checks,
1163                &gas_cost_summary,
1164                &transaction_signer,
1165                &sponsor,
1166                is_epoch_change,
1167                execution_result.is_ok(),
1168            ) {
1169                // FIXME: we cannot fail the transaction if this is an epoch change transaction.
1170                execution_result = Err(e);
1171            }
1172
1173            let status = if let Err(error) = &execution_result {
1174                ExecutionStatus::new_failure(error.to_execution_failure())
1175            } else {
1176                ExecutionStatus::Success
1177            };
1178
1179            #[skip_checked_arithmetic]
1180            trace!(
1181                tx_digest = ?transaction_digest,
1182                computation_gas_cost = gas_cost_summary.computation_cost,
1183                storage_gas_cost = gas_cost_summary.storage_cost,
1184                storage_gas_rebate = gas_cost_summary.storage_rebate,
1185                "Finished execution of transaction with status {:?}",
1186                status
1187            );
1188
1189            // Genesis writes a special digest to indicate that an object was created during
1190            // genesis and not written by any normal transaction - remove that from the
1191            // dependencies
1192            transaction_dependencies.remove(&TransactionDigest::genesis_marker());
1193
1194            let gas_coin = gas_charger.gas_coin();
1195            let (inner, effects) = temporary_store.into_effects(
1196                shared_object_refs,
1197                &transaction_digest,
1198                transaction_dependencies,
1199                gas_cost_summary,
1200                status,
1201                gas_coin,
1202                *epoch_id,
1203            );
1204
1205            // Skip VM telemetry on simulation paths (dev-inspect / dry-run) since a new runtime is
1206            // spun-up each time.
1207            if !Mode::TRACK_EXECUTION {
1208                update_vm_telemetry_metrics(&metrics, move_vm);
1209            }
1210
1211            ExecutionOutput {
1212                inner_store: inner,
1213                gas_status: gas_charger.into_gas_status(),
1214                effects,
1215                timings,
1216                execution_result,
1217            }
1218        }
1219
1220        #[instrument(name = "tx_execute", level = "debug", skip_all)]
1221        fn execute_transaction<Mode: ExecutionMode>(
1222            store: &dyn BackingStore,
1223            temporary_store: &mut TemporaryStore<'_>,
1224            transaction_kind: TransactionKind,
1225            rewritten_inputs: Option<Vec<bool>>,
1226            gas_charger: &mut GasCharger,
1227            tx_ctx: Rc<RefCell<TxContext>>,
1228            move_vm: &Arc<MoveRuntime>,
1229            protocol_config: &ProtocolConfig,
1230            metrics: Arc<ExecutionMetrics>,
1231            execution_params: ExecutionOrEarlyError,
1232            trace_builder_opt: &mut Option<MoveTraceBuilder>,
1233            is_gasless: bool,
1234        ) -> ExecutionOutcome<Mode> {
1235            // At this point no charges have been applied yet
1236            debug_assert!(
1237                gas_charger.no_charges(),
1238                "No gas charges must be applied yet"
1239            );
1240
1241            let withdrawal_reservations =
1242                if is_gasless && protocol_config.gasless_verify_remaining_balance() {
1243                    gasless_withdrawal_reservations(&transaction_kind, &tx_ctx.borrow())
1244                } else {
1245                    None
1246                };
1247
1248            // We must charge object read here during transaction execution, because if this fails
1249            // we must still ensure an effect is committed and all objects versions incremented
1250            let result = gas_charger.charge_input_objects_legacy(temporary_store);
1251
1252            let result: ResultWithTimings<Mode::ExecutionResults, Mode::Error> =
1253                result.map_err(|e| (e.into(), vec![])).and_then(
1254                    |()| -> ResultWithTimings<Mode::ExecutionResults, Mode::Error> {
1255                        let mut execution_result: ResultWithTimings<
1256                            Mode::ExecutionResults,
1257                            Mode::Error,
1258                        > = match execution_params.into_early_errors() {
1259                            Some(early_execution_errors) => {
1260                                Err((Mode::Error::from_kind(early_execution_errors.head), vec![]))
1261                            }
1262                            None => execution_loop::<Mode>(
1263                                store,
1264                                temporary_store,
1265                                transaction_kind,
1266                                rewritten_inputs,
1267                                tx_ctx,
1268                                move_vm,
1269                                gas_charger,
1270                                protocol_config,
1271                                metrics.clone(),
1272                                trace_builder_opt,
1273                            ),
1274                        };
1275
1276                        let meter_check = check_meter_limit::<Mode>(
1277                            temporary_store,
1278                            gas_charger,
1279                            protocol_config,
1280                            metrics.clone(),
1281                        );
1282                        if let Err(e) = meter_check {
1283                            execution_result = Err((e, vec![]));
1284                        }
1285
1286                        if execution_result.is_ok() {
1287                            let gas_check = check_written_objects_limit::<Mode>(
1288                                temporary_store,
1289                                gas_charger,
1290                                protocol_config,
1291                                metrics,
1292                            );
1293                            if let Err(e) = gas_check {
1294                                execution_result = Err((e, vec![]));
1295                            }
1296                        }
1297
1298                        execution_result
1299                    },
1300                );
1301
1302            let (mut result, timings) = match result {
1303                Ok((r, t)) => (Ok(r), t),
1304                Err((e, t)) => (Err(e), t),
1305            };
1306            if is_gasless
1307                && result.is_ok()
1308                && let Err(msg) = temporary_store
1309                    .check_gasless_execution_requirements_with_reservations(
1310                        withdrawal_reservations.as_ref(),
1311                    )
1312            {
1313                result = Err(Mode::Error::new_with_source(
1314                    ExecutionErrorKind::InsufficientGas,
1315                    msg,
1316                ));
1317            }
1318
1319            // Reject transactions whose per-key accumulator totals are not representable *before*
1320            // charging gas. For SUI this bounds each per-key gross Merge/Split total to the total supply;
1321            // for other balances it bounds them to u64. Doing so here means the rejected PTB-emitted
1322            // accumulator events are dropped during the gas reset on the error path (only the bounded gas
1323            // events remain). Bounding SUI to the supply (which is ~8.4B SUI below u64::MAX) leaves enough
1324            // headroom that the gas-smash deposit / gas-charge events emitted *after* this point cannot
1325            // push any per-key total past u64::MAX, so the fold in AccumulatorWriteV1::merge cannot
1326            // overflow even though those gas events are not re-checked here.
1327            //
1328            // Ungated: this only ever turns a would-be arithmetic failure into a deterministic abort,
1329            // which produces no committed effects and so cannot diverge from any previously-committed
1330            // result, and it applies uniformly across protocol versions.
1331            // TODO: Remove this check from future executor versions once object funds checks run
1332            // during execution.
1333            if result.is_ok()
1334                && let Err(e) = temporary_store.check_accumulator_amounts_representable()
1335            {
1336                result = Err(e.into());
1337            }
1338
1339            let cost_summary =
1340                gas_charger.legacy_charge_gas(temporary_store, protocol_config, &mut result);
1341            // For advance epoch transaction, we need to provide epoch rewards and rebates as extra
1342            // information provided to check_sui_conserved, because we mint rewards, and burn
1343            // the rebates. We also need to pass in the unmetered_storage_rebate because storage
1344            // rebate is not reflected in the storage_rebate of gas summary. This is a bit confusing.
1345            // We could probably clean up the code a bit.
1346            // Put all the storage rebate accumulated in the system transaction
1347            // to the 0x5 object so that it's not lost.
1348            temporary_store
1349                .conserve_unmetered_storage_rebate(gas_charger.unmetered_storage_rebate());
1350
1351            ExecutionOutcome {
1352                cost_summary,
1353                execution_result: result,
1354                timings,
1355            }
1356        }
1357
1358        fn gasless_withdrawal_reservations(
1359            transaction_kind: &TransactionKind,
1360            tx_ctx: &TxContext,
1361        ) -> Option<BTreeMap<(SuiAddress, TypeTag), u64>> {
1362            let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
1363                debug_fatal!("Gasless transaction must be a ProgrammableTransaction");
1364                return None;
1365            };
1366            let sender = tx_ctx.sender();
1367            let mut reservations = BTreeMap::<(SuiAddress, TypeTag), u64>::new();
1368            for input in &pt.inputs {
1369                let CallArg::FundsWithdrawal(fw) = input else {
1370                    continue;
1371                };
1372                let Some(coin_type) = fw.type_arg.get_balance_type_param() else {
1373                    debug_fatal!("expected Balance type for withdrawal");
1374                    continue;
1375                };
1376                let owner = match fw.withdraw_from {
1377                    WithdrawFrom::Sender => sender,
1378                    WithdrawFrom::Sponsor => {
1379                        debug_fatal!(
1380                            "WithdrawFrom::Sponsor is not expected in gasless transactions"
1381                        );
1382                        tx_ctx.sponsor().unwrap_or(sender)
1383                    }
1384                    WithdrawFrom::SenderAllowance { funder, .. } => funder,
1385                };
1386                let Reservation::MaxAmountU64(amount) = fw.reservation;
1387                let entry = reservations.entry((owner, coin_type)).or_insert(0);
1388                *entry = entry.saturating_add(amount);
1389            }
1390            Some(reservations)
1391        }
1392
1393        /// Run all post-execution system-invariant checks against the finalized (gas-charged) store.
1394        ///
1395        /// Two families, with deliberately different failure handling:
1396        /// - SUI conservation / balance-accumulator authorization, via [`run_conservation_checks`]. A
1397        ///   violation is recoverable: the tx is aborted (and conserves SUI) rather than panicking.
1398        /// - Object-ownership authentication (expensive-checks only, skipped under dev-inspect). This
1399        ///   is a non-recoverable assertion, so it runs *after* conservation and *outside* its
1400        ///   gas-charging recovery, and panics on violation. (Folding it into the recovery would let
1401        ///   the recovery's `drop_writes` mask a real violation into a silent abort.)
1402        ///
1403        /// Returns the conservation result so the caller can fail the transaction on a violation; an
1404        /// ownership violation panics directly.
1405        #[allow(clippy::too_many_arguments)]
1406        fn run_invariant_checks<Mode: ExecutionMode>(
1407            temporary_store: &mut TemporaryStore<'_>,
1408            gas_charger: &mut GasCharger,
1409            tx_digest: TransactionDigest,
1410            move_vm: &Arc<MoveRuntime>,
1411            protocol_config: &ProtocolConfig,
1412            enable_expensive_checks: bool,
1413            cost_summary: &GasCostSummary,
1414            sender: &SuiAddress,
1415            sponsor: &Option<SuiAddress>,
1416            is_epoch_change: bool,
1417            execution_succeeded: bool,
1418        ) -> Result<(), Mode::Error> {
1419            let conservation = run_conservation_checks::<Mode>(
1420                temporary_store,
1421                gas_charger,
1422                tx_digest,
1423                move_vm,
1424                protocol_config,
1425                enable_expensive_checks,
1426                cost_summary,
1427            );
1428            if enable_expensive_checks && !Mode::allow_arbitrary_function_calls() {
1429                temporary_store
1430                    .check_ownership_invariants(sender, sponsor, gas_charger, is_epoch_change)
1431                    .unwrap()
1432            } // else, in dev inspect mode and anything goes--don't check
1433
1434            if execution_succeeded {
1435                temporary_store.check_published_packages()?;
1436            }
1437            conservation
1438        }
1439
1440        /// Run the SUI-conservation and balance-accumulator invariant checks
1441        /// ([`TemporaryStore::check_conservation_invariants`]) against the finalized store. On a
1442        /// violation, recover by dumping all writes, charging gas in
1443        /// the aborted state, and re-checking; a surviving double failure means gas charging itself
1444        /// mints or burns SUI, which is unrecoverable, so we panic. The checks themselves are read-only;
1445        /// the recovery's gas-charging mutations are orchestrated here alongside the main-path charge.
1446        #[instrument(name = "run_conservation_checks", level = "debug", skip_all)]
1447        fn run_conservation_checks<Mode: ExecutionMode>(
1448            temporary_store: &mut TemporaryStore<'_>,
1449            gas_charger: &mut GasCharger,
1450            tx_digest: TransactionDigest,
1451            move_vm: &Arc<MoveRuntime>,
1452            protocol_config: &ProtocolConfig,
1453            enable_expensive_checks: bool,
1454            cost_summary: &GasCostSummary,
1455        ) -> Result<(), Mode::Error> {
1456            let Err(conservation_err) = temporary_store.check_conservation_invariants::<Mode>(
1457                move_vm,
1458                enable_expensive_checks,
1459                cost_summary,
1460            ) else {
1461                return Ok(());
1462            };
1463
1464            // Conservation violated. Try to avoid a panic by dumping all writes, charging for gas in
1465            // the aborted state, and re-checking; surface an aborted transaction with the invariant
1466            // violation if that works.
1467            let mut result: Result<(), Mode::Error> = Err(conservation_err.into());
1468            gas_charger.reset(temporary_store);
1469            gas_charger.legacy_charge_gas(temporary_store, protocol_config, &mut result);
1470            if let Err(recovery_err) = temporary_store.check_conservation_invariants::<Mode>(
1471                move_vm,
1472                enable_expensive_checks,
1473                cost_summary,
1474            ) {
1475                // If we still fail, it's a problem with gas charging that happens even in the
1476                // "aborted" case - no other option but panic. We would create or destroy SUI
1477                // otherwise (or admit an unauthorized accumulator Split).
1478                panic!(
1479                    "SUI conservation fail in tx block {}: {}\nGas status is {}\nTx was ",
1480                    tx_digest,
1481                    recovery_err,
1482                    gas_charger.summary()
1483                )
1484            }
1485            result
1486        }
1487    }
1488
1489    #[instrument(name = "check_meter_limit", level = "debug", skip_all)]
1490    fn check_meter_limit<Mode: ExecutionMode>(
1491        temporary_store: &mut TemporaryStore<'_>,
1492        gas_charger: &mut GasCharger,
1493        protocol_config: &ProtocolConfig,
1494        metrics: Arc<ExecutionMetrics>,
1495    ) -> Result<(), Mode::Error> {
1496        let effects_estimated_size = temporary_store.estimate_effects_size_upperbound();
1497
1498        // Check if a limit threshold was crossed.
1499        // For metered transactions, there is not soft limit.
1500        // For system transactions, we allow a soft limit with alerting, and a hard limit where we terminate
1501        match check_limit_by_meter!(
1502            !gas_charger.is_unmetered(),
1503            effects_estimated_size,
1504            protocol_config.max_serialized_tx_effects_size_bytes(),
1505            protocol_config.max_serialized_tx_effects_size_bytes_system_tx(),
1506            metrics.limits_metrics.excessive_estimated_effects_size
1507        ) {
1508            LimitThresholdCrossed::None => Ok(()),
1509            LimitThresholdCrossed::Soft(_, limit) => {
1510                warn!(
1511                    effects_estimated_size = effects_estimated_size,
1512                    soft_limit = limit,
1513                    "Estimated transaction effects size crossed soft limit",
1514                );
1515                Ok(())
1516            }
1517            LimitThresholdCrossed::Hard(_, lim) => Err(Mode::Error::new_with_source(
1518                ExecutionErrorKind::EffectsTooLarge {
1519                    current_size: effects_estimated_size as u64,
1520                    max_size: lim as u64,
1521                },
1522                "Transaction effects are too large",
1523            )),
1524        }
1525    }
1526
1527    #[instrument(name = "check_written_objects_limit", level = "debug", skip_all)]
1528    fn check_written_objects_limit<Mode: ExecutionMode>(
1529        temporary_store: &mut TemporaryStore<'_>,
1530        gas_charger: &mut GasCharger,
1531        protocol_config: &ProtocolConfig,
1532        metrics: Arc<ExecutionMetrics>,
1533    ) -> Result<(), Mode::Error> {
1534        if let (Some(normal_lim), Some(system_lim)) = (
1535            protocol_config.max_size_written_objects_as_option(),
1536            protocol_config.max_size_written_objects_system_tx_as_option(),
1537        ) {
1538            let written_objects_size = temporary_store.written_objects_size();
1539
1540            match check_limit_by_meter!(
1541                !gas_charger.is_unmetered(),
1542                written_objects_size,
1543                normal_lim,
1544                system_lim,
1545                metrics.limits_metrics.excessive_written_objects_size
1546            ) {
1547                LimitThresholdCrossed::None => (),
1548                LimitThresholdCrossed::Soft(_, limit) => {
1549                    warn!(
1550                        written_objects_size = written_objects_size,
1551                        soft_limit = limit,
1552                        "Written objects size crossed soft limit",
1553                    )
1554                }
1555                LimitThresholdCrossed::Hard(_, lim) => {
1556                    return Err(Mode::Error::new_with_source(
1557                        ExecutionErrorKind::WrittenObjectsTooLarge {
1558                            current_size: written_objects_size as u64,
1559                            max_size: lim as u64,
1560                        },
1561                        "Written objects size crossed hard limit",
1562                    ));
1563                }
1564            };
1565        }
1566
1567        Ok(())
1568    }
1569
1570    #[instrument(level = "debug", skip_all)]
1571    fn execution_loop<Mode: ExecutionMode>(
1572        store: &dyn BackingStore,
1573        temporary_store: &mut TemporaryStore<'_>,
1574        transaction_kind: TransactionKind,
1575        rewritten_inputs: Option<Vec<bool>>,
1576        tx_ctx: Rc<RefCell<TxContext>>,
1577        move_vm: &Arc<MoveRuntime>,
1578        gas_charger: &mut GasCharger,
1579        protocol_config: &ProtocolConfig,
1580        metrics: Arc<ExecutionMetrics>,
1581        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1582    ) -> ResultWithTimings<Mode::ExecutionResults, Mode::Error> {
1583        let result = match transaction_kind {
1584            TransactionKind::ChangeEpoch(change_epoch) => {
1585                let builder = ProgrammableTransactionBuilder::new();
1586                advance_epoch::<Mode>(
1587                    builder,
1588                    change_epoch,
1589                    temporary_store,
1590                    store,
1591                    tx_ctx,
1592                    move_vm,
1593                    gas_charger,
1594                    protocol_config,
1595                    metrics,
1596                    trace_builder_opt,
1597                )
1598                .map_err(|e| (e, vec![]))?;
1599                Ok((Mode::empty_results(), vec![]))
1600            }
1601            TransactionKind::Genesis(GenesisTransaction { objects }) => {
1602                if tx_ctx.borrow().epoch() != 0 {
1603                    panic!("BUG: Genesis Transactions can only be executed in epoch 0");
1604                }
1605
1606                for genesis_object in objects {
1607                    match genesis_object {
1608                        sui_types::transaction::GenesisObject::RawObject { data, owner } => {
1609                            let object = ObjectInner {
1610                                data,
1611                                owner,
1612                                previous_transaction: tx_ctx.borrow().digest(),
1613                                storage_rebate: 0,
1614                            };
1615                            temporary_store.create_object(object.into());
1616                        }
1617                    }
1618                }
1619                Ok((Mode::empty_results(), vec![]))
1620            }
1621            TransactionKind::ConsensusCommitPrologue(prologue) => {
1622                setup_consensus_commit::<Mode>(
1623                    prologue.commit_timestamp_ms,
1624                    temporary_store,
1625                    store,
1626                    tx_ctx,
1627                    move_vm,
1628                    gas_charger,
1629                    protocol_config,
1630                    metrics,
1631                    trace_builder_opt,
1632                )
1633                .expect("ConsensusCommitPrologue cannot fail");
1634                Ok((Mode::empty_results(), vec![]))
1635            }
1636            TransactionKind::ConsensusCommitPrologueV2(prologue) => {
1637                setup_consensus_commit::<Mode>(
1638                    prologue.commit_timestamp_ms,
1639                    temporary_store,
1640                    store,
1641                    tx_ctx,
1642                    move_vm,
1643                    gas_charger,
1644                    protocol_config,
1645                    metrics,
1646                    trace_builder_opt,
1647                )
1648                .expect("ConsensusCommitPrologueV2 cannot fail");
1649                Ok((Mode::empty_results(), vec![]))
1650            }
1651            TransactionKind::ConsensusCommitPrologueV3(prologue) => {
1652                setup_consensus_commit::<Mode>(
1653                    prologue.commit_timestamp_ms,
1654                    temporary_store,
1655                    store,
1656                    tx_ctx,
1657                    move_vm,
1658                    gas_charger,
1659                    protocol_config,
1660                    metrics,
1661                    trace_builder_opt,
1662                )
1663                .expect("ConsensusCommitPrologueV3 cannot fail");
1664                Ok((Mode::empty_results(), vec![]))
1665            }
1666            TransactionKind::ConsensusCommitPrologueV4(prologue) => {
1667                setup_consensus_commit::<Mode>(
1668                    prologue.commit_timestamp_ms,
1669                    temporary_store,
1670                    store,
1671                    tx_ctx,
1672                    move_vm,
1673                    gas_charger,
1674                    protocol_config,
1675                    metrics,
1676                    trace_builder_opt,
1677                )
1678                .expect("ConsensusCommitPrologue cannot fail");
1679                Ok((Mode::empty_results(), vec![]))
1680            }
1681            TransactionKind::ProgrammableTransaction(pt) => SPT::execute::<Mode>(
1682                protocol_config,
1683                metrics,
1684                move_vm,
1685                temporary_store,
1686                store,
1687                tx_ctx,
1688                gas_charger,
1689                rewritten_inputs,
1690                pt,
1691                trace_builder_opt,
1692            ),
1693            TransactionKind::ProgrammableSystemTransaction(pt) => {
1694                SPT::execute::<execution_mode::System<Mode::Error>>(
1695                    protocol_config,
1696                    metrics,
1697                    move_vm,
1698                    temporary_store,
1699                    store,
1700                    tx_ctx,
1701                    gas_charger,
1702                    None,
1703                    pt,
1704                    trace_builder_opt,
1705                )
1706                .map_err(|(e, _)| (e, vec![]))?;
1707                Ok((Mode::empty_results(), vec![]))
1708            }
1709            TransactionKind::EndOfEpochTransaction(txns) => {
1710                let mut builder = ProgrammableTransactionBuilder::new();
1711                let len = txns.len();
1712                for (i, tx) in txns.into_iter().enumerate() {
1713                    match tx {
1714                        EndOfEpochTransactionKind::ChangeEpoch(change_epoch) => {
1715                            assert_eq!(i, len - 1);
1716                            advance_epoch::<Mode>(
1717                                builder,
1718                                change_epoch,
1719                                temporary_store,
1720                                store,
1721                                tx_ctx,
1722                                move_vm,
1723                                gas_charger,
1724                                protocol_config,
1725                                metrics,
1726                                trace_builder_opt,
1727                            )
1728                            .map_err(|e| (e, vec![]))?;
1729                            return Ok((Mode::empty_results(), vec![]));
1730                        }
1731                        EndOfEpochTransactionKind::AuthenticatorStateCreate => {
1732                            assert!(protocol_config.enable_jwk_consensus_updates());
1733                            builder = setup_authenticator_state_create(builder);
1734                        }
1735                        EndOfEpochTransactionKind::AuthenticatorStateExpire(expire) => {
1736                            assert!(protocol_config.enable_jwk_consensus_updates());
1737
1738                            // TODO: it would be nice if a failure of this function didn't cause
1739                            // safe mode.
1740                            builder = setup_authenticator_state_expire(builder, expire);
1741                        }
1742                        EndOfEpochTransactionKind::RandomnessStateCreate => {
1743                            assert!(protocol_config.random_beacon());
1744                            builder = setup_randomness_state_create(builder);
1745                        }
1746                        EndOfEpochTransactionKind::DenyListStateCreate => {
1747                            assert!(protocol_config.enable_coin_deny_list());
1748                            builder = setup_coin_deny_list_state_create(builder);
1749                        }
1750                        EndOfEpochTransactionKind::BridgeStateCreate(chain_id) => {
1751                            assert!(protocol_config.bridge());
1752                            builder = setup_bridge_create(builder, chain_id)
1753                        }
1754                        EndOfEpochTransactionKind::BridgeCommitteeInit(bridge_shared_version) => {
1755                            assert!(protocol_config.bridge());
1756                            assert!(protocol_config.should_try_to_finalize_bridge_committee());
1757                            builder = setup_bridge_committee_update(builder, bridge_shared_version)
1758                        }
1759                        EndOfEpochTransactionKind::StoreExecutionTimeObservations(estimates) => {
1760                            if let PerObjectCongestionControlMode::ExecutionTimeEstimate(params) =
1761                                protocol_config.per_object_congestion_control_mode()
1762                            {
1763                                let chunk_size = params
1764                                    .observations_chunk_size
1765                                    .expect("observation chunking is enabled at all protocol versions handled by this execution layer");
1766                                builder = setup_store_execution_time_estimates(
1767                                    builder,
1768                                    estimates,
1769                                    chunk_size as usize,
1770                                );
1771                            }
1772                        }
1773                        EndOfEpochTransactionKind::AccumulatorRootCreate => {
1774                            assert!(protocol_config.create_root_accumulator_object());
1775                            builder = setup_accumulator_root_create(builder);
1776                        }
1777                        EndOfEpochTransactionKind::WriteAccumulatorStorageCost(
1778                            write_storage_cost,
1779                        ) => {
1780                            assert!(protocol_config.enable_accumulators());
1781                            builder =
1782                                setup_write_accumulator_storage_cost(builder, &write_storage_cost);
1783                        }
1784                        EndOfEpochTransactionKind::CoinRegistryCreate => {
1785                            assert!(protocol_config.enable_coin_registry());
1786                            builder = setup_coin_registry_create(builder);
1787                        }
1788                        EndOfEpochTransactionKind::DisplayRegistryCreate => {
1789                            assert!(protocol_config.enable_display_registry());
1790                            builder = setup_display_registry_create(builder);
1791                        }
1792                        EndOfEpochTransactionKind::AddressAliasStateCreate => {
1793                            assert!(protocol_config.address_aliases());
1794                            builder = setup_address_alias_state_create(builder);
1795                        }
1796                        EndOfEpochTransactionKind::ForwardingAddressRegistryCreate => {
1797                            assert!(protocol_config.create_forwarding_address_registry());
1798                            builder = setup_forwarding_address_registry_create(builder);
1799                        }
1800                    }
1801                }
1802                unreachable!(
1803                    "EndOfEpochTransactionKind::ChangeEpoch should be the last transaction in the list"
1804                )
1805            }
1806            TransactionKind::AuthenticatorStateUpdate(auth_state_update) => {
1807                setup_authenticator_state_update::<Mode>(
1808                    auth_state_update,
1809                    temporary_store,
1810                    store,
1811                    tx_ctx,
1812                    move_vm,
1813                    gas_charger,
1814                    protocol_config,
1815                    metrics,
1816                    trace_builder_opt,
1817                )
1818                .map_err(|e| (e, vec![]))?;
1819                Ok((Mode::empty_results(), vec![]))
1820            }
1821            TransactionKind::RandomnessStateUpdate(randomness_state_update) => {
1822                setup_randomness_state_update::<Mode>(
1823                    randomness_state_update,
1824                    temporary_store,
1825                    store,
1826                    tx_ctx,
1827                    move_vm,
1828                    gas_charger,
1829                    protocol_config,
1830                    metrics,
1831                    trace_builder_opt,
1832                )
1833                .map_err(|e| (e, vec![]))?;
1834                Ok((Mode::empty_results(), vec![]))
1835            }
1836        }?;
1837        temporary_store
1838            .check_execution_results_consistency::<Mode>()
1839            .map_err(|e| (e, vec![]))?;
1840        Ok(result)
1841    }
1842
1843    fn mint_epoch_rewards_in_pt(
1844        builder: &mut ProgrammableTransactionBuilder,
1845        params: &AdvanceEpochParams,
1846    ) -> (Argument, Argument) {
1847        // Create storage rewards.
1848        let storage_charge_arg = builder
1849            .input(CallArg::Pure(
1850                bcs::to_bytes(&params.storage_charge).unwrap(),
1851            ))
1852            .unwrap();
1853        let storage_rewards = builder.programmable_move_call(
1854            SUI_FRAMEWORK_PACKAGE_ID,
1855            BALANCE_MODULE_NAME.to_owned(),
1856            BALANCE_CREATE_REWARDS_FUNCTION_NAME.to_owned(),
1857            vec![GAS::type_tag()],
1858            vec![storage_charge_arg],
1859        );
1860
1861        // Create computation rewards.
1862        let computation_charge_arg = builder
1863            .input(CallArg::Pure(
1864                bcs::to_bytes(&params.computation_charge).unwrap(),
1865            ))
1866            .unwrap();
1867        let computation_rewards = builder.programmable_move_call(
1868            SUI_FRAMEWORK_PACKAGE_ID,
1869            BALANCE_MODULE_NAME.to_owned(),
1870            BALANCE_CREATE_REWARDS_FUNCTION_NAME.to_owned(),
1871            vec![GAS::type_tag()],
1872            vec![computation_charge_arg],
1873        );
1874        (storage_rewards, computation_rewards)
1875    }
1876
1877    pub fn construct_advance_epoch_pt<Mode: ExecutionMode>(
1878        mut builder: ProgrammableTransactionBuilder,
1879        params: &AdvanceEpochParams,
1880    ) -> Result<ProgrammableTransaction, Mode::Error> {
1881        // Step 1: Create storage and computation rewards.
1882        let (storage_rewards, computation_rewards) = mint_epoch_rewards_in_pt(&mut builder, params);
1883
1884        // Step 2: Advance the epoch.
1885        let mut arguments = vec![storage_rewards, computation_rewards];
1886        let call_arg_arguments = vec![
1887            CallArg::SUI_SYSTEM_MUT,
1888            CallArg::Pure(bcs::to_bytes(&params.epoch).unwrap()),
1889            CallArg::Pure(bcs::to_bytes(&params.next_protocol_version.as_u64()).unwrap()),
1890            CallArg::Pure(bcs::to_bytes(&params.storage_rebate).unwrap()),
1891            CallArg::Pure(bcs::to_bytes(&params.non_refundable_storage_fee).unwrap()),
1892            CallArg::Pure(bcs::to_bytes(&params.storage_fund_reinvest_rate).unwrap()),
1893            CallArg::Pure(bcs::to_bytes(&params.reward_slashing_rate).unwrap()),
1894            CallArg::Pure(bcs::to_bytes(&params.epoch_start_timestamp_ms).unwrap()),
1895        ]
1896        .into_iter()
1897        .map(|a| builder.input(a))
1898        .collect::<Result<_, _>>();
1899
1900        assert_invariant!(
1901            call_arg_arguments.is_ok(),
1902            "Unable to generate args for advance_epoch transaction!"
1903        );
1904
1905        arguments.append(&mut call_arg_arguments.unwrap());
1906
1907        info!("Call arguments to advance_epoch transaction: {:?}", params);
1908
1909        let storage_rebates = builder.programmable_move_call(
1910            SUI_SYSTEM_PACKAGE_ID,
1911            SUI_SYSTEM_MODULE_NAME.to_owned(),
1912            ADVANCE_EPOCH_FUNCTION_NAME.to_owned(),
1913            vec![],
1914            arguments,
1915        );
1916
1917        // Step 3: Destroy the storage rebates.
1918        builder.programmable_move_call(
1919            SUI_FRAMEWORK_PACKAGE_ID,
1920            BALANCE_MODULE_NAME.to_owned(),
1921            BALANCE_DESTROY_REBATES_FUNCTION_NAME.to_owned(),
1922            vec![GAS::type_tag()],
1923            vec![storage_rebates],
1924        );
1925        Ok(builder.finish())
1926    }
1927
1928    pub fn construct_advance_epoch_safe_mode_pt(
1929        params: &AdvanceEpochParams,
1930    ) -> Result<ProgrammableTransaction, ExecutionError> {
1931        let mut builder = ProgrammableTransactionBuilder::new();
1932        // Step 1: Create storage and computation rewards.
1933        let (storage_rewards, computation_rewards) = mint_epoch_rewards_in_pt(&mut builder, params);
1934
1935        // Step 2: Advance the epoch.
1936        let mut arguments = vec![storage_rewards, computation_rewards];
1937
1938        let mut args = vec![
1939            CallArg::SUI_SYSTEM_MUT,
1940            CallArg::Pure(bcs::to_bytes(&params.epoch).unwrap()),
1941            CallArg::Pure(bcs::to_bytes(&params.next_protocol_version.as_u64()).unwrap()),
1942            CallArg::Pure(bcs::to_bytes(&params.storage_rebate).unwrap()),
1943            CallArg::Pure(bcs::to_bytes(&params.non_refundable_storage_fee).unwrap()),
1944        ];
1945
1946        args.push(CallArg::Pure(
1947            bcs::to_bytes(&params.epoch_start_timestamp_ms).unwrap(),
1948        ));
1949
1950        let call_arg_arguments = args
1951            .into_iter()
1952            .map(|a| builder.input(a))
1953            .collect::<Result<_, _>>();
1954
1955        assert_invariant!(
1956            call_arg_arguments.is_ok(),
1957            "Unable to generate args for advance_epoch transaction!"
1958        );
1959
1960        arguments.append(&mut call_arg_arguments.unwrap());
1961
1962        info!("Call arguments to advance_epoch transaction: {:?}", params);
1963
1964        builder.programmable_move_call(
1965            SUI_SYSTEM_PACKAGE_ID,
1966            SUI_SYSTEM_MODULE_NAME.to_owned(),
1967            ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME.to_owned(),
1968            vec![],
1969            arguments,
1970        );
1971
1972        Ok(builder.finish())
1973    }
1974
1975    fn advance_epoch<Mode: ExecutionMode>(
1976        builder: ProgrammableTransactionBuilder,
1977        change_epoch: ChangeEpoch,
1978        temporary_store: &mut TemporaryStore<'_>,
1979        store: &dyn BackingStore,
1980        tx_ctx: Rc<RefCell<TxContext>>,
1981        move_vm: &Arc<MoveRuntime>,
1982        gas_charger: &mut GasCharger,
1983        protocol_config: &ProtocolConfig,
1984        metrics: Arc<ExecutionMetrics>,
1985        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1986    ) -> Result<(), Mode::Error> {
1987        let params = AdvanceEpochParams {
1988            epoch: change_epoch.epoch,
1989            next_protocol_version: change_epoch.protocol_version,
1990            storage_charge: change_epoch.storage_charge,
1991            computation_charge: change_epoch.computation_charge,
1992            storage_rebate: change_epoch.storage_rebate,
1993            non_refundable_storage_fee: change_epoch.non_refundable_storage_fee,
1994            storage_fund_reinvest_rate: protocol_config.storage_fund_reinvest_rate(),
1995            reward_slashing_rate: protocol_config.reward_slashing_rate(),
1996            epoch_start_timestamp_ms: change_epoch.epoch_start_timestamp_ms,
1997        };
1998        let advance_epoch_pt = construct_advance_epoch_pt::<Mode>(builder, &params)?;
1999        let result = SPT::execute::<execution_mode::System<Mode::Error>>(
2000            protocol_config,
2001            metrics.clone(),
2002            move_vm,
2003            temporary_store,
2004            store,
2005            tx_ctx.clone(),
2006            gas_charger,
2007            None,
2008            advance_epoch_pt,
2009            trace_builder_opt,
2010        );
2011
2012        #[cfg(msim)]
2013        let result = maybe_modify_result_for(result, change_epoch.epoch);
2014
2015        if let Err(err) = &result {
2016            tracing::error!(
2017                "Failed to execute advance epoch transaction. Switching to safe mode. Error: {:?}. Input objects: {:?}. Tx data: {:?}",
2018                err.0,
2019                temporary_store.objects(),
2020                change_epoch,
2021            );
2022            temporary_store.drop_writes();
2023            // Must reset the storage rebate since we are re-executing.
2024            gas_charger.reset_storage_cost_and_rebate();
2025
2026            temporary_store.advance_epoch_safe_mode(&params, protocol_config);
2027        }
2028
2029        let new_vm = new_move_runtime(
2030            all_natives(/* silent */ true, protocol_config),
2031            protocol_config,
2032        )
2033        .expect("Failed to create new MoveRuntime");
2034        process_system_packages(
2035            change_epoch,
2036            temporary_store,
2037            store,
2038            tx_ctx,
2039            &new_vm,
2040            gas_charger,
2041            protocol_config,
2042            metrics,
2043            trace_builder_opt,
2044        );
2045        Ok(())
2046    }
2047
2048    fn process_system_packages(
2049        change_epoch: ChangeEpoch,
2050        temporary_store: &mut TemporaryStore<'_>,
2051        store: &dyn BackingStore,
2052        tx_ctx: Rc<RefCell<TxContext>>,
2053        move_vm: &MoveRuntime,
2054        gas_charger: &mut GasCharger,
2055        protocol_config: &ProtocolConfig,
2056        metrics: Arc<ExecutionMetrics>,
2057        trace_builder_opt: &mut Option<MoveTraceBuilder>,
2058    ) {
2059        let digest = tx_ctx.borrow().digest();
2060        let binary_config = protocol_config.binary_config(None);
2061        for (version, modules, dependencies) in change_epoch.system_packages.into_iter() {
2062            let deserialized_modules: Vec<_> = modules
2063                .iter()
2064                .map(|m| CompiledModule::deserialize_with_config(m, &binary_config).unwrap())
2065                .collect();
2066
2067            if version == OBJECT_START_VERSION {
2068                let package_id = deserialized_modules.first().unwrap().address();
2069                info!("adding new system package {package_id}");
2070
2071                let publish_pt = {
2072                    let mut b = ProgrammableTransactionBuilder::new();
2073                    b.command(Command::Publish(modules, dependencies));
2074                    b.finish()
2075                };
2076
2077                SPT::execute::<execution_mode::System>(
2078                    protocol_config,
2079                    metrics.clone(),
2080                    move_vm,
2081                    temporary_store,
2082                    store,
2083                    tx_ctx.clone(),
2084                    gas_charger,
2085                    None,
2086                    publish_pt,
2087                    trace_builder_opt,
2088                )
2089                .map_err(|(e, _)| e)
2090                .expect("System Package Publish must succeed");
2091            } else {
2092                let mut new_package = Object::new_system_package(
2093                    &deserialized_modules,
2094                    version,
2095                    dependencies,
2096                    digest,
2097                );
2098
2099                info!(
2100                    "upgraded system package {:?}",
2101                    new_package.compute_object_reference()
2102                );
2103
2104                // Decrement the version before writing the package so that the store can record the
2105                // version growing by one in the effects.
2106                new_package
2107                    .data
2108                    .try_as_package_mut()
2109                    .unwrap()
2110                    .decrement_version();
2111
2112                // upgrade of a previously existing framework module
2113                temporary_store.upgrade_system_package(new_package);
2114            }
2115        }
2116    }
2117
2118    /// Perform metadata updates in preparation for the transactions in the upcoming checkpoint:
2119    ///
2120    /// - Set the timestamp for the `Clock` shared object from the timestamp in the header from
2121    ///   consensus.
2122    fn setup_consensus_commit<Mode: ExecutionMode>(
2123        consensus_commit_timestamp_ms: CheckpointTimestamp,
2124        temporary_store: &mut TemporaryStore<'_>,
2125        store: &dyn BackingStore,
2126        tx_ctx: Rc<RefCell<TxContext>>,
2127        move_vm: &Arc<MoveRuntime>,
2128        gas_charger: &mut GasCharger,
2129        protocol_config: &ProtocolConfig,
2130        metrics: Arc<ExecutionMetrics>,
2131        trace_builder_opt: &mut Option<MoveTraceBuilder>,
2132    ) -> Result<(), Mode::Error> {
2133        let pt = {
2134            let mut builder = ProgrammableTransactionBuilder::new();
2135            let res = builder.move_call(
2136                SUI_FRAMEWORK_ADDRESS.into(),
2137                CLOCK_MODULE_NAME.to_owned(),
2138                CONSENSUS_COMMIT_PROLOGUE_FUNCTION_NAME.to_owned(),
2139                vec![],
2140                vec![
2141                    CallArg::CLOCK_MUT,
2142                    CallArg::Pure(bcs::to_bytes(&consensus_commit_timestamp_ms).unwrap()),
2143                ],
2144            );
2145            assert_invariant!(
2146                res.is_ok(),
2147                "Unable to generate consensus_commit_prologue transaction!"
2148            );
2149            builder.finish()
2150        };
2151        SPT::execute::<execution_mode::System<Mode::Error>>(
2152            protocol_config,
2153            metrics,
2154            move_vm,
2155            temporary_store,
2156            store,
2157            tx_ctx,
2158            gas_charger,
2159            None,
2160            pt,
2161            trace_builder_opt,
2162        )
2163        .map_err(|(e, _)| e)?;
2164        Ok(())
2165    }
2166
2167    fn setup_authenticator_state_create(
2168        mut builder: ProgrammableTransactionBuilder,
2169    ) -> ProgrammableTransactionBuilder {
2170        builder
2171            .move_call(
2172                SUI_FRAMEWORK_ADDRESS.into(),
2173                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
2174                AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME.to_owned(),
2175                vec![],
2176                vec![],
2177            )
2178            .expect("Unable to generate authenticator_state_create transaction!");
2179        builder
2180    }
2181
2182    fn setup_randomness_state_create(
2183        mut builder: ProgrammableTransactionBuilder,
2184    ) -> ProgrammableTransactionBuilder {
2185        builder
2186            .move_call(
2187                SUI_FRAMEWORK_ADDRESS.into(),
2188                RANDOMNESS_MODULE_NAME.to_owned(),
2189                RANDOMNESS_STATE_CREATE_FUNCTION_NAME.to_owned(),
2190                vec![],
2191                vec![],
2192            )
2193            .expect("Unable to generate randomness_state_create transaction!");
2194        builder
2195    }
2196
2197    fn setup_bridge_create(
2198        mut builder: ProgrammableTransactionBuilder,
2199        chain_id: ChainIdentifier,
2200    ) -> ProgrammableTransactionBuilder {
2201        let bridge_uid = builder
2202            .input(CallArg::Pure(UID::new(SUI_BRIDGE_OBJECT_ID).to_bcs_bytes()))
2203            .expect("Unable to create Bridge object UID!");
2204
2205        let bridge_chain_id = if chain_id == get_mainnet_chain_identifier() {
2206            BridgeChainId::SuiMainnet as u8
2207        } else if chain_id == get_testnet_chain_identifier() {
2208            BridgeChainId::SuiTestnet as u8
2209        } else {
2210            // How do we distinguish devnet from other test envs?
2211            BridgeChainId::SuiCustom as u8
2212        };
2213
2214        let bridge_chain_id = builder.pure(bridge_chain_id).unwrap();
2215        builder.programmable_move_call(
2216            BRIDGE_ADDRESS.into(),
2217            BRIDGE_MODULE_NAME.to_owned(),
2218            BRIDGE_CREATE_FUNCTION_NAME.to_owned(),
2219            vec![],
2220            vec![bridge_uid, bridge_chain_id],
2221        );
2222        builder
2223    }
2224
2225    fn setup_bridge_committee_update(
2226        mut builder: ProgrammableTransactionBuilder,
2227        bridge_shared_version: SequenceNumber,
2228    ) -> ProgrammableTransactionBuilder {
2229        let bridge = builder
2230            .obj(ObjectArg::SharedObject {
2231                id: SUI_BRIDGE_OBJECT_ID,
2232                initial_shared_version: bridge_shared_version,
2233                mutability: sui_types::transaction::SharedObjectMutability::Mutable,
2234            })
2235            .expect("Unable to create Bridge object arg!");
2236        let system_state = builder
2237            .obj(ObjectArg::SUI_SYSTEM_MUT)
2238            .expect("Unable to create System State object arg!");
2239
2240        let voting_power = builder.programmable_move_call(
2241            SUI_SYSTEM_PACKAGE_ID,
2242            SUI_SYSTEM_MODULE_NAME.to_owned(),
2243            ident_str!("validator_voting_powers").to_owned(),
2244            vec![],
2245            vec![system_state],
2246        );
2247
2248        // Hardcoding min stake participation to 75.00%
2249        // TODO: We need to set a correct value or make this configurable.
2250        let min_stake_participation_percentage = builder
2251            .input(CallArg::Pure(
2252                bcs::to_bytes(&BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER).unwrap(),
2253            ))
2254            .unwrap();
2255
2256        builder.programmable_move_call(
2257            BRIDGE_ADDRESS.into(),
2258            BRIDGE_MODULE_NAME.to_owned(),
2259            BRIDGE_INIT_COMMITTEE_FUNCTION_NAME.to_owned(),
2260            vec![],
2261            vec![bridge, voting_power, min_stake_participation_percentage],
2262        );
2263        builder
2264    }
2265
2266    fn setup_authenticator_state_update<Mode: ExecutionMode>(
2267        update: AuthenticatorStateUpdate,
2268        temporary_store: &mut TemporaryStore<'_>,
2269        store: &dyn BackingStore,
2270        tx_ctx: Rc<RefCell<TxContext>>,
2271        move_vm: &Arc<MoveRuntime>,
2272        gas_charger: &mut GasCharger,
2273        protocol_config: &ProtocolConfig,
2274        metrics: Arc<ExecutionMetrics>,
2275        trace_builder_opt: &mut Option<MoveTraceBuilder>,
2276    ) -> Result<(), Mode::Error> {
2277        let pt = {
2278            let mut builder = ProgrammableTransactionBuilder::new();
2279            let res = builder.move_call(
2280                SUI_FRAMEWORK_ADDRESS.into(),
2281                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
2282                AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME.to_owned(),
2283                vec![],
2284                vec![
2285                    CallArg::Object(ObjectArg::SharedObject {
2286                        id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2287                        initial_shared_version: update.authenticator_obj_initial_shared_version,
2288                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
2289                    }),
2290                    CallArg::Pure(bcs::to_bytes(&update.new_active_jwks).unwrap()),
2291                ],
2292            );
2293            assert_invariant!(
2294                res.is_ok(),
2295                "Unable to generate authenticator_state_update transaction!"
2296            );
2297            builder.finish()
2298        };
2299        SPT::execute::<execution_mode::System<Mode::Error>>(
2300            protocol_config,
2301            metrics,
2302            move_vm,
2303            temporary_store,
2304            store,
2305            tx_ctx,
2306            gas_charger,
2307            None,
2308            pt,
2309            trace_builder_opt,
2310        )
2311        .map_err(|(e, _)| e)?;
2312        Ok(())
2313    }
2314
2315    fn setup_authenticator_state_expire(
2316        mut builder: ProgrammableTransactionBuilder,
2317        expire: AuthenticatorStateExpire,
2318    ) -> ProgrammableTransactionBuilder {
2319        builder
2320            .move_call(
2321                SUI_FRAMEWORK_ADDRESS.into(),
2322                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
2323                AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME.to_owned(),
2324                vec![],
2325                vec![
2326                    CallArg::Object(ObjectArg::SharedObject {
2327                        id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2328                        initial_shared_version: expire.authenticator_obj_initial_shared_version,
2329                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
2330                    }),
2331                    CallArg::Pure(bcs::to_bytes(&expire.min_epoch).unwrap()),
2332                ],
2333            )
2334            .expect("Unable to generate authenticator_state_expire transaction!");
2335        builder
2336    }
2337
2338    fn setup_randomness_state_update<Mode: ExecutionMode>(
2339        update: RandomnessStateUpdate,
2340        temporary_store: &mut TemporaryStore<'_>,
2341        store: &dyn BackingStore,
2342        tx_ctx: Rc<RefCell<TxContext>>,
2343        move_vm: &Arc<MoveRuntime>,
2344        gas_charger: &mut GasCharger,
2345        protocol_config: &ProtocolConfig,
2346        metrics: Arc<ExecutionMetrics>,
2347        trace_builder_opt: &mut Option<MoveTraceBuilder>,
2348    ) -> Result<(), Mode::Error> {
2349        let pt = {
2350            let mut builder = ProgrammableTransactionBuilder::new();
2351            let res = builder.move_call(
2352                SUI_FRAMEWORK_ADDRESS.into(),
2353                RANDOMNESS_MODULE_NAME.to_owned(),
2354                RANDOMNESS_STATE_UPDATE_FUNCTION_NAME.to_owned(),
2355                vec![],
2356                vec![
2357                    CallArg::Object(ObjectArg::SharedObject {
2358                        id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2359                        initial_shared_version: update.randomness_obj_initial_shared_version,
2360                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
2361                    }),
2362                    CallArg::Pure(bcs::to_bytes(&update.randomness_round).unwrap()),
2363                    CallArg::Pure(bcs::to_bytes(&update.random_bytes).unwrap()),
2364                ],
2365            );
2366            assert_invariant!(
2367                res.is_ok(),
2368                "Unable to generate randomness_state_update transaction!"
2369            );
2370            builder.finish()
2371        };
2372        SPT::execute::<execution_mode::System<Mode::Error>>(
2373            protocol_config,
2374            metrics,
2375            move_vm,
2376            temporary_store,
2377            store,
2378            tx_ctx,
2379            gas_charger,
2380            None,
2381            pt,
2382            trace_builder_opt,
2383        )
2384        .map_err(|(e, _)| e)?;
2385        Ok(())
2386    }
2387
2388    fn setup_coin_deny_list_state_create(
2389        mut builder: ProgrammableTransactionBuilder,
2390    ) -> ProgrammableTransactionBuilder {
2391        builder
2392            .move_call(
2393                SUI_FRAMEWORK_ADDRESS.into(),
2394                DENY_LIST_MODULE.to_owned(),
2395                DENY_LIST_CREATE_FUNC.to_owned(),
2396                vec![],
2397                vec![],
2398            )
2399            .expect("Unable to generate coin_deny_list_create transaction!");
2400        builder
2401    }
2402
2403    fn setup_store_execution_time_estimates(
2404        mut builder: ProgrammableTransactionBuilder,
2405        estimates: StoredExecutionTimeObservations,
2406        chunk_size: usize,
2407    ) -> ProgrammableTransactionBuilder {
2408        let system_state = builder.obj(ObjectArg::SUI_SYSTEM_MUT).unwrap();
2409
2410        let estimate_chunks = estimates.chunk_observations(chunk_size);
2411
2412        let chunk_bytes: Vec<Vec<u8>> = estimate_chunks
2413            .into_iter()
2414            .map(|chunk| bcs::to_bytes(&chunk).unwrap())
2415            .collect();
2416
2417        let chunks_arg = builder.pure(chunk_bytes).unwrap();
2418
2419        builder.programmable_move_call(
2420            SUI_SYSTEM_PACKAGE_ID,
2421            SUI_SYSTEM_MODULE_NAME.to_owned(),
2422            ident_str!("store_execution_time_estimates_v2").to_owned(),
2423            vec![],
2424            vec![system_state, chunks_arg],
2425        );
2426        builder
2427    }
2428
2429    fn setup_accumulator_root_create(
2430        mut builder: ProgrammableTransactionBuilder,
2431    ) -> ProgrammableTransactionBuilder {
2432        builder
2433            .move_call(
2434                SUI_FRAMEWORK_ADDRESS.into(),
2435                ACCUMULATOR_ROOT_MODULE.to_owned(),
2436                ACCUMULATOR_ROOT_CREATE_FUNC.to_owned(),
2437                vec![],
2438                vec![],
2439            )
2440            .expect("Unable to generate accumulator_root_create transaction!");
2441        builder
2442    }
2443
2444    fn setup_write_accumulator_storage_cost(
2445        mut builder: ProgrammableTransactionBuilder,
2446        write_storage_cost: &WriteAccumulatorStorageCost,
2447    ) -> ProgrammableTransactionBuilder {
2448        let system_state = builder.obj(ObjectArg::SUI_SYSTEM_MUT).unwrap();
2449        let storage_cost_arg = builder.pure(write_storage_cost.storage_cost).unwrap();
2450        builder.programmable_move_call(
2451            SUI_SYSTEM_PACKAGE_ID,
2452            SUI_SYSTEM_MODULE_NAME.to_owned(),
2453            ident_str!("write_accumulator_storage_cost").to_owned(),
2454            vec![],
2455            vec![system_state, storage_cost_arg],
2456        );
2457        builder
2458    }
2459
2460    fn setup_coin_registry_create(
2461        mut builder: ProgrammableTransactionBuilder,
2462    ) -> ProgrammableTransactionBuilder {
2463        builder
2464            .move_call(
2465                SUI_FRAMEWORK_ADDRESS.into(),
2466                ident_str!("coin_registry").to_owned(),
2467                ident_str!("create").to_owned(),
2468                vec![],
2469                vec![],
2470            )
2471            .expect("Unable to generate coin_registry_create transaction!");
2472        builder
2473    }
2474
2475    fn setup_display_registry_create(
2476        mut builder: ProgrammableTransactionBuilder,
2477    ) -> ProgrammableTransactionBuilder {
2478        builder
2479            .move_call(
2480                SUI_FRAMEWORK_ADDRESS.into(),
2481                ident_str!("display_registry").to_owned(),
2482                ident_str!("create").to_owned(),
2483                vec![],
2484                vec![],
2485            )
2486            .expect("Unable to generate display_registry_create transaction!");
2487        builder
2488    }
2489
2490    fn setup_address_alias_state_create(
2491        mut builder: ProgrammableTransactionBuilder,
2492    ) -> ProgrammableTransactionBuilder {
2493        builder
2494            .move_call(
2495                SUI_FRAMEWORK_ADDRESS.into(),
2496                ident_str!("address_alias").to_owned(),
2497                ident_str!("create").to_owned(),
2498                vec![],
2499                vec![],
2500            )
2501            .expect("Unable to generate address_alias_state_create transaction!");
2502        builder
2503    }
2504    fn setup_forwarding_address_registry_create(
2505        mut builder: ProgrammableTransactionBuilder,
2506    ) -> ProgrammableTransactionBuilder {
2507        builder
2508            .move_call(
2509                SUI_FRAMEWORK_ADDRESS.into(),
2510                ident_str!("forwarding_address").to_owned(),
2511                ident_str!("create").to_owned(),
2512                vec![],
2513                vec![],
2514            )
2515            .expect("Unable to generate forwarding_address_registry_create transaction!");
2516        builder
2517    }
2518}