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