sui_adapter_latest/static_programmable_transactions/execution/
interpreter.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    execution_mode::ExecutionMode,
6    gas_charger::GasCharger,
7    object_runtime, sp,
8    static_programmable_transactions::{
9        env::Env,
10        execution::{
11            context::{Context, CtxValue, GasCoinTransfer},
12            trace_utils,
13        },
14        typing::{ast as T, verify::input_arguments::is_coin_send_funds},
15    },
16};
17use move_core_types::account_address::AccountAddress;
18use move_trace_format::format::MoveTraceBuilder;
19use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant};
20use sui_types::{
21    base_types::TxContext,
22    error::ExecutionError,
23    execution::{ExecutionTiming, ResultWithTimings},
24    execution_status::{ExecutionErrorKind, PackageUpgradeError},
25    metrics::LimitsMetrics,
26    move_package::MovePackage,
27    object::Owner,
28};
29use tracing::instrument;
30
31pub fn execute<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
32    env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension>,
33    metrics: Arc<LimitsMetrics>,
34    tx_context: Rc<RefCell<TxContext>>,
35    gas_charger: &mut GasCharger,
36    ast: T::Transaction,
37    trace_builder_opt: &mut Option<MoveTraceBuilder>,
38) -> ResultWithTimings<Mode::ExecutionResults, ExecutionError>
39where
40    'pc: 'state,
41    'env: 'state,
42{
43    let mut timings = vec![];
44    let result = execute_inner::<Mode>(
45        &mut timings,
46        env,
47        metrics,
48        tx_context,
49        gas_charger,
50        ast,
51        trace_builder_opt,
52    );
53
54    match result {
55        Ok(result) => Ok((result, timings)),
56        Err(e) => {
57            trace_utils::trace_execution_error(trace_builder_opt, e.to_string());
58            Err((e, timings))
59        }
60    }
61}
62
63pub fn execute_inner<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
64    timings: &mut Vec<ExecutionTiming>,
65    env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension>,
66    metrics: Arc<LimitsMetrics>,
67    tx_context: Rc<RefCell<TxContext>>,
68    gas_charger: &mut GasCharger,
69    ast: T::Transaction,
70    trace_builder_opt: &mut Option<MoveTraceBuilder>,
71) -> Result<Mode::ExecutionResults, ExecutionError>
72where
73    'pc: 'state,
74{
75    debug_assert_eq!(gas_charger.move_gas_status().stack_height_current(), 0);
76    let T::Transaction {
77        gas_payment,
78        bytes,
79        objects,
80        withdrawals,
81        pure,
82        receiving,
83        withdrawal_compatibility_conversions: _,
84        commands,
85    } = ast;
86    let mut context = Context::new(
87        env,
88        metrics,
89        tx_context,
90        gas_charger,
91        gas_payment,
92        bytes,
93        objects,
94        withdrawals,
95        pure,
96        receiving,
97    )?;
98
99    trace_utils::trace_ptb_summary(&mut context, trace_builder_opt, &commands)?;
100
101    let mut mode_results = Mode::empty_results();
102    for sp!(idx, c) in commands {
103        let start = Instant::now();
104        if let Err(err) =
105            execute_command::<Mode>(&mut context, &mut mode_results, c, trace_builder_opt)
106        {
107            // We still need to record the loaded child objects for replay
108            let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
109            // we do not save the wrapped objects since on error, they should not be modified
110            drop(context);
111            // TODO wtf is going on with the borrow checker here. 'state is bound into the object
112            // runtime, but its since been dropped. what gives with this error?
113            env.state_view
114                .save_loaded_runtime_objects(loaded_runtime_objects);
115            timings.push(ExecutionTiming::Abort(start.elapsed()));
116            return Err(err.with_command_index(idx as usize));
117        };
118        timings.push(ExecutionTiming::Success(start.elapsed()));
119    }
120    // Save loaded objects table in case we fail in post execution
121    //
122    // We still need to record the loaded child objects for replay
123    // Record the objects loaded at runtime (dynamic fields + received) for
124    // storage rebate calculation.
125    let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
126    // We record what objects were contained in at the start of the transaction
127    // for expensive invariant checks
128    let wrapped_object_containers = object_runtime!(context)?.wrapped_object_containers();
129    // We record the generated object IDs for expensive invariant checks
130    let generated_object_ids = object_runtime!(context)?.generated_object_ids();
131
132    // apply changes
133    let finished = context.finish::<Mode>();
134    // Save loaded objects for debug. We dont want to lose the info
135    env.state_view
136        .save_loaded_runtime_objects(loaded_runtime_objects);
137    env.state_view
138        .save_wrapped_object_containers(wrapped_object_containers);
139    env.state_view.record_execution_results(finished?)?;
140    env.state_view
141        .record_generated_object_ids(generated_object_ids);
142    Ok(mode_results)
143}
144
145/// Execute a single command
146#[instrument(level = "trace", skip_all)]
147fn execute_command<Mode: ExecutionMode>(
148    context: &mut Context,
149    mode_results: &mut Mode::ExecutionResults,
150    c: T::Command_,
151    trace_builder_opt: &mut Option<MoveTraceBuilder>,
152) -> Result<(), ExecutionError> {
153    let T::Command_ {
154        command,
155        result_type,
156        drop_values,
157        consumed_shared_objects: _,
158    } = c;
159    assert_invariant!(
160        context.gas_charger.move_gas_status().stack_height_current() == 0,
161        "stack height did not start at 0"
162    );
163    let is_move_call = matches!(command, T::Command__::MoveCall(_));
164    let num_args = command.arguments_len();
165    let mut args_to_update = vec![];
166    let result = match command {
167        T::Command__::MoveCall(move_call) => {
168            trace_utils::trace_move_call_start(trace_builder_opt);
169            let T::MoveCall {
170                function,
171                arguments,
172            } = *move_call;
173            // Detect send_funds with gas coin
174            let is_gas_coin_send_funds = is_coin_send_funds(&function)
175                && arguments.first().is_some_and(|arg| {
176                    matches!(
177                        &arg.value.0,
178                        T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
179                    )
180                });
181            if Mode::TRACK_EXECUTION {
182                args_to_update.extend(
183                    arguments
184                        .iter()
185                        .filter(|arg| matches!(&arg.value.1, T::Type::Reference(/* mut */ true, _)))
186                        .cloned(),
187                )
188            }
189            let arguments: Vec<CtxValue> = context.arguments(arguments)?;
190            if is_gas_coin_send_funds {
191                assert_invariant!(arguments.len() == 2, "coin::send_funds should have 2 args");
192                let recipient = arguments.last().unwrap().to_address()?;
193                context.record_gas_coin_transfer(GasCoinTransfer::SendFunds { recipient })?;
194            }
195            let res = context.vm_move_call(function, arguments, trace_builder_opt);
196            trace_utils::trace_move_call_end(trace_builder_opt);
197            res?
198        }
199        T::Command__::TransferObjects(objects, recipient) => {
200            // Check if any object is the gas coin moved by value before consuming
201            let has_gas_coin_move = objects.iter().any(|arg| {
202                matches!(
203                    &arg.value.0,
204                    T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
205                )
206            });
207            if has_gas_coin_move {
208                context.record_gas_coin_transfer(GasCoinTransfer::TransferObjects)?;
209            }
210            let object_tys = objects
211                .iter()
212                .map(|sp!(_, (_, ty))| ty.clone())
213                .collect::<Vec<_>>();
214            let object_values: Vec<CtxValue> = context.arguments(objects)?;
215            let recipient: AccountAddress = context.argument(recipient)?;
216            assert_invariant!(
217                object_values.len() == object_tys.len(),
218                "object values and types mismatch"
219            );
220            trace_utils::trace_transfer(context, trace_builder_opt, &object_values, &object_tys)?;
221            for (object_value, ty) in object_values.into_iter().zip(object_tys) {
222                // TODO should we just call a Move function?
223                let recipient = Owner::AddressOwner(recipient.into());
224                context.transfer_object(recipient, ty, object_value)?;
225            }
226            vec![]
227        }
228        T::Command__::SplitCoins(ty, coin, amounts) => {
229            let mut trace_values = vec![];
230            // TODO should we just call a Move function?
231            if Mode::TRACK_EXECUTION {
232                args_to_update.push(coin.clone());
233            }
234            let coin_ref: CtxValue = context.argument(coin)?;
235            let amount_values: Vec<u64> = context.arguments(amounts)?;
236            let mut total: u64 = 0;
237            for amount in &amount_values {
238                let Some(new_total) = total.checked_add(*amount) else {
239                    return Err(ExecutionError::from_kind(
240                        ExecutionErrorKind::CoinBalanceOverflow,
241                    ));
242                };
243                total = new_total;
244            }
245            trace_utils::add_move_value_info_from_ctx_value(
246                context,
247                trace_builder_opt,
248                &mut trace_values,
249                &ty,
250                &coin_ref,
251            )?;
252            let coin_value = context.copy_value(&coin_ref)?.coin_ref_value()?;
253            fp_ensure!(
254                coin_value >= total,
255                ExecutionError::new_with_source(
256                    ExecutionErrorKind::InsufficientCoinBalance,
257                    format!("balance: {coin_value} required: {total}")
258                )
259            );
260            coin_ref.coin_ref_subtract_balance(total)?;
261            let amounts = amount_values
262                .into_iter()
263                .map(|a| context.new_coin(a))
264                .collect::<Result<Vec<_>, _>>()?;
265            trace_utils::trace_split_coins(
266                context,
267                trace_builder_opt,
268                &ty,
269                trace_values,
270                &amounts,
271                total,
272            )?;
273
274            amounts
275        }
276        T::Command__::MergeCoins(ty, target, coins) => {
277            let mut trace_values = vec![];
278            // TODO should we just call a Move function?
279            if Mode::TRACK_EXECUTION {
280                args_to_update.push(target.clone());
281            }
282            let target_ref: CtxValue = context.argument(target)?;
283            trace_utils::add_move_value_info_from_ctx_value(
284                context,
285                trace_builder_opt,
286                &mut trace_values,
287                &ty,
288                &target_ref,
289            )?;
290            let coins = context.arguments(coins)?;
291            let amounts = coins
292                .into_iter()
293                .map(|coin| {
294                    trace_utils::add_move_value_info_from_ctx_value(
295                        context,
296                        trace_builder_opt,
297                        &mut trace_values,
298                        &ty,
299                        &coin,
300                    )?;
301                    context.destroy_coin(coin)
302                })
303                .collect::<Result<Vec<_>, _>>()?;
304            let mut additional: u64 = 0;
305            for amount in amounts {
306                let Some(new_additional) = additional.checked_add(amount) else {
307                    return Err(ExecutionError::from_kind(
308                        ExecutionErrorKind::CoinBalanceOverflow,
309                    ));
310                };
311                additional = new_additional;
312            }
313            let target_value = context.copy_value(&target_ref)?.coin_ref_value()?;
314            fp_ensure!(
315                target_value.checked_add(additional).is_some(),
316                ExecutionError::from_kind(ExecutionErrorKind::CoinBalanceOverflow,)
317            );
318            target_ref.coin_ref_add_balance(additional)?;
319            trace_utils::trace_merge_coins(
320                context,
321                trace_builder_opt,
322                &ty,
323                trace_values,
324                additional,
325            )?;
326            vec![]
327        }
328        T::Command__::MakeMoveVec(ty, items) => {
329            let items: Vec<CtxValue> = context.arguments(items)?;
330            trace_utils::trace_make_move_vec(context, trace_builder_opt, &items, &ty)?;
331            vec![CtxValue::vec_pack(ty, items)?]
332        }
333        T::Command__::Publish(module_bytes, dep_ids, linkage) => {
334            trace_utils::trace_publish_event(trace_builder_opt)?;
335            let modules =
336                context.deserialize_modules(&module_bytes, /* is upgrade */ false)?;
337
338            let original_id = context.publish_and_init_package::<Mode>(
339                modules,
340                &dep_ids,
341                linkage,
342                trace_builder_opt,
343            )?;
344
345            if <Mode>::packages_are_predefined() {
346                // no upgrade cap for genesis modules
347                std::vec![]
348            } else {
349                std::vec![context.new_upgrade_cap(original_id)?]
350            }
351        }
352        T::Command__::Upgrade(
353            module_bytes,
354            dep_ids,
355            current_package_id,
356            upgrade_ticket,
357            linkage,
358        ) => {
359            trace_utils::trace_upgrade_event(trace_builder_opt)?;
360            let upgrade_ticket = context
361                .argument::<CtxValue>(upgrade_ticket)?
362                .into_upgrade_ticket()?;
363            // Make sure the passed-in package ID matches the package ID in the `upgrade_ticket`.
364            if current_package_id != upgrade_ticket.package.bytes {
365                return Err(ExecutionError::from_kind(
366                    ExecutionErrorKind::PackageUpgradeError {
367                        upgrade_error: PackageUpgradeError::PackageIDDoesNotMatch {
368                            package_id: current_package_id,
369                            ticket_id: upgrade_ticket.package.bytes,
370                        },
371                    },
372                ));
373            }
374            // deserialize modules and charge gas
375            let modules = context.deserialize_modules(&module_bytes, /* is upgrade */ true)?;
376
377            let computed_digest = MovePackage::compute_digest_for_modules_and_deps(
378                &module_bytes,
379                &dep_ids,
380                /* hash_modules */ true,
381            )
382            .to_vec();
383            if computed_digest != upgrade_ticket.digest {
384                return Err(ExecutionError::from_kind(
385                    ExecutionErrorKind::PackageUpgradeError {
386                        upgrade_error: PackageUpgradeError::DigestDoesNotMatch {
387                            digest: computed_digest,
388                        },
389                    },
390                ));
391            }
392
393            let upgraded_package_id = context.upgrade(
394                modules,
395                &dep_ids,
396                current_package_id,
397                upgrade_ticket.policy,
398                linkage,
399            )?;
400
401            vec![context.upgrade_receipt(upgrade_ticket, upgraded_package_id)]
402        }
403    };
404    if Mode::TRACK_EXECUTION {
405        let argument_updates = context.argument_updates(args_to_update)?;
406        let command_result = context.tracked_results(&result, &result_type)?;
407        Mode::finish_command_v2(mode_results, argument_updates, command_result)?;
408    }
409    assert_invariant!(
410        result.len() == drop_values.len(),
411        "result values and drop values mismatch"
412    );
413    context.charge_command(is_move_call, num_args, result.len())?;
414    let result = result
415        .into_iter()
416        .zip(drop_values)
417        .map(|(value, drop)| if !drop { Some(value) } else { None })
418        .collect::<Vec<_>>();
419    context.result(result)?;
420    assert_invariant!(
421        context.gas_charger.move_gas_status().stack_height_current() == 0,
422        "stack height did not end at 0"
423    );
424    Ok(())
425}