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