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