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::execution_value::SuiResolver;
12    use crate::gas_charger::{PaymentKind, PaymentMethod};
13    use move_binary_format::CompiledModule;
14    use move_trace_format::format::MoveTraceBuilder;
15    use move_vm_runtime::runtime::MoveRuntime;
16    use mysten_common::{assert_reachable, debug_fatal, in_test_configuration};
17    use std::collections::{BTreeMap, BTreeSet};
18    use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
19    use sui_types::accumulator_root::{ACCUMULATOR_ROOT_CREATE_FUNC, ACCUMULATOR_ROOT_MODULE};
20    use sui_types::balance::{
21        BALANCE_CREATE_REWARDS_FUNCTION_NAME, BALANCE_DESTROY_REBATES_FUNCTION_NAME,
22        BALANCE_MODULE_NAME,
23    };
24    use sui_types::coin_reservation::ParsedDigest;
25    use sui_types::execution_params::ExecutionOrEarlyError;
26    use sui_types::gas_coin::GAS;
27    use sui_types::messages_checkpoint::CheckpointTimestamp;
28    use sui_types::metrics::ExecutionMetrics;
29    use sui_types::object::OBJECT_START_VERSION;
30    use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
31    use sui_types::randomness_state::{
32        RANDOMNESS_MODULE_NAME, RANDOMNESS_STATE_CREATE_FUNCTION_NAME,
33        RANDOMNESS_STATE_UPDATE_FUNCTION_NAME,
34    };
35    use sui_types::{BRIDGE_ADDRESS, SUI_BRIDGE_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
36    use tracing::{info, instrument, trace, warn};
37
38    use crate::static_programmable_transactions as SPT;
39    use crate::sui_types::gas::SuiGasStatusAPI;
40    use crate::{gas_charger::GasCharger, temporary_store::TemporaryStore};
41    use move_core_types::ident_str;
42    use move_core_types::language_storage::TypeTag;
43    use sui_move_natives::all_natives;
44    use sui_protocol_config::{
45        Chain, LimitThresholdCrossed, PerObjectCongestionControlMode, ProtocolConfig,
46        check_limit_by_meter,
47    };
48    use sui_types::authenticator_state::{
49        AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME, AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME,
50        AUTHENTICATOR_STATE_MODULE_NAME, AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME,
51    };
52    use sui_types::base_types::{ObjectID, SequenceNumber};
53    use sui_types::bridge::BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER;
54    use sui_types::bridge::{
55        BRIDGE_CREATE_FUNCTION_NAME, BRIDGE_INIT_COMMITTEE_FUNCTION_NAME, BRIDGE_MODULE_NAME,
56        BridgeChainId,
57    };
58    use sui_types::clock::{CLOCK_MODULE_NAME, CONSENSUS_COMMIT_PROLOGUE_FUNCTION_NAME};
59    use sui_types::committee::EpochId;
60    use sui_types::deny_list_v1::{DENY_LIST_CREATE_FUNC, DENY_LIST_MODULE};
61    use sui_types::digests::{
62        ChainIdentifier, get_mainnet_chain_identifier, get_testnet_chain_identifier,
63    };
64    use sui_types::effects::TransactionEffects;
65    use sui_types::error::{ExecutionError, ExecutionErrorTrait};
66    use sui_types::execution::{ExecutionTiming, ResultWithTimings, SharedInput};
67    use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
68    use sui_types::gas::GasCostSummary;
69    use sui_types::gas::SuiGasStatus;
70    use sui_types::id::UID;
71    use sui_types::inner_temporary_store::InnerTemporaryStore;
72    use sui_types::storage::BackingStore;
73    #[cfg(msim)]
74    use sui_types::sui_system_state::advance_epoch_result_injection::maybe_modify_result_for;
75    use sui_types::sui_system_state::{ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME, AdvanceEpochParams};
76    use sui_types::transaction::{
77        Argument, AuthenticatorStateExpire, AuthenticatorStateUpdate, CallArg, ChangeEpoch,
78        Command, EndOfEpochTransactionKind, GasData, GenesisTransaction, ObjectArg,
79        ProgrammableTransaction, Reservation, StoredExecutionTimeObservations, TransactionKind,
80        WithdrawFrom, WriteAccumulatorStorageCost, is_gasless_transaction,
81    };
82    use sui_types::transaction::{CheckedInputObjects, RandomnessStateUpdate};
83    use sui_types::{
84        SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID,
85        SUI_SYSTEM_PACKAGE_ID,
86        base_types::{SuiAddress, TransactionDigest, TxContext},
87        object::{Object, ObjectInner},
88        sui_system_state::{ADVANCE_EPOCH_FUNCTION_NAME, SUI_SYSTEM_MODULE_NAME},
89    };
90
91    /// Whether the *head* early error is `InsufficientFundsForWithdraw`. Used to gate the first
92    /// address-balance gas-payment pruning hotfix: the head error is the one surfaced as the
93    /// failure status, so keying off it (rather than any occurrence) keeps the pruning bit-for-bit
94    /// with the original single-error hotfix.
95    fn head_error_is_insufficient_funds_for_withdraw(
96        execution_params: &ExecutionOrEarlyError,
97    ) -> bool {
98        execution_params.early_errors().is_some_and(|errors| {
99            matches!(
100                errors.head,
101                ExecutionErrorKind::InsufficientFundsForWithdraw
102            )
103        })
104    }
105
106    fn payment_kind(
107        gas_data: &GasData,
108        transaction_kind: &TransactionKind,
109        protocol_config: &ProtocolConfig,
110    ) -> PaymentKind {
111        if gas_data.is_unmetered() || transaction_kind.is_system_tx() {
112            PaymentKind::unmetered()
113        } else if protocol_config.enable_gasless()
114            && is_gasless_transaction(gas_data, transaction_kind)
115        {
116            PaymentKind::gasless()
117        } else if gas_data.payment.is_empty() {
118            PaymentKind::smash(vec![PaymentMethod::AddressBalance(
119                gas_data.owner,
120                gas_data.budget,
121            )])
122            .expect("unable to create a payment kind with a single address balance")
123        } else {
124            let payment_methods = gas_data
125                .payment
126                .iter()
127                .map(|entry| {
128                    if let Ok(parsed) = ParsedDigest::try_from(entry.2) {
129                        PaymentMethod::AddressBalance(gas_data.owner, parsed.reservation_amount())
130                    } else {
131                        PaymentMethod::Coin(*entry)
132                    }
133                })
134                .collect();
135            PaymentKind::smash(payment_methods).expect(
136                "unable to create a payment kind from payment methods. \
137                 Should not be possible wit ha non-empty vector",
138            )
139        }
140    }
141
142    type ExecutionOutput<Mode> = (
143        InnerTemporaryStore,
144        SuiGasStatus,
145        TransactionEffects,
146        Vec<ExecutionTiming>,
147        Result<<Mode as ExecutionMode>::ExecutionResults, <Mode as ExecutionMode>::Error>,
148    );
149    #[instrument(name = "tx_execute_to_effects", level = "debug", skip_all)]
150    pub fn execute_transaction_to_effects<Mode: ExecutionMode>(
151        store: &dyn BackingStore,
152        input_objects: CheckedInputObjects,
153        system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
154        gas_data: GasData,
155        gas_status: SuiGasStatus,
156        transaction_kind: TransactionKind,
157        rewritten_inputs: Option<Vec<bool>>,
158        transaction_signer: SuiAddress,
159        transaction_digest: TransactionDigest,
160        move_vm: &Arc<MoveRuntime>,
161        epoch_id: &EpochId,
162        epoch_timestamp_ms: u64,
163        protocol_config: &ProtocolConfig,
164        metrics: Arc<ExecutionMetrics>,
165        enable_expensive_checks: bool,
166        execution_params: ExecutionOrEarlyError,
167        trace_builder_opt: &mut Option<MoveTraceBuilder>,
168    ) -> ExecutionOutput<Mode> {
169        let input_objects = input_objects.into_inner();
170        let mutable_inputs = if enable_expensive_checks {
171            input_objects.all_mutable_inputs().keys().copied().collect()
172        } else {
173            HashSet::new()
174        };
175        let shared_object_refs = input_objects.filter_shared_objects();
176        let receiving_objects = transaction_kind.receiving_objects();
177        let transaction_dependencies = input_objects.transaction_dependencies();
178
179        let temporary_store = TemporaryStore::new(
180            store,
181            input_objects,
182            receiving_objects,
183            transaction_digest,
184            protocol_config,
185            *epoch_id,
186            system_object_versions,
187        );
188
189        // TODO: remove all `legacy` code on the next execution version cut
190        legacy::execute_transaction_inner::<Mode>(
191            store,
192            temporary_store,
193            gas_data,
194            gas_status,
195            transaction_kind,
196            rewritten_inputs,
197            transaction_signer,
198            transaction_digest,
199            move_vm,
200            epoch_id,
201            epoch_timestamp_ms,
202            protocol_config,
203            metrics,
204            enable_expensive_checks,
205            execution_params,
206            trace_builder_opt,
207            shared_object_refs,
208            transaction_dependencies,
209            mutable_inputs,
210        )
211    }
212
213    fn update_vm_telemetry_metrics(metrics: &ExecutionMetrics, move_vm: &MoveRuntime) {
214        metrics.vm_telemetry_metrics.try_update(|vm_metrics| {
215            let t = move_vm.get_telemetry_report();
216            vm_metrics
217                .move_vm_package_cache_count
218                .set(t.package_cache_count as i64);
219            vm_metrics
220                .move_vm_total_arena_size_bytes
221                .set(t.total_arena_size as i64);
222            vm_metrics.move_vm_module_count.set(t.module_count as i64);
223            vm_metrics
224                .move_vm_function_count
225                .set(t.function_count as i64);
226            vm_metrics.move_vm_type_count.set(t.type_count as i64);
227            vm_metrics.move_vm_interner_size.set(t.interner_size as i64);
228            vm_metrics
229                .move_vm_vtable_cache_count
230                .set(t.vtable_cache_count as i64);
231            vm_metrics
232                .move_vm_vtable_cache_hits
233                .set(t.vtable_cache_hits as i64);
234            vm_metrics
235                .move_vm_vtable_cache_misses
236                .set(t.vtable_cache_misses as i64);
237            vm_metrics
238                .move_vm_load_time_ms
239                .set(t.total_load_time as i64);
240            vm_metrics.move_vm_load_count.set(t.load_count as i64);
241            vm_metrics
242                .move_vm_validation_time_ms
243                .set(t.total_validation_time as i64);
244            vm_metrics
245                .move_vm_validation_count
246                .set(t.validation_count as i64);
247            vm_metrics.move_vm_jit_time_ms.set(t.total_jit_time as i64);
248            vm_metrics.move_vm_jit_count.set(t.jit_count as i64);
249            vm_metrics
250                .move_vm_execution_time_ms
251                .set(t.total_execution_time as i64);
252            vm_metrics
253                .move_vm_execution_count
254                .set(t.execution_count as i64);
255            vm_metrics
256                .move_vm_interpreter_time_ms
257                .set(t.total_interpreter_time as i64);
258            vm_metrics
259                .move_vm_interpreter_count
260                .set(t.interpreter_count as i64);
261            vm_metrics
262                .move_vm_max_callstack_size
263                .set(t.max_callstack_size as i64);
264            vm_metrics
265                .move_vm_max_valuestack_size
266                .set(t.max_valuestack_size as i64);
267            vm_metrics.move_vm_total_time_ms.set(t.total_time as i64);
268            vm_metrics.move_vm_total_count.set(t.total_count as i64);
269        });
270    }
271
272    pub fn execute_genesis_state_update(
273        store: &dyn BackingStore,
274        protocol_config: &ProtocolConfig,
275        metrics: Arc<ExecutionMetrics>,
276        move_vm: &Arc<MoveRuntime>,
277        tx_context: Rc<RefCell<TxContext>>,
278        input_objects: CheckedInputObjects,
279        pt: ProgrammableTransaction,
280    ) -> Result<InnerTemporaryStore, ExecutionError> {
281        let input_objects = input_objects.into_inner();
282        let mut temporary_store = TemporaryStore::new(
283            store,
284            input_objects,
285            vec![],
286            tx_context.borrow().digest(),
287            protocol_config,
288            0,
289            BTreeMap::new(),
290        );
291        let mut gas_charger = GasCharger::new_unmetered(tx_context.borrow().digest());
292        SPT::execute::<execution_mode::Genesis>(
293            protocol_config,
294            metrics,
295            move_vm,
296            &mut temporary_store,
297            store.as_backing_package_store(),
298            tx_context,
299            &mut gas_charger,
300            None,
301            pt,
302            &mut None,
303        )
304        .map_err(|(e, _)| e)?;
305        temporary_store.update_object_version_and_prev_tx();
306        Ok(temporary_store.into_inner(BTreeMap::new()))
307    }
308
309    /// Frozen pre-v15 (`gas_model_version < 15`) execution, mirroring `origin/main`. Removed at the
310    /// next execution-version cut.
311    pub(crate) mod legacy {
312        use super::*;
313
314        // MAGIC CONSTANTS -- these are all mainnet-only hardcoded constants and should not be
315        // changed (but can be removed in future execution cuts).
316
317        /// Mainnet recovery point: the fix replays for transactions at/above this accumulator root
318        /// version and keeps the old behavior below it. A compiled constant (not a protocol flag)
319        /// because it had to take effect mid-epoch during recovery, when the network can't reconfigure.
320        pub(crate) const ADDRESS_BALANCE_SMASH_FIX_MIN_ACCUMULATOR_VERSION: SequenceNumber =
321            SequenceNumber::from_u64(692949576);
322
323        /// Mainnet settlement version at/above which an `InsufficientFundsForWithdraw` transaction
324        /// short-circuits execution entirely (zero-gas effects, mutable-input version bumps only),
325        /// superseding the address-balance gas-payment pruning hotfix. A compiled constant, not a
326        /// protocol flag, because it must take effect mid-epoch during recovery when the network cannot
327        /// reconfigure. Only consulted when an accumulator version is assigned (mainnet committed
328        /// execution); everywhere else the short-circuit is protocol gated (see
329        /// `should_short_circuit_insufficient_funds`).
330        ///
331        /// Value is the mainnet accumulator root version where the new binary was activated on the network.
332        pub(crate) const ADDRESS_BALANCE_SMASH_SHORT_CIRCUIT_MIN_ACCUMULATOR_VERSION:
333            SequenceNumber = SequenceNumber::from_u64(693531074);
334
335        /// Whether to prune the address-balance leg of gas smashing for an IFFW transaction. This is
336        /// the mainnet-only accumulator backfill that replays the pre-flag incident hotfix below the
337        /// short-circuit rollout point; once `early_exit_on_iffw` is set the short-circuit handles IFFW
338        /// upstream, so reaching here implies the flag is off (asserted below).
339        pub(crate) fn should_filter_address_balance_gas_smash(
340            execution_params: &ExecutionOrEarlyError,
341            protocol_config: &ProtocolConfig,
342        ) -> bool {
343            if !head_error_is_insufficient_funds_for_withdraw(execution_params) {
344                return false;
345            }
346            debug_assert!(
347                !protocol_config.early_exit_on_iffw(),
348                "Should not reach gas smashing filtering address balances if IFFW early exit is enabled"
349            );
350            // In test/debug builds, always apply the fix unconditionally to match the behaviour of
351            // the 1.72 mainnet release (where it was deployed as an ungated hotfix).
352            in_test_configuration()
353                || protocol_config.early_exit_on_iffw()
354                || (protocol_config.chain() == Chain::Mainnet
355                    && execution_params
356                        .accumulator_version()
357                        .is_some_and(|v| v >= ADDRESS_BALANCE_SMASH_FIX_MIN_ACCUMULATOR_VERSION))
358        }
359
360        /// Whether to short-circuit an IFFW transaction. When an accumulator version is assigned
361        /// (mainnet committed execution) it gates on the settlement-version rollout point; otherwise
362        /// (every other chain and non-committed paths, where no accumulator version is assigned) the
363        /// short-circuit applies based on `early_exit_on_iffw`.
364        pub(crate) fn should_short_circuit_insufficient_funds(
365            execution_params: &ExecutionOrEarlyError,
366            protocol_config: &ProtocolConfig,
367        ) -> bool {
368            // If no IFWWs, then does not apply
369            if !execution_params.early_errors().is_some_and(|errors| {
370                errors
371                    .iter()
372                    .any(|e| matches!(e, ExecutionErrorKind::InsufficientFundsForWithdraw))
373            }) {
374                return false;
375            }
376
377            // In test/debug builds, always short-circuit unconditionally to match the behaviour of
378            // the 1.72 mainnet release (where it was deployed as an ungated hotfix).
379            if in_test_configuration() {
380                return true;
381            }
382
383            // otherwise gate by accumulator version (if present) or protocol flag
384            protocol_config.early_exit_on_iffw()
385                || (protocol_config.chain() == Chain::Mainnet
386                    && execution_params.accumulator_version().is_some_and(|v| {
387                        v >= ADDRESS_BALANCE_SMASH_SHORT_CIRCUIT_MIN_ACCUMULATOR_VERSION
388                    }))
389        }
390
391        /// Frozen pre-v15 (`gas_model_version < 15`) execution; mirrors `origin/main`.
392        #[allow(clippy::too_many_arguments)]
393        pub(super) fn execute_transaction_inner<Mode: ExecutionMode>(
394            store: &dyn BackingStore,
395            mut temporary_store: TemporaryStore<'_>,
396            mut gas_data: GasData,
397            gas_status: SuiGasStatus,
398            transaction_kind: TransactionKind,
399            rewritten_inputs: Option<Vec<bool>>,
400            transaction_signer: SuiAddress,
401            transaction_digest: TransactionDigest,
402            move_vm: &Arc<MoveRuntime>,
403            epoch_id: &EpochId,
404            epoch_timestamp_ms: u64,
405            protocol_config: &ProtocolConfig,
406            metrics: Arc<ExecutionMetrics>,
407            enable_expensive_checks: bool,
408            execution_params: ExecutionOrEarlyError,
409            trace_builder_opt: &mut Option<MoveTraceBuilder>,
410            shared_object_refs: Vec<SharedInput>,
411            mut transaction_dependencies: BTreeSet<TransactionDigest>,
412            mutable_inputs: HashSet<ObjectID>,
413        ) -> ExecutionOutput<Mode> {
414            // Short-circuit on InsufficientFundsForWithdraw: the transaction is guaranteed to fail
415            // and has nothing to execute, so skip the executor pipeline. Bump versions of mutable
416            // inputs (so locks advance) and emit effects with a zero gas cost summary. On mainnet
417            // committed execution this is gated on the settlement-version rollout point (below it we
418            // fall through to the address-balance gas-payment pruning hotfix instead); everywhere else
419            // it applies based on `early_exit_on_iffw`.
420            if should_short_circuit_insufficient_funds(&execution_params, protocol_config) {
421                assert_reachable!("IFFW short-circuit fired");
422                temporary_store.ensure_active_inputs_mutated();
423                transaction_dependencies.remove(&TransactionDigest::genesis_marker());
424
425                let execution_error: Mode::Error =
426                    ExecutionError::from_kind(ExecutionErrorKind::InsufficientFundsForWithdraw)
427                        .into();
428                let status = ExecutionStatus::new_failure(execution_error.to_execution_failure());
429                let gas_meter = GasCharger::new(
430                    transaction_digest,
431                    PaymentKind::gasless(),
432                    gas_status,
433                    &mut temporary_store,
434                    protocol_config,
435                );
436
437                let gas_coin = gas_meter.gas_coin();
438                let (inner, effects) = temporary_store.into_effects(
439                    shared_object_refs,
440                    &transaction_digest,
441                    transaction_dependencies,
442                    GasCostSummary::default(),
443                    status,
444                    gas_coin,
445                    *epoch_id,
446                );
447
448                return (
449                    inner,
450                    gas_meter.into_gas_status(),
451                    effects,
452                    vec![],
453                    Err(execution_error),
454                );
455            }
456
457            let sponsor = {
458                let gas_owner = gas_data.owner;
459                if gas_owner == transaction_signer {
460                    None
461                } else {
462                    Some(gas_owner)
463                }
464            };
465            let gas_price = gas_status.gas_price();
466            let rgp = gas_status.reference_gas_price();
467
468            // On an IFFW abort, drop the address-balance gas payments (keeping real coins) so the
469            // pruned list flows into `payment_kind`/`compute_input_reservations` with no special
470            // handling. See `should_filter_address_balance_gas_smash` for when this applies.
471            if should_filter_address_balance_gas_smash(&execution_params, protocol_config)
472                && gas_data.payment.len() > 1
473                && ParsedDigest::try_from(gas_data.payment[0].2).is_err()
474            {
475                gas_data
476                    .payment
477                    .retain(|entry| ParsedDigest::try_from(entry.2).is_err());
478            }
479
480            let mut gas_charger = GasCharger::new(
481                transaction_digest,
482                payment_kind(&gas_data, &transaction_kind, protocol_config),
483                gas_status,
484                &mut temporary_store,
485                protocol_config,
486            );
487
488            let tx_ctx = TxContext::new_from_components(
489                &transaction_signer,
490                &transaction_digest,
491                epoch_id,
492                epoch_timestamp_ms,
493                rgp,
494                gas_price,
495                gas_data.budget,
496                sponsor,
497                protocol_config,
498            );
499            let tx_ctx = Rc::new(RefCell::new(tx_ctx));
500
501            let is_gasless = protocol_config.enable_gasless()
502                && is_gasless_transaction(&gas_data, &transaction_kind);
503            let is_epoch_change = transaction_kind.is_end_of_epoch_tx();
504
505            // Cache the transaction-derived invariant-check inputs (reservation budget, advance-epoch
506            // mint/burn, genesis flag) on the store, after the gas-smash filtering above. The
507            // conservation checks and the ownership-invariant check read them from there.
508            temporary_store.set_invariant_inputs(&transaction_kind, &gas_data, transaction_signer);
509
510            let (gas_cost_summary, mut execution_result, timings) = execute_transaction::<Mode>(
511                store,
512                &mut temporary_store,
513                transaction_kind,
514                rewritten_inputs,
515                &mut gas_charger,
516                tx_ctx,
517                move_vm,
518                protocol_config,
519                metrics.clone(),
520                execution_params,
521                trace_builder_opt,
522                is_gasless,
523            );
524
525            // Post-execution system-invariant checks, run after gas charging: SUI conservation
526            // (recoverable) followed by object-ownership authentication (panics on violation).
527            if let Err(e) = run_invariant_checks::<Mode>(
528                &mut temporary_store,
529                &mut gas_charger,
530                transaction_digest,
531                move_vm,
532                protocol_config,
533                enable_expensive_checks,
534                &gas_cost_summary,
535                &transaction_signer,
536                &sponsor,
537                &mutable_inputs,
538                is_epoch_change,
539            ) {
540                // FIXME: we cannot fail the transaction if this is an epoch change transaction.
541                execution_result = Err(e);
542            }
543
544            let status = if let Err(error) = &execution_result {
545                ExecutionStatus::new_failure(error.to_execution_failure())
546            } else {
547                ExecutionStatus::Success
548            };
549
550            #[skip_checked_arithmetic]
551            trace!(
552                tx_digest = ?transaction_digest,
553                computation_gas_cost = gas_cost_summary.computation_cost,
554                storage_gas_cost = gas_cost_summary.storage_cost,
555                storage_gas_rebate = gas_cost_summary.storage_rebate,
556                "Finished execution of transaction with status {:?}",
557                status
558            );
559
560            // Genesis writes a special digest to indicate that an object was created during
561            // genesis and not written by any normal transaction - remove that from the
562            // dependencies
563            transaction_dependencies.remove(&TransactionDigest::genesis_marker());
564
565            let gas_coin = gas_charger.gas_coin();
566            let (inner, effects) = temporary_store.into_effects(
567                shared_object_refs,
568                &transaction_digest,
569                transaction_dependencies,
570                gas_cost_summary,
571                status,
572                gas_coin,
573                *epoch_id,
574            );
575
576            // Skip VM telemetry on simulation paths (dev-inspect / dry-run) since a new runtime is
577            // spun-up each time.
578            if !Mode::TRACK_EXECUTION {
579                update_vm_telemetry_metrics(&metrics, move_vm);
580            }
581
582            (
583                inner,
584                gas_charger.into_gas_status(),
585                effects,
586                timings,
587                execution_result,
588            )
589        }
590
591        #[instrument(name = "tx_execute", level = "debug", skip_all)]
592        fn execute_transaction<Mode: ExecutionMode>(
593            store: &dyn BackingStore,
594            temporary_store: &mut TemporaryStore<'_>,
595            transaction_kind: TransactionKind,
596            rewritten_inputs: Option<Vec<bool>>,
597            gas_charger: &mut GasCharger,
598            tx_ctx: Rc<RefCell<TxContext>>,
599            move_vm: &Arc<MoveRuntime>,
600            protocol_config: &ProtocolConfig,
601            metrics: Arc<ExecutionMetrics>,
602            execution_params: ExecutionOrEarlyError,
603            trace_builder_opt: &mut Option<MoveTraceBuilder>,
604            is_gasless: bool,
605        ) -> (
606            GasCostSummary,
607            Result<Mode::ExecutionResults, Mode::Error>,
608            Vec<ExecutionTiming>,
609        ) {
610            // At this point no charges have been applied yet
611            debug_assert!(
612                gas_charger.no_charges(),
613                "No gas charges must be applied yet"
614            );
615
616            let withdrawal_reservations =
617                if is_gasless && protocol_config.gasless_verify_remaining_balance() {
618                    gasless_withdrawal_reservations(&transaction_kind, &tx_ctx.borrow())
619                } else {
620                    None
621                };
622
623            // We must charge object read here during transaction execution, because if this fails
624            // we must still ensure an effect is committed and all objects versions incremented
625            let result = gas_charger.charge_input_objects_legacy(temporary_store);
626
627            let result: ResultWithTimings<Mode::ExecutionResults, Mode::Error> =
628                result.map_err(|e| (e.into(), vec![])).and_then(
629                    |()| -> ResultWithTimings<Mode::ExecutionResults, Mode::Error> {
630                        let mut execution_result: ResultWithTimings<
631                            Mode::ExecutionResults,
632                            Mode::Error,
633                        > = match execution_params.into_early_errors() {
634                            Some(early_execution_errors) => {
635                                Err((Mode::Error::from_kind(early_execution_errors.head), vec![]))
636                            }
637                            None => execution_loop::<Mode>(
638                                store,
639                                temporary_store,
640                                transaction_kind,
641                                rewritten_inputs,
642                                tx_ctx,
643                                move_vm,
644                                gas_charger,
645                                protocol_config,
646                                metrics.clone(),
647                                trace_builder_opt,
648                            ),
649                        };
650
651                        let meter_check = check_meter_limit::<Mode>(
652                            temporary_store,
653                            gas_charger,
654                            protocol_config,
655                            metrics.clone(),
656                        );
657                        if let Err(e) = meter_check {
658                            execution_result = Err((e, vec![]));
659                        }
660
661                        if execution_result.is_ok() {
662                            let gas_check = check_written_objects_limit::<Mode>(
663                                temporary_store,
664                                gas_charger,
665                                protocol_config,
666                                metrics,
667                            );
668                            if let Err(e) = gas_check {
669                                execution_result = Err((e, vec![]));
670                            }
671                        }
672
673                        execution_result
674                    },
675                );
676
677            let (mut result, timings) = match result {
678                Ok((r, t)) => (Ok(r), t),
679                Err((e, t)) => (Err(e), t),
680            };
681            if is_gasless
682                && result.is_ok()
683                && let Err(msg) = temporary_store
684                    .check_gasless_execution_requirements(withdrawal_reservations.as_ref())
685            {
686                result = Err(Mode::Error::new_with_source(
687                    ExecutionErrorKind::InsufficientGas,
688                    msg,
689                ));
690            }
691
692            // Reject transactions whose per-key accumulator totals are not representable *before*
693            // charging gas. For SUI this bounds each per-key gross Merge/Split total to the total supply;
694            // for other balances it bounds them to u64. Doing so here means the rejected PTB-emitted
695            // accumulator events are dropped during the gas reset on the error path (only the bounded gas
696            // events remain). Bounding SUI to the supply (which is ~8.4B SUI below u64::MAX) leaves enough
697            // headroom that the gas-smash deposit / gas-charge events emitted *after* this point cannot
698            // push any per-key total past u64::MAX, so the fold in AccumulatorWriteV1::merge cannot
699            // overflow even though those gas events are not re-checked here.
700            //
701            // Ungated: this only ever turns a would-be arithmetic failure into a deterministic abort,
702            // which produces no committed effects and so cannot diverge from any previously-committed
703            // result, and it applies uniformly across protocol versions.
704            if result.is_ok()
705                && let Err(e) = temporary_store.check_accumulator_amounts_representable()
706            {
707                result = Err(e.into());
708            }
709
710            let cost_summary =
711                gas_charger.legacy_charge_gas(temporary_store, protocol_config, &mut result);
712            // For advance epoch transaction, we need to provide epoch rewards and rebates as extra
713            // information provided to check_sui_conserved, because we mint rewards, and burn
714            // the rebates. We also need to pass in the unmetered_storage_rebate because storage
715            // rebate is not reflected in the storage_rebate of gas summary. This is a bit confusing.
716            // We could probably clean up the code a bit.
717            // Put all the storage rebate accumulated in the system transaction
718            // to the 0x5 object so that it's not lost.
719            temporary_store
720                .conserve_unmetered_storage_rebate(gas_charger.unmetered_storage_rebate());
721
722            (cost_summary, result, timings)
723        }
724
725        /// Run all post-execution system-invariant checks against the finalized (gas-charged) store.
726        ///
727        /// Two families, with deliberately different failure handling:
728        /// - SUI conservation / balance-accumulator authorization, via [`run_conservation_checks`]. A
729        ///   violation is recoverable: the tx is aborted (and conserves SUI) rather than panicking.
730        /// - Object-ownership authentication (expensive-checks only, skipped under dev-inspect). This
731        ///   is a non-recoverable assertion, so it runs *after* conservation and *outside* its
732        ///   gas-charging recovery, and panics on violation. (Folding it into the recovery would let
733        ///   the recovery's `drop_writes` mask a real violation into a silent abort.)
734        ///
735        /// Returns the conservation result so the caller can fail the transaction on a violation; an
736        /// ownership violation panics directly.
737        #[allow(clippy::too_many_arguments)]
738        fn run_invariant_checks<Mode: ExecutionMode>(
739            temporary_store: &mut TemporaryStore<'_>,
740            gas_charger: &mut GasCharger,
741            tx_digest: TransactionDigest,
742            move_vm: &Arc<MoveRuntime>,
743            protocol_config: &ProtocolConfig,
744            enable_expensive_checks: bool,
745            cost_summary: &GasCostSummary,
746            sender: &SuiAddress,
747            sponsor: &Option<SuiAddress>,
748            mutable_inputs: &HashSet<ObjectID>,
749            is_epoch_change: bool,
750        ) -> Result<(), Mode::Error> {
751            let conservation = run_conservation_checks::<Mode>(
752                temporary_store,
753                gas_charger,
754                tx_digest,
755                move_vm,
756                protocol_config,
757                enable_expensive_checks,
758                cost_summary,
759            );
760            if enable_expensive_checks && !Mode::allow_arbitrary_function_calls() {
761                temporary_store
762                    .check_ownership_invariants(
763                        sender,
764                        sponsor,
765                        gas_charger,
766                        mutable_inputs,
767                        is_epoch_change,
768                    )
769                    .unwrap()
770            } // else, in dev inspect mode and anything goes--don't check
771            conservation
772        }
773
774        /// Run the SUI-conservation and balance-accumulator invariant checks
775        /// ([`TemporaryStore::check_conservation_invariants`]) against the finalized store. On a
776        /// violation, recover by dumping all writes, charging gas in
777        /// the aborted state, and re-checking; a surviving double failure means gas charging itself
778        /// mints or burns SUI, which is unrecoverable, so we panic. The checks themselves are read-only;
779        /// the recovery's gas-charging mutations are orchestrated here alongside the main-path charge.
780        #[instrument(name = "run_conservation_checks", level = "debug", skip_all)]
781        fn run_conservation_checks<Mode: ExecutionMode>(
782            temporary_store: &mut TemporaryStore<'_>,
783            gas_charger: &mut GasCharger,
784            tx_digest: TransactionDigest,
785            move_vm: &Arc<MoveRuntime>,
786            protocol_config: &ProtocolConfig,
787            enable_expensive_checks: bool,
788            cost_summary: &GasCostSummary,
789        ) -> Result<(), Mode::Error> {
790            let Err(conservation_err) = temporary_store.check_conservation_invariants::<Mode>(
791                move_vm,
792                enable_expensive_checks,
793                cost_summary,
794            ) else {
795                return Ok(());
796            };
797
798            // Conservation violated. Try to avoid a panic by dumping all writes, charging for gas in
799            // the aborted state, and re-checking; surface an aborted transaction with the invariant
800            // violation if that works.
801            let mut result: Result<(), Mode::Error> = Err(conservation_err.into());
802            gas_charger.reset(temporary_store);
803            gas_charger.legacy_charge_gas(temporary_store, protocol_config, &mut result);
804            if let Err(recovery_err) = temporary_store.check_conservation_invariants::<Mode>(
805                move_vm,
806                enable_expensive_checks,
807                cost_summary,
808            ) {
809                // If we still fail, it's a problem with gas charging that happens even in the
810                // "aborted" case — no other option but panic. We would create or destroy SUI
811                // otherwise (or admit an unauthorized accumulator Split).
812                panic!(
813                    "SUI conservation fail in tx block {}: {}\nGas status is {}\nTx was ",
814                    tx_digest,
815                    recovery_err,
816                    gas_charger.summary()
817                )
818            }
819            result
820        }
821    }
822
823    #[instrument(name = "check_meter_limit", level = "debug", skip_all)]
824    fn check_meter_limit<Mode: ExecutionMode>(
825        temporary_store: &mut TemporaryStore<'_>,
826        gas_charger: &mut GasCharger,
827        protocol_config: &ProtocolConfig,
828        metrics: Arc<ExecutionMetrics>,
829    ) -> Result<(), Mode::Error> {
830        let effects_estimated_size = temporary_store.estimate_effects_size_upperbound();
831
832        // Check if a limit threshold was crossed.
833        // For metered transactions, there is not soft limit.
834        // For system transactions, we allow a soft limit with alerting, and a hard limit where we terminate
835        match check_limit_by_meter!(
836            !gas_charger.is_unmetered(),
837            effects_estimated_size,
838            protocol_config.max_serialized_tx_effects_size_bytes(),
839            protocol_config.max_serialized_tx_effects_size_bytes_system_tx(),
840            metrics.limits_metrics.excessive_estimated_effects_size
841        ) {
842            LimitThresholdCrossed::None => Ok(()),
843            LimitThresholdCrossed::Soft(_, limit) => {
844                warn!(
845                    effects_estimated_size = effects_estimated_size,
846                    soft_limit = limit,
847                    "Estimated transaction effects size crossed soft limit",
848                );
849                Ok(())
850            }
851            LimitThresholdCrossed::Hard(_, lim) => Err(Mode::Error::new_with_source(
852                ExecutionErrorKind::EffectsTooLarge {
853                    current_size: effects_estimated_size as u64,
854                    max_size: lim as u64,
855                },
856                "Transaction effects are too large",
857            )),
858        }
859    }
860
861    #[instrument(name = "check_written_objects_limit", level = "debug", skip_all)]
862    fn check_written_objects_limit<Mode: ExecutionMode>(
863        temporary_store: &mut TemporaryStore<'_>,
864        gas_charger: &mut GasCharger,
865        protocol_config: &ProtocolConfig,
866        metrics: Arc<ExecutionMetrics>,
867    ) -> Result<(), Mode::Error> {
868        if let (Some(normal_lim), Some(system_lim)) = (
869            protocol_config.max_size_written_objects_as_option(),
870            protocol_config.max_size_written_objects_system_tx_as_option(),
871        ) {
872            let written_objects_size = temporary_store.written_objects_size();
873
874            match check_limit_by_meter!(
875                !gas_charger.is_unmetered(),
876                written_objects_size,
877                normal_lim,
878                system_lim,
879                metrics.limits_metrics.excessive_written_objects_size
880            ) {
881                LimitThresholdCrossed::None => (),
882                LimitThresholdCrossed::Soft(_, limit) => {
883                    warn!(
884                        written_objects_size = written_objects_size,
885                        soft_limit = limit,
886                        "Written objects size crossed soft limit",
887                    )
888                }
889                LimitThresholdCrossed::Hard(_, lim) => {
890                    return Err(Mode::Error::new_with_source(
891                        ExecutionErrorKind::WrittenObjectsTooLarge {
892                            current_size: written_objects_size as u64,
893                            max_size: lim as u64,
894                        },
895                        "Written objects size crossed hard limit",
896                    ));
897                }
898            };
899        }
900
901        Ok(())
902    }
903
904    fn gasless_withdrawal_reservations(
905        transaction_kind: &TransactionKind,
906        tx_ctx: &TxContext,
907    ) -> Option<BTreeMap<(SuiAddress, TypeTag), u64>> {
908        let TransactionKind::ProgrammableTransaction(pt) = transaction_kind else {
909            debug_fatal!("Gasless transaction must be a ProgrammableTransaction");
910            return None;
911        };
912        let sender = tx_ctx.sender();
913        let mut reservations = BTreeMap::<(SuiAddress, TypeTag), u64>::new();
914        for input in &pt.inputs {
915            let CallArg::FundsWithdrawal(fw) = input else {
916                continue;
917            };
918            let Some(coin_type) = fw.type_arg.get_balance_type_param() else {
919                debug_fatal!("expected Balance type for withdrawal");
920                continue;
921            };
922            let owner = match fw.withdraw_from {
923                WithdrawFrom::Sender => sender,
924                WithdrawFrom::Sponsor => {
925                    debug_fatal!("WithdrawFrom::Sponsor is not expected in gasless transactions");
926                    tx_ctx.sponsor().unwrap_or(sender)
927                }
928            };
929            let Reservation::MaxAmountU64(amount) = fw.reservation;
930            let entry = reservations.entry((owner, coin_type)).or_insert(0);
931            *entry = entry.saturating_add(amount);
932        }
933        Some(reservations)
934    }
935
936    #[instrument(level = "debug", skip_all)]
937    fn execution_loop<Mode: ExecutionMode>(
938        store: &dyn BackingStore,
939        temporary_store: &mut TemporaryStore<'_>,
940        transaction_kind: TransactionKind,
941        rewritten_inputs: Option<Vec<bool>>,
942        tx_ctx: Rc<RefCell<TxContext>>,
943        move_vm: &Arc<MoveRuntime>,
944        gas_charger: &mut GasCharger,
945        protocol_config: &ProtocolConfig,
946        metrics: Arc<ExecutionMetrics>,
947        trace_builder_opt: &mut Option<MoveTraceBuilder>,
948    ) -> ResultWithTimings<Mode::ExecutionResults, Mode::Error> {
949        let result = match transaction_kind {
950            TransactionKind::ChangeEpoch(change_epoch) => {
951                let builder = ProgrammableTransactionBuilder::new();
952                advance_epoch::<Mode>(
953                    builder,
954                    change_epoch,
955                    temporary_store,
956                    store,
957                    tx_ctx,
958                    move_vm,
959                    gas_charger,
960                    protocol_config,
961                    metrics,
962                    trace_builder_opt,
963                )
964                .map_err(|e| (e, vec![]))?;
965                Ok((Mode::empty_results(), vec![]))
966            }
967            TransactionKind::Genesis(GenesisTransaction { objects }) => {
968                if tx_ctx.borrow().epoch() != 0 {
969                    panic!("BUG: Genesis Transactions can only be executed in epoch 0");
970                }
971
972                for genesis_object in objects {
973                    match genesis_object {
974                        sui_types::transaction::GenesisObject::RawObject { data, owner } => {
975                            let object = ObjectInner {
976                                data,
977                                owner,
978                                previous_transaction: tx_ctx.borrow().digest(),
979                                storage_rebate: 0,
980                            };
981                            temporary_store.create_object(object.into());
982                        }
983                    }
984                }
985                Ok((Mode::empty_results(), vec![]))
986            }
987            TransactionKind::ConsensusCommitPrologue(prologue) => {
988                setup_consensus_commit::<Mode>(
989                    prologue.commit_timestamp_ms,
990                    temporary_store,
991                    store,
992                    tx_ctx,
993                    move_vm,
994                    gas_charger,
995                    protocol_config,
996                    metrics,
997                    trace_builder_opt,
998                )
999                .expect("ConsensusCommitPrologue cannot fail");
1000                Ok((Mode::empty_results(), vec![]))
1001            }
1002            TransactionKind::ConsensusCommitPrologueV2(prologue) => {
1003                setup_consensus_commit::<Mode>(
1004                    prologue.commit_timestamp_ms,
1005                    temporary_store,
1006                    store,
1007                    tx_ctx,
1008                    move_vm,
1009                    gas_charger,
1010                    protocol_config,
1011                    metrics,
1012                    trace_builder_opt,
1013                )
1014                .expect("ConsensusCommitPrologueV2 cannot fail");
1015                Ok((Mode::empty_results(), vec![]))
1016            }
1017            TransactionKind::ConsensusCommitPrologueV3(prologue) => {
1018                setup_consensus_commit::<Mode>(
1019                    prologue.commit_timestamp_ms,
1020                    temporary_store,
1021                    store,
1022                    tx_ctx,
1023                    move_vm,
1024                    gas_charger,
1025                    protocol_config,
1026                    metrics,
1027                    trace_builder_opt,
1028                )
1029                .expect("ConsensusCommitPrologueV3 cannot fail");
1030                Ok((Mode::empty_results(), vec![]))
1031            }
1032            TransactionKind::ConsensusCommitPrologueV4(prologue) => {
1033                setup_consensus_commit::<Mode>(
1034                    prologue.commit_timestamp_ms,
1035                    temporary_store,
1036                    store,
1037                    tx_ctx,
1038                    move_vm,
1039                    gas_charger,
1040                    protocol_config,
1041                    metrics,
1042                    trace_builder_opt,
1043                )
1044                .expect("ConsensusCommitPrologue cannot fail");
1045                Ok((Mode::empty_results(), vec![]))
1046            }
1047            TransactionKind::ProgrammableTransaction(pt) => SPT::execute::<Mode>(
1048                protocol_config,
1049                metrics,
1050                move_vm,
1051                temporary_store,
1052                store.as_backing_package_store(),
1053                tx_ctx,
1054                gas_charger,
1055                rewritten_inputs,
1056                pt,
1057                trace_builder_opt,
1058            ),
1059            TransactionKind::ProgrammableSystemTransaction(pt) => {
1060                SPT::execute::<execution_mode::System<Mode::Error>>(
1061                    protocol_config,
1062                    metrics,
1063                    move_vm,
1064                    temporary_store,
1065                    store.as_backing_package_store(),
1066                    tx_ctx,
1067                    gas_charger,
1068                    None,
1069                    pt,
1070                    trace_builder_opt,
1071                )
1072                .map_err(|(e, _)| (e, vec![]))?;
1073                Ok((Mode::empty_results(), vec![]))
1074            }
1075            TransactionKind::EndOfEpochTransaction(txns) => {
1076                let mut builder = ProgrammableTransactionBuilder::new();
1077                let len = txns.len();
1078                for (i, tx) in txns.into_iter().enumerate() {
1079                    match tx {
1080                        EndOfEpochTransactionKind::ChangeEpoch(change_epoch) => {
1081                            assert_eq!(i, len - 1);
1082                            advance_epoch::<Mode>(
1083                                builder,
1084                                change_epoch,
1085                                temporary_store,
1086                                store,
1087                                tx_ctx,
1088                                move_vm,
1089                                gas_charger,
1090                                protocol_config,
1091                                metrics,
1092                                trace_builder_opt,
1093                            )
1094                            .map_err(|e| (e, vec![]))?;
1095                            return Ok((Mode::empty_results(), vec![]));
1096                        }
1097                        EndOfEpochTransactionKind::AuthenticatorStateCreate => {
1098                            assert!(protocol_config.enable_jwk_consensus_updates());
1099                            builder = setup_authenticator_state_create(builder);
1100                        }
1101                        EndOfEpochTransactionKind::AuthenticatorStateExpire(expire) => {
1102                            assert!(protocol_config.enable_jwk_consensus_updates());
1103
1104                            // TODO: it would be nice if a failure of this function didn't cause
1105                            // safe mode.
1106                            builder = setup_authenticator_state_expire(builder, expire);
1107                        }
1108                        EndOfEpochTransactionKind::RandomnessStateCreate => {
1109                            assert!(protocol_config.random_beacon());
1110                            builder = setup_randomness_state_create(builder);
1111                        }
1112                        EndOfEpochTransactionKind::DenyListStateCreate => {
1113                            assert!(protocol_config.enable_coin_deny_list());
1114                            builder = setup_coin_deny_list_state_create(builder);
1115                        }
1116                        EndOfEpochTransactionKind::BridgeStateCreate(chain_id) => {
1117                            assert!(protocol_config.bridge());
1118                            builder = setup_bridge_create(builder, chain_id)
1119                        }
1120                        EndOfEpochTransactionKind::BridgeCommitteeInit(bridge_shared_version) => {
1121                            assert!(protocol_config.bridge());
1122                            assert!(protocol_config.should_try_to_finalize_bridge_committee());
1123                            builder = setup_bridge_committee_update(builder, bridge_shared_version)
1124                        }
1125                        EndOfEpochTransactionKind::StoreExecutionTimeObservations(estimates) => {
1126                            if let PerObjectCongestionControlMode::ExecutionTimeEstimate(params) =
1127                                protocol_config.per_object_congestion_control_mode()
1128                            {
1129                                let chunk_size = params
1130                                    .observations_chunk_size
1131                                    .expect("observation chunking is enabled at all protocol versions handled by this execution layer");
1132                                builder = setup_store_execution_time_estimates(
1133                                    builder,
1134                                    estimates,
1135                                    chunk_size as usize,
1136                                );
1137                            }
1138                        }
1139                        EndOfEpochTransactionKind::AccumulatorRootCreate => {
1140                            assert!(protocol_config.create_root_accumulator_object());
1141                            builder = setup_accumulator_root_create(builder);
1142                        }
1143                        EndOfEpochTransactionKind::WriteAccumulatorStorageCost(
1144                            write_storage_cost,
1145                        ) => {
1146                            assert!(protocol_config.enable_accumulators());
1147                            builder =
1148                                setup_write_accumulator_storage_cost(builder, &write_storage_cost);
1149                        }
1150                        EndOfEpochTransactionKind::CoinRegistryCreate => {
1151                            assert!(protocol_config.enable_coin_registry());
1152                            builder = setup_coin_registry_create(builder);
1153                        }
1154                        EndOfEpochTransactionKind::DisplayRegistryCreate => {
1155                            assert!(protocol_config.enable_display_registry());
1156                            builder = setup_display_registry_create(builder);
1157                        }
1158                        EndOfEpochTransactionKind::AddressAliasStateCreate => {
1159                            assert!(protocol_config.address_aliases());
1160                            builder = setup_address_alias_state_create(builder);
1161                        }
1162                        EndOfEpochTransactionKind::ForwardingAddressRegistryCreate => {
1163                            assert!(protocol_config.create_forwarding_address_registry());
1164                            builder = setup_forwarding_address_registry_create(builder);
1165                        }
1166                    }
1167                }
1168                unreachable!(
1169                    "EndOfEpochTransactionKind::ChangeEpoch should be the last transaction in the list"
1170                )
1171            }
1172            TransactionKind::AuthenticatorStateUpdate(auth_state_update) => {
1173                setup_authenticator_state_update::<Mode>(
1174                    auth_state_update,
1175                    temporary_store,
1176                    store,
1177                    tx_ctx,
1178                    move_vm,
1179                    gas_charger,
1180                    protocol_config,
1181                    metrics,
1182                    trace_builder_opt,
1183                )
1184                .map_err(|e| (e, vec![]))?;
1185                Ok((Mode::empty_results(), vec![]))
1186            }
1187            TransactionKind::RandomnessStateUpdate(randomness_state_update) => {
1188                setup_randomness_state_update::<Mode>(
1189                    randomness_state_update,
1190                    temporary_store,
1191                    store,
1192                    tx_ctx,
1193                    move_vm,
1194                    gas_charger,
1195                    protocol_config,
1196                    metrics,
1197                    trace_builder_opt,
1198                )
1199                .map_err(|e| (e, vec![]))?;
1200                Ok((Mode::empty_results(), vec![]))
1201            }
1202        }?;
1203        temporary_store
1204            .check_execution_results_consistency::<Mode>()
1205            .map_err(|e| (e, vec![]))?;
1206        Ok(result)
1207    }
1208
1209    fn mint_epoch_rewards_in_pt(
1210        builder: &mut ProgrammableTransactionBuilder,
1211        params: &AdvanceEpochParams,
1212    ) -> (Argument, Argument) {
1213        // Create storage rewards.
1214        let storage_charge_arg = builder
1215            .input(CallArg::Pure(
1216                bcs::to_bytes(&params.storage_charge).unwrap(),
1217            ))
1218            .unwrap();
1219        let storage_rewards = builder.programmable_move_call(
1220            SUI_FRAMEWORK_PACKAGE_ID,
1221            BALANCE_MODULE_NAME.to_owned(),
1222            BALANCE_CREATE_REWARDS_FUNCTION_NAME.to_owned(),
1223            vec![GAS::type_tag()],
1224            vec![storage_charge_arg],
1225        );
1226
1227        // Create computation rewards.
1228        let computation_charge_arg = builder
1229            .input(CallArg::Pure(
1230                bcs::to_bytes(&params.computation_charge).unwrap(),
1231            ))
1232            .unwrap();
1233        let computation_rewards = builder.programmable_move_call(
1234            SUI_FRAMEWORK_PACKAGE_ID,
1235            BALANCE_MODULE_NAME.to_owned(),
1236            BALANCE_CREATE_REWARDS_FUNCTION_NAME.to_owned(),
1237            vec![GAS::type_tag()],
1238            vec![computation_charge_arg],
1239        );
1240        (storage_rewards, computation_rewards)
1241    }
1242
1243    pub fn construct_advance_epoch_pt<Mode: ExecutionMode>(
1244        mut builder: ProgrammableTransactionBuilder,
1245        params: &AdvanceEpochParams,
1246    ) -> Result<ProgrammableTransaction, Mode::Error> {
1247        // Step 1: Create storage and computation rewards.
1248        let (storage_rewards, computation_rewards) = mint_epoch_rewards_in_pt(&mut builder, params);
1249
1250        // Step 2: Advance the epoch.
1251        let mut arguments = vec![storage_rewards, computation_rewards];
1252        let call_arg_arguments = vec![
1253            CallArg::SUI_SYSTEM_MUT,
1254            CallArg::Pure(bcs::to_bytes(&params.epoch).unwrap()),
1255            CallArg::Pure(bcs::to_bytes(&params.next_protocol_version.as_u64()).unwrap()),
1256            CallArg::Pure(bcs::to_bytes(&params.storage_rebate).unwrap()),
1257            CallArg::Pure(bcs::to_bytes(&params.non_refundable_storage_fee).unwrap()),
1258            CallArg::Pure(bcs::to_bytes(&params.storage_fund_reinvest_rate).unwrap()),
1259            CallArg::Pure(bcs::to_bytes(&params.reward_slashing_rate).unwrap()),
1260            CallArg::Pure(bcs::to_bytes(&params.epoch_start_timestamp_ms).unwrap()),
1261        ]
1262        .into_iter()
1263        .map(|a| builder.input(a))
1264        .collect::<Result<_, _>>();
1265
1266        assert_invariant!(
1267            call_arg_arguments.is_ok(),
1268            "Unable to generate args for advance_epoch transaction!"
1269        );
1270
1271        arguments.append(&mut call_arg_arguments.unwrap());
1272
1273        info!("Call arguments to advance_epoch transaction: {:?}", params);
1274
1275        let storage_rebates = builder.programmable_move_call(
1276            SUI_SYSTEM_PACKAGE_ID,
1277            SUI_SYSTEM_MODULE_NAME.to_owned(),
1278            ADVANCE_EPOCH_FUNCTION_NAME.to_owned(),
1279            vec![],
1280            arguments,
1281        );
1282
1283        // Step 3: Destroy the storage rebates.
1284        builder.programmable_move_call(
1285            SUI_FRAMEWORK_PACKAGE_ID,
1286            BALANCE_MODULE_NAME.to_owned(),
1287            BALANCE_DESTROY_REBATES_FUNCTION_NAME.to_owned(),
1288            vec![GAS::type_tag()],
1289            vec![storage_rebates],
1290        );
1291        Ok(builder.finish())
1292    }
1293
1294    pub fn construct_advance_epoch_safe_mode_pt(
1295        params: &AdvanceEpochParams,
1296    ) -> Result<ProgrammableTransaction, ExecutionError> {
1297        let mut builder = ProgrammableTransactionBuilder::new();
1298        // Step 1: Create storage and computation rewards.
1299        let (storage_rewards, computation_rewards) = mint_epoch_rewards_in_pt(&mut builder, params);
1300
1301        // Step 2: Advance the epoch.
1302        let mut arguments = vec![storage_rewards, computation_rewards];
1303
1304        let mut args = vec![
1305            CallArg::SUI_SYSTEM_MUT,
1306            CallArg::Pure(bcs::to_bytes(&params.epoch).unwrap()),
1307            CallArg::Pure(bcs::to_bytes(&params.next_protocol_version.as_u64()).unwrap()),
1308            CallArg::Pure(bcs::to_bytes(&params.storage_rebate).unwrap()),
1309            CallArg::Pure(bcs::to_bytes(&params.non_refundable_storage_fee).unwrap()),
1310        ];
1311
1312        args.push(CallArg::Pure(
1313            bcs::to_bytes(&params.epoch_start_timestamp_ms).unwrap(),
1314        ));
1315
1316        let call_arg_arguments = args
1317            .into_iter()
1318            .map(|a| builder.input(a))
1319            .collect::<Result<_, _>>();
1320
1321        assert_invariant!(
1322            call_arg_arguments.is_ok(),
1323            "Unable to generate args for advance_epoch transaction!"
1324        );
1325
1326        arguments.append(&mut call_arg_arguments.unwrap());
1327
1328        info!("Call arguments to advance_epoch transaction: {:?}", params);
1329
1330        builder.programmable_move_call(
1331            SUI_SYSTEM_PACKAGE_ID,
1332            SUI_SYSTEM_MODULE_NAME.to_owned(),
1333            ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME.to_owned(),
1334            vec![],
1335            arguments,
1336        );
1337
1338        Ok(builder.finish())
1339    }
1340
1341    fn advance_epoch<Mode: ExecutionMode>(
1342        builder: ProgrammableTransactionBuilder,
1343        change_epoch: ChangeEpoch,
1344        temporary_store: &mut TemporaryStore<'_>,
1345        store: &dyn BackingStore,
1346        tx_ctx: Rc<RefCell<TxContext>>,
1347        move_vm: &Arc<MoveRuntime>,
1348        gas_charger: &mut GasCharger,
1349        protocol_config: &ProtocolConfig,
1350        metrics: Arc<ExecutionMetrics>,
1351        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1352    ) -> Result<(), Mode::Error> {
1353        let params = AdvanceEpochParams {
1354            epoch: change_epoch.epoch,
1355            next_protocol_version: change_epoch.protocol_version,
1356            storage_charge: change_epoch.storage_charge,
1357            computation_charge: change_epoch.computation_charge,
1358            storage_rebate: change_epoch.storage_rebate,
1359            non_refundable_storage_fee: change_epoch.non_refundable_storage_fee,
1360            storage_fund_reinvest_rate: protocol_config.storage_fund_reinvest_rate(),
1361            reward_slashing_rate: protocol_config.reward_slashing_rate(),
1362            epoch_start_timestamp_ms: change_epoch.epoch_start_timestamp_ms,
1363        };
1364        let advance_epoch_pt = construct_advance_epoch_pt::<Mode>(builder, &params)?;
1365        let result = SPT::execute::<execution_mode::System<Mode::Error>>(
1366            protocol_config,
1367            metrics.clone(),
1368            move_vm,
1369            temporary_store,
1370            store.as_backing_package_store(),
1371            tx_ctx.clone(),
1372            gas_charger,
1373            None,
1374            advance_epoch_pt,
1375            trace_builder_opt,
1376        );
1377
1378        #[cfg(msim)]
1379        let result = maybe_modify_result_for(result, change_epoch.epoch);
1380
1381        if let Err(err) = &result {
1382            tracing::error!(
1383                "Failed to execute advance epoch transaction. Switching to safe mode. Error: {:?}. Input objects: {:?}. Tx data: {:?}",
1384                err.0,
1385                temporary_store.objects(),
1386                change_epoch,
1387            );
1388            temporary_store.drop_writes();
1389            // Must reset the storage rebate since we are re-executing.
1390            gas_charger.reset_storage_cost_and_rebate();
1391
1392            temporary_store.advance_epoch_safe_mode(&params, protocol_config);
1393        }
1394
1395        let new_vm = new_move_runtime(
1396            all_natives(/* silent */ true, protocol_config),
1397            protocol_config,
1398        )
1399        .expect("Failed to create new MoveRuntime");
1400        process_system_packages(
1401            change_epoch,
1402            temporary_store,
1403            store,
1404            tx_ctx,
1405            &new_vm,
1406            gas_charger,
1407            protocol_config,
1408            metrics,
1409            trace_builder_opt,
1410        );
1411        Ok(())
1412    }
1413
1414    fn process_system_packages(
1415        change_epoch: ChangeEpoch,
1416        temporary_store: &mut TemporaryStore<'_>,
1417        store: &dyn BackingStore,
1418        tx_ctx: Rc<RefCell<TxContext>>,
1419        move_vm: &MoveRuntime,
1420        gas_charger: &mut GasCharger,
1421        protocol_config: &ProtocolConfig,
1422        metrics: Arc<ExecutionMetrics>,
1423        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1424    ) {
1425        let digest = tx_ctx.borrow().digest();
1426        let binary_config = protocol_config.binary_config(None);
1427        for (version, modules, dependencies) in change_epoch.system_packages.into_iter() {
1428            let deserialized_modules: Vec<_> = modules
1429                .iter()
1430                .map(|m| CompiledModule::deserialize_with_config(m, &binary_config).unwrap())
1431                .collect();
1432
1433            if version == OBJECT_START_VERSION {
1434                let package_id = deserialized_modules.first().unwrap().address();
1435                info!("adding new system package {package_id}");
1436
1437                let publish_pt = {
1438                    let mut b = ProgrammableTransactionBuilder::new();
1439                    b.command(Command::Publish(modules, dependencies));
1440                    b.finish()
1441                };
1442
1443                SPT::execute::<execution_mode::System>(
1444                    protocol_config,
1445                    metrics.clone(),
1446                    move_vm,
1447                    temporary_store,
1448                    store.as_backing_package_store(),
1449                    tx_ctx.clone(),
1450                    gas_charger,
1451                    None,
1452                    publish_pt,
1453                    trace_builder_opt,
1454                )
1455                .map_err(|(e, _)| e)
1456                .expect("System Package Publish must succeed");
1457            } else {
1458                let mut new_package = Object::new_system_package(
1459                    &deserialized_modules,
1460                    version,
1461                    dependencies,
1462                    digest,
1463                );
1464
1465                info!(
1466                    "upgraded system package {:?}",
1467                    new_package.compute_object_reference()
1468                );
1469
1470                // Decrement the version before writing the package so that the store can record the
1471                // version growing by one in the effects.
1472                new_package
1473                    .data
1474                    .try_as_package_mut()
1475                    .unwrap()
1476                    .decrement_version();
1477
1478                // upgrade of a previously existing framework module
1479                temporary_store.upgrade_system_package(new_package);
1480            }
1481        }
1482    }
1483
1484    /// Perform metadata updates in preparation for the transactions in the upcoming checkpoint:
1485    ///
1486    /// - Set the timestamp for the `Clock` shared object from the timestamp in the header from
1487    ///   consensus.
1488    fn setup_consensus_commit<Mode: ExecutionMode>(
1489        consensus_commit_timestamp_ms: CheckpointTimestamp,
1490        temporary_store: &mut TemporaryStore<'_>,
1491        store: &dyn BackingStore,
1492        tx_ctx: Rc<RefCell<TxContext>>,
1493        move_vm: &Arc<MoveRuntime>,
1494        gas_charger: &mut GasCharger,
1495        protocol_config: &ProtocolConfig,
1496        metrics: Arc<ExecutionMetrics>,
1497        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1498    ) -> Result<(), Mode::Error> {
1499        let pt = {
1500            let mut builder = ProgrammableTransactionBuilder::new();
1501            let res = builder.move_call(
1502                SUI_FRAMEWORK_ADDRESS.into(),
1503                CLOCK_MODULE_NAME.to_owned(),
1504                CONSENSUS_COMMIT_PROLOGUE_FUNCTION_NAME.to_owned(),
1505                vec![],
1506                vec![
1507                    CallArg::CLOCK_MUT,
1508                    CallArg::Pure(bcs::to_bytes(&consensus_commit_timestamp_ms).unwrap()),
1509                ],
1510            );
1511            assert_invariant!(
1512                res.is_ok(),
1513                "Unable to generate consensus_commit_prologue transaction!"
1514            );
1515            builder.finish()
1516        };
1517        SPT::execute::<execution_mode::System<Mode::Error>>(
1518            protocol_config,
1519            metrics,
1520            move_vm,
1521            temporary_store,
1522            store.as_backing_package_store(),
1523            tx_ctx,
1524            gas_charger,
1525            None,
1526            pt,
1527            trace_builder_opt,
1528        )
1529        .map_err(|(e, _)| e)?;
1530        Ok(())
1531    }
1532
1533    fn setup_authenticator_state_create(
1534        mut builder: ProgrammableTransactionBuilder,
1535    ) -> ProgrammableTransactionBuilder {
1536        builder
1537            .move_call(
1538                SUI_FRAMEWORK_ADDRESS.into(),
1539                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
1540                AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME.to_owned(),
1541                vec![],
1542                vec![],
1543            )
1544            .expect("Unable to generate authenticator_state_create transaction!");
1545        builder
1546    }
1547
1548    fn setup_randomness_state_create(
1549        mut builder: ProgrammableTransactionBuilder,
1550    ) -> ProgrammableTransactionBuilder {
1551        builder
1552            .move_call(
1553                SUI_FRAMEWORK_ADDRESS.into(),
1554                RANDOMNESS_MODULE_NAME.to_owned(),
1555                RANDOMNESS_STATE_CREATE_FUNCTION_NAME.to_owned(),
1556                vec![],
1557                vec![],
1558            )
1559            .expect("Unable to generate randomness_state_create transaction!");
1560        builder
1561    }
1562
1563    fn setup_bridge_create(
1564        mut builder: ProgrammableTransactionBuilder,
1565        chain_id: ChainIdentifier,
1566    ) -> ProgrammableTransactionBuilder {
1567        let bridge_uid = builder
1568            .input(CallArg::Pure(UID::new(SUI_BRIDGE_OBJECT_ID).to_bcs_bytes()))
1569            .expect("Unable to create Bridge object UID!");
1570
1571        let bridge_chain_id = if chain_id == get_mainnet_chain_identifier() {
1572            BridgeChainId::SuiMainnet as u8
1573        } else if chain_id == get_testnet_chain_identifier() {
1574            BridgeChainId::SuiTestnet as u8
1575        } else {
1576            // How do we distinguish devnet from other test envs?
1577            BridgeChainId::SuiCustom as u8
1578        };
1579
1580        let bridge_chain_id = builder.pure(bridge_chain_id).unwrap();
1581        builder.programmable_move_call(
1582            BRIDGE_ADDRESS.into(),
1583            BRIDGE_MODULE_NAME.to_owned(),
1584            BRIDGE_CREATE_FUNCTION_NAME.to_owned(),
1585            vec![],
1586            vec![bridge_uid, bridge_chain_id],
1587        );
1588        builder
1589    }
1590
1591    fn setup_bridge_committee_update(
1592        mut builder: ProgrammableTransactionBuilder,
1593        bridge_shared_version: SequenceNumber,
1594    ) -> ProgrammableTransactionBuilder {
1595        let bridge = builder
1596            .obj(ObjectArg::SharedObject {
1597                id: SUI_BRIDGE_OBJECT_ID,
1598                initial_shared_version: bridge_shared_version,
1599                mutability: sui_types::transaction::SharedObjectMutability::Mutable,
1600            })
1601            .expect("Unable to create Bridge object arg!");
1602        let system_state = builder
1603            .obj(ObjectArg::SUI_SYSTEM_MUT)
1604            .expect("Unable to create System State object arg!");
1605
1606        let voting_power = builder.programmable_move_call(
1607            SUI_SYSTEM_PACKAGE_ID,
1608            SUI_SYSTEM_MODULE_NAME.to_owned(),
1609            ident_str!("validator_voting_powers").to_owned(),
1610            vec![],
1611            vec![system_state],
1612        );
1613
1614        // Hardcoding min stake participation to 75.00%
1615        // TODO: We need to set a correct value or make this configurable.
1616        let min_stake_participation_percentage = builder
1617            .input(CallArg::Pure(
1618                bcs::to_bytes(&BRIDGE_COMMITTEE_MINIMAL_VOTING_POWER).unwrap(),
1619            ))
1620            .unwrap();
1621
1622        builder.programmable_move_call(
1623            BRIDGE_ADDRESS.into(),
1624            BRIDGE_MODULE_NAME.to_owned(),
1625            BRIDGE_INIT_COMMITTEE_FUNCTION_NAME.to_owned(),
1626            vec![],
1627            vec![bridge, voting_power, min_stake_participation_percentage],
1628        );
1629        builder
1630    }
1631
1632    fn setup_authenticator_state_update<Mode: ExecutionMode>(
1633        update: AuthenticatorStateUpdate,
1634        temporary_store: &mut TemporaryStore<'_>,
1635        store: &dyn BackingStore,
1636        tx_ctx: Rc<RefCell<TxContext>>,
1637        move_vm: &Arc<MoveRuntime>,
1638        gas_charger: &mut GasCharger,
1639        protocol_config: &ProtocolConfig,
1640        metrics: Arc<ExecutionMetrics>,
1641        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1642    ) -> Result<(), Mode::Error> {
1643        let pt = {
1644            let mut builder = ProgrammableTransactionBuilder::new();
1645            let res = builder.move_call(
1646                SUI_FRAMEWORK_ADDRESS.into(),
1647                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
1648                AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME.to_owned(),
1649                vec![],
1650                vec![
1651                    CallArg::Object(ObjectArg::SharedObject {
1652                        id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1653                        initial_shared_version: update.authenticator_obj_initial_shared_version,
1654                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
1655                    }),
1656                    CallArg::Pure(bcs::to_bytes(&update.new_active_jwks).unwrap()),
1657                ],
1658            );
1659            assert_invariant!(
1660                res.is_ok(),
1661                "Unable to generate authenticator_state_update transaction!"
1662            );
1663            builder.finish()
1664        };
1665        SPT::execute::<execution_mode::System<Mode::Error>>(
1666            protocol_config,
1667            metrics,
1668            move_vm,
1669            temporary_store,
1670            store.as_backing_package_store(),
1671            tx_ctx,
1672            gas_charger,
1673            None,
1674            pt,
1675            trace_builder_opt,
1676        )
1677        .map_err(|(e, _)| e)?;
1678        Ok(())
1679    }
1680
1681    fn setup_authenticator_state_expire(
1682        mut builder: ProgrammableTransactionBuilder,
1683        expire: AuthenticatorStateExpire,
1684    ) -> ProgrammableTransactionBuilder {
1685        builder
1686            .move_call(
1687                SUI_FRAMEWORK_ADDRESS.into(),
1688                AUTHENTICATOR_STATE_MODULE_NAME.to_owned(),
1689                AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME.to_owned(),
1690                vec![],
1691                vec![
1692                    CallArg::Object(ObjectArg::SharedObject {
1693                        id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1694                        initial_shared_version: expire.authenticator_obj_initial_shared_version,
1695                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
1696                    }),
1697                    CallArg::Pure(bcs::to_bytes(&expire.min_epoch).unwrap()),
1698                ],
1699            )
1700            .expect("Unable to generate authenticator_state_expire transaction!");
1701        builder
1702    }
1703
1704    fn setup_randomness_state_update<Mode: ExecutionMode>(
1705        update: RandomnessStateUpdate,
1706        temporary_store: &mut TemporaryStore<'_>,
1707        store: &dyn BackingStore,
1708        tx_ctx: Rc<RefCell<TxContext>>,
1709        move_vm: &Arc<MoveRuntime>,
1710        gas_charger: &mut GasCharger,
1711        protocol_config: &ProtocolConfig,
1712        metrics: Arc<ExecutionMetrics>,
1713        trace_builder_opt: &mut Option<MoveTraceBuilder>,
1714    ) -> Result<(), Mode::Error> {
1715        let pt = {
1716            let mut builder = ProgrammableTransactionBuilder::new();
1717            let res = builder.move_call(
1718                SUI_FRAMEWORK_ADDRESS.into(),
1719                RANDOMNESS_MODULE_NAME.to_owned(),
1720                RANDOMNESS_STATE_UPDATE_FUNCTION_NAME.to_owned(),
1721                vec![],
1722                vec![
1723                    CallArg::Object(ObjectArg::SharedObject {
1724                        id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1725                        initial_shared_version: update.randomness_obj_initial_shared_version,
1726                        mutability: sui_types::transaction::SharedObjectMutability::Mutable,
1727                    }),
1728                    CallArg::Pure(bcs::to_bytes(&update.randomness_round).unwrap()),
1729                    CallArg::Pure(bcs::to_bytes(&update.random_bytes).unwrap()),
1730                ],
1731            );
1732            assert_invariant!(
1733                res.is_ok(),
1734                "Unable to generate randomness_state_update transaction!"
1735            );
1736            builder.finish()
1737        };
1738        SPT::execute::<execution_mode::System<Mode::Error>>(
1739            protocol_config,
1740            metrics,
1741            move_vm,
1742            temporary_store,
1743            store.as_backing_package_store(),
1744            tx_ctx,
1745            gas_charger,
1746            None,
1747            pt,
1748            trace_builder_opt,
1749        )
1750        .map_err(|(e, _)| e)?;
1751        Ok(())
1752    }
1753
1754    fn setup_coin_deny_list_state_create(
1755        mut builder: ProgrammableTransactionBuilder,
1756    ) -> ProgrammableTransactionBuilder {
1757        builder
1758            .move_call(
1759                SUI_FRAMEWORK_ADDRESS.into(),
1760                DENY_LIST_MODULE.to_owned(),
1761                DENY_LIST_CREATE_FUNC.to_owned(),
1762                vec![],
1763                vec![],
1764            )
1765            .expect("Unable to generate coin_deny_list_create transaction!");
1766        builder
1767    }
1768
1769    fn setup_store_execution_time_estimates(
1770        mut builder: ProgrammableTransactionBuilder,
1771        estimates: StoredExecutionTimeObservations,
1772        chunk_size: usize,
1773    ) -> ProgrammableTransactionBuilder {
1774        let system_state = builder.obj(ObjectArg::SUI_SYSTEM_MUT).unwrap();
1775
1776        let estimate_chunks = estimates.chunk_observations(chunk_size);
1777
1778        let chunk_bytes: Vec<Vec<u8>> = estimate_chunks
1779            .into_iter()
1780            .map(|chunk| bcs::to_bytes(&chunk).unwrap())
1781            .collect();
1782
1783        let chunks_arg = builder.pure(chunk_bytes).unwrap();
1784
1785        builder.programmable_move_call(
1786            SUI_SYSTEM_PACKAGE_ID,
1787            SUI_SYSTEM_MODULE_NAME.to_owned(),
1788            ident_str!("store_execution_time_estimates_v2").to_owned(),
1789            vec![],
1790            vec![system_state, chunks_arg],
1791        );
1792        builder
1793    }
1794
1795    fn setup_accumulator_root_create(
1796        mut builder: ProgrammableTransactionBuilder,
1797    ) -> ProgrammableTransactionBuilder {
1798        builder
1799            .move_call(
1800                SUI_FRAMEWORK_ADDRESS.into(),
1801                ACCUMULATOR_ROOT_MODULE.to_owned(),
1802                ACCUMULATOR_ROOT_CREATE_FUNC.to_owned(),
1803                vec![],
1804                vec![],
1805            )
1806            .expect("Unable to generate accumulator_root_create transaction!");
1807        builder
1808    }
1809
1810    fn setup_write_accumulator_storage_cost(
1811        mut builder: ProgrammableTransactionBuilder,
1812        write_storage_cost: &WriteAccumulatorStorageCost,
1813    ) -> ProgrammableTransactionBuilder {
1814        let system_state = builder.obj(ObjectArg::SUI_SYSTEM_MUT).unwrap();
1815        let storage_cost_arg = builder.pure(write_storage_cost.storage_cost).unwrap();
1816        builder.programmable_move_call(
1817            SUI_SYSTEM_PACKAGE_ID,
1818            SUI_SYSTEM_MODULE_NAME.to_owned(),
1819            ident_str!("write_accumulator_storage_cost").to_owned(),
1820            vec![],
1821            vec![system_state, storage_cost_arg],
1822        );
1823        builder
1824    }
1825
1826    fn setup_coin_registry_create(
1827        mut builder: ProgrammableTransactionBuilder,
1828    ) -> ProgrammableTransactionBuilder {
1829        builder
1830            .move_call(
1831                SUI_FRAMEWORK_ADDRESS.into(),
1832                ident_str!("coin_registry").to_owned(),
1833                ident_str!("create").to_owned(),
1834                vec![],
1835                vec![],
1836            )
1837            .expect("Unable to generate coin_registry_create transaction!");
1838        builder
1839    }
1840
1841    fn setup_display_registry_create(
1842        mut builder: ProgrammableTransactionBuilder,
1843    ) -> ProgrammableTransactionBuilder {
1844        builder
1845            .move_call(
1846                SUI_FRAMEWORK_ADDRESS.into(),
1847                ident_str!("display_registry").to_owned(),
1848                ident_str!("create").to_owned(),
1849                vec![],
1850                vec![],
1851            )
1852            .expect("Unable to generate display_registry_create transaction!");
1853        builder
1854    }
1855
1856    fn setup_address_alias_state_create(
1857        mut builder: ProgrammableTransactionBuilder,
1858    ) -> ProgrammableTransactionBuilder {
1859        builder
1860            .move_call(
1861                SUI_FRAMEWORK_ADDRESS.into(),
1862                ident_str!("address_alias").to_owned(),
1863                ident_str!("create").to_owned(),
1864                vec![],
1865                vec![],
1866            )
1867            .expect("Unable to generate address_alias_state_create transaction!");
1868        builder
1869    }
1870    fn setup_forwarding_address_registry_create(
1871        mut builder: ProgrammableTransactionBuilder,
1872    ) -> ProgrammableTransactionBuilder {
1873        builder
1874            .move_call(
1875                SUI_FRAMEWORK_ADDRESS.into(),
1876                ident_str!("forwarding_address").to_owned(),
1877                ident_str!("create").to_owned(),
1878                vec![],
1879                vec![],
1880            )
1881            .expect("Unable to generate forwarding_address_registry_create transaction!");
1882        builder
1883    }
1884}