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