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