1use 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 mysten_common::ZipDebugEqIteratorExt;
20use std::{
21 cell::RefCell,
22 collections::BTreeMap,
23 rc::Rc,
24 sync::Arc,
25 time::{Duration, Instant},
26};
27use sui_types::{
28 base_types::TxContext,
29 error::ExecutionErrorTrait,
30 execution::{ExecutionTiming, ResultWithTimings},
31 execution_status::{ExecutionErrorKind, PackageUpgradeError},
32 metrics::ExecutionMetrics,
33 object::Owner,
34};
35use tracing::instrument;
36
37pub fn execute<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
38 env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension, Mode>,
39 metrics: Arc<ExecutionMetrics>,
40 tx_context: Rc<RefCell<TxContext>>,
41 gas_charger: &mut GasCharger,
42 ast: T::Transaction,
43 trace_builder_opt: &mut Option<MoveTraceBuilder>,
44) -> ResultWithTimings<Mode::ExecutionResults, Mode::Error>
45where
46 'pc: 'state,
47 'env: 'state,
48{
49 let mut indexed_timings = IndexedExecutionTimings::new(ast.original_command_len);
50 let result = execute_inner::<Mode>(
51 &mut indexed_timings,
52 env,
53 metrics,
54 tx_context,
55 gas_charger,
56 ast,
57 trace_builder_opt,
58 );
59 let timings = indexed_timings.into_coalesced();
60
61 match result {
62 Ok(result) => Ok((result, timings)),
63 Err(e) => {
64 trace_utils::trace_execution_error(trace_builder_opt, e.to_string());
65 Err((e, timings))
66 }
67 }
68}
69
70fn execute_inner<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
71 timings: &mut IndexedExecutionTimings,
72 env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension, Mode>,
73 metrics: Arc<ExecutionMetrics>,
74 tx_context: Rc<RefCell<TxContext>>,
75 gas_charger: &mut GasCharger,
76 ast: T::Transaction,
77 trace_builder_opt: &mut Option<MoveTraceBuilder>,
78) -> Result<Mode::ExecutionResults, Mode::Error>
79where
80 'pc: 'state,
81{
82 debug_assert_eq!(gas_charger.move_gas_status().stack_height_current(), 0);
83 let T::Transaction {
84 gas_payment,
85 bytes,
86 objects,
87 withdrawals,
88 pure,
89 receiving,
90 withdrawal_compatibility_conversions: _,
91 original_command_len: _,
92 commands,
93 unified_linkage: _,
94 } = ast;
95 let mut context = Context::new(
96 env,
97 metrics,
98 tx_context,
99 gas_charger,
100 gas_payment,
101 bytes,
102 objects,
103 withdrawals,
104 pure,
105 receiving,
106 )?;
107
108 trace_utils::trace_ptb_summary(&mut context, trace_builder_opt, &commands)?;
109
110 let mut mode_results = Mode::empty_results();
111 for sp!(annotated_index, c) in commands {
112 let annotated_index = annotated_index as usize;
113 let start = Instant::now();
114 if let Err(err) =
115 execute_command::<Mode>(&mut context, &mut mode_results, c, trace_builder_opt)
116 {
117 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
119 drop(context);
121 env.state_view
124 .save_loaded_runtime_objects(loaded_runtime_objects);
125 timings.error(annotated_index, start.elapsed());
126 return Err(err.with_command_index(annotated_index));
127 };
128 timings.executed(annotated_index, start.elapsed());
129 }
130 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
136 let wrapped_object_containers = object_runtime!(context)?.wrapped_object_containers();
139 let generated_object_ids = object_runtime!(context)?.generated_object_ids();
141
142 let finished = context.finish();
144 env.state_view
146 .save_loaded_runtime_objects(loaded_runtime_objects);
147 env.state_view
148 .save_wrapped_object_containers(wrapped_object_containers);
149 env.state_view.record_execution_results(finished?)?;
150 env.state_view
151 .record_generated_object_ids(generated_object_ids);
152 Ok(mode_results)
153}
154
155#[instrument(level = "trace", skip_all)]
157fn execute_command<Mode: ExecutionMode>(
158 context: &mut Context<Mode>,
159 mode_results: &mut Mode::ExecutionResults,
160 c: T::Command_,
161 trace_builder_opt: &mut Option<MoveTraceBuilder>,
162) -> Result<(), Mode::Error> {
163 let T::Command_ {
164 command,
165 result_type,
166 drop_values,
167 incurs_post_execution_checks: _,
168 } = c;
169 assert_invariant!(
170 context.gas_charger.move_gas_status().stack_height_current() == 0,
171 "stack height did not start at 0"
172 );
173 let is_move_call = matches!(command, T::Command__::MoveCall(_));
174 let num_args = command.arguments_len();
175 let mut args_to_update = vec![];
176 let result = match command {
177 T::Command__::MoveCall(move_call) => {
178 trace_utils::trace_move_call_start(trace_builder_opt);
179 let T::MoveCall {
180 function,
181 arguments,
182 } = *move_call;
183 let is_gas_coin_send_funds = is_coin_send_funds(&function)
185 && arguments.first().is_some_and(|arg| {
186 matches!(
187 &arg.value.0,
188 T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
189 )
190 });
191 if Mode::TRACK_EXECUTION {
192 args_to_update.extend(
193 arguments
194 .iter()
195 .filter(|arg| matches!(&arg.value.1, T::Type::Reference(true, _)))
196 .cloned(),
197 )
198 }
199 let arguments: Vec<CtxValue> = context.arguments(arguments)?;
200 if is_gas_coin_send_funds {
201 assert_invariant!(arguments.len() == 2, "coin::send_funds should have 2 args");
202 let recipient = arguments.last().unwrap().to_address()?;
203 context.record_gas_coin_transfer(GasCoinTransfer::SendFunds { recipient })?;
204 }
205 let res = context.vm_move_call(function, arguments, trace_builder_opt);
206 trace_utils::trace_move_call_end(trace_builder_opt);
207 res?
208 }
209 T::Command__::TransferObjects(objects, recipient) => {
210 let has_gas_coin_move = objects.iter().any(|arg| {
212 matches!(
213 &arg.value.0,
214 T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
215 )
216 });
217 if has_gas_coin_move {
218 context.record_gas_coin_transfer(GasCoinTransfer::TransferObjects)?;
219 }
220 let object_tys = objects
221 .iter()
222 .map(|sp!(_, (_, ty))| ty.clone())
223 .collect::<Vec<_>>();
224 let object_values: Vec<CtxValue> = context.arguments(objects)?;
225 let recipient: AccountAddress = context.argument(recipient)?;
226 assert_invariant!(
227 object_values.len() == object_tys.len(),
228 "object values and types mismatch"
229 );
230 trace_utils::trace_transfer(context, trace_builder_opt, &object_values, &object_tys)?;
231 for (object_value, ty) in object_values.into_iter().zip_debug_eq(object_tys) {
232 let recipient = Owner::AddressOwner(recipient.into());
234 context.transfer_object(recipient, ty, object_value)?;
235 }
236 vec![]
237 }
238 T::Command__::SplitCoins(ty, coin, amounts) => {
239 let mut trace_values = vec![];
240 if Mode::TRACK_EXECUTION {
242 args_to_update.push(coin.clone());
243 }
244 let coin_ref: CtxValue = context.argument(coin)?;
245 let amount_values: Vec<u64> = context.arguments(amounts)?;
246 let mut total: u64 = 0;
247 for amount in &amount_values {
248 let Some(new_total) = total.checked_add(*amount) else {
249 return Err(Mode::Error::from_kind(
250 ExecutionErrorKind::CoinBalanceOverflow,
251 ));
252 };
253 total = new_total;
254 }
255 trace_utils::add_move_value_info_from_ctx_value(
256 context,
257 trace_builder_opt,
258 &mut trace_values,
259 &ty,
260 &coin_ref,
261 )?;
262 let coin_value = context.copy_value(&coin_ref)?.coin_ref_value()?;
263 fp_ensure!(
264 coin_value >= total,
265 Mode::Error::new_with_source(
266 ExecutionErrorKind::InsufficientCoinBalance,
267 format!("balance: {coin_value} required: {total}")
268 )
269 );
270 coin_ref.coin_ref_subtract_balance(total)?;
271 let amounts = amount_values
272 .into_iter()
273 .map(|a| context.new_coin(a))
274 .collect::<Result<Vec<_>, _>>()?;
275 trace_utils::trace_split_coins(
276 context,
277 trace_builder_opt,
278 &ty,
279 trace_values,
280 &amounts,
281 total,
282 )?;
283
284 amounts
285 }
286 T::Command__::MergeCoins(ty, target, coins) => {
287 let mut trace_values = vec![];
288 if Mode::TRACK_EXECUTION {
290 args_to_update.push(target.clone());
291 }
292 let target_ref: CtxValue = context.argument(target)?;
293 trace_utils::add_move_value_info_from_ctx_value(
294 context,
295 trace_builder_opt,
296 &mut trace_values,
297 &ty,
298 &target_ref,
299 )?;
300 let coins = context.arguments(coins)?;
301 let amounts = coins
302 .into_iter()
303 .map(|coin| {
304 trace_utils::add_move_value_info_from_ctx_value(
305 context,
306 trace_builder_opt,
307 &mut trace_values,
308 &ty,
309 &coin,
310 )?;
311 context.destroy_coin(coin)
312 })
313 .collect::<Result<Vec<_>, _>>()?;
314 let mut additional: u64 = 0;
315 for amount in amounts {
316 let Some(new_additional) = additional.checked_add(amount) else {
317 return Err(Mode::Error::from_kind(
318 ExecutionErrorKind::CoinBalanceOverflow,
319 ));
320 };
321 additional = new_additional;
322 }
323 let target_value = context.copy_value(&target_ref)?.coin_ref_value()?;
324 fp_ensure!(
325 target_value.checked_add(additional).is_some(),
326 Mode::Error::from_kind(ExecutionErrorKind::CoinBalanceOverflow,)
327 );
328 target_ref.coin_ref_add_balance(additional)?;
329 trace_utils::trace_merge_coins(
330 context,
331 trace_builder_opt,
332 &ty,
333 trace_values,
334 additional,
335 )?;
336 vec![]
337 }
338 T::Command__::MakeMoveVec(ty, items) => {
339 let items: Vec<CtxValue> = context.arguments(items)?;
340 trace_utils::trace_make_move_vec(context, trace_builder_opt, &items, &ty)?;
341 vec![CtxValue::vec_pack(ty, items)?]
342 }
343 T::Command__::Publish(payload, dep_ids, linkage) => {
344 trace_utils::trace_publish_event(trace_builder_opt)?;
345 let package_payload = context.deserialize_package(payload, &dep_ids)?;
346
347 let original_id = context.publish_and_init_package(
348 package_payload,
349 &dep_ids,
350 linkage,
351 trace_builder_opt,
352 )?;
353
354 if <Mode>::packages_are_predefined() {
355 std::vec![]
357 } else {
358 std::vec![context.new_upgrade_cap(original_id)?]
359 }
360 }
361 T::Command__::Upgrade(payload, dep_ids, current_package_id, upgrade_ticket, linkage) => {
362 trace_utils::trace_upgrade_event(trace_builder_opt)?;
363 let upgrade_ticket = context
364 .argument::<CtxValue>(upgrade_ticket)?
365 .into_upgrade_ticket()?;
366 if current_package_id != upgrade_ticket.package.bytes {
368 return Err(Mode::Error::from_kind(
369 ExecutionErrorKind::PackageUpgradeError {
370 upgrade_error: PackageUpgradeError::PackageIDDoesNotMatch {
371 package_id: current_package_id,
372 ticket_id: upgrade_ticket.package.bytes,
373 },
374 },
375 ));
376 }
377 let package_payload = context.deserialize_package(payload, &dep_ids)?;
379 let computed_digest = package_payload.computed_digest.to_vec();
380
381 if computed_digest != upgrade_ticket.digest {
382 return Err(Mode::Error::from_kind(
383 ExecutionErrorKind::PackageUpgradeError {
384 upgrade_error: PackageUpgradeError::DigestDoesNotMatch {
385 digest: computed_digest,
386 },
387 },
388 ));
389 }
390
391 let upgraded_package_id = context.upgrade(
392 package_payload,
393 &dep_ids,
394 current_package_id,
395 upgrade_ticket.policy,
396 linkage,
397 trace_builder_opt,
398 )?;
399
400 vec![context.upgrade_receipt(upgrade_ticket, upgraded_package_id)]
401 }
402 };
403 if Mode::TRACK_EXECUTION {
404 let argument_updates = context.argument_updates(args_to_update)?;
405 let command_result = context.tracked_results(&result, &result_type)?;
406 Mode::finish_command(mode_results, argument_updates, command_result)?;
407 }
408 assert_invariant!(
409 result.len() == drop_values.len(),
410 "result values and drop values mismatch"
411 );
412 context.charge_command(is_move_call, num_args, result.len())?;
413 let result = result
414 .into_iter()
415 .zip_debug_eq(drop_values)
416 .map(|(value, drop)| if !drop { Some(value) } else { None })
417 .collect::<Vec<_>>();
418 context.result(result)?;
419 assert_invariant!(
420 context.gas_charger.move_gas_status().stack_height_current() == 0,
421 "stack height did not end at 0"
422 );
423 Ok(())
424}
425
426struct IndexedExecutionTimings {
428 original_command_len: usize,
430 executed_commands: BTreeMap<usize, Duration>,
433 error_command: Option<(usize, Duration)>,
436}
437
438impl IndexedExecutionTimings {
439 fn new(original_command_len: usize) -> Self {
440 Self {
441 original_command_len,
442 executed_commands: BTreeMap::new(),
443 error_command: None,
444 }
445 }
446
447 fn max_allowed_index(&self) -> usize {
449 self.original_command_len.saturating_sub(1)
450 }
451
452 fn executed(&mut self, annotated_index: usize, duration: Duration) {
454 debug_assert!(
455 self.error_command.is_none(),
456 "command executed after an error occurred"
457 );
458 let index = annotated_index.min(self.max_allowed_index());
459 let existing = self
460 .executed_commands
461 .entry(index)
462 .or_insert(Duration::ZERO);
463 *existing = existing.saturating_add(duration);
464 }
465
466 fn error(&mut self, annotated_index: usize, duration: Duration) {
468 debug_assert!(self.error_command.is_none(), "multiple errors recorded");
469 let index = annotated_index.min(self.max_allowed_index());
470 debug_assert!(
471 self.executed_commands
472 .last_key_value()
473 .is_none_or(|(last, _)| *last <= index),
474 "execution timings recorded for command index {:?} after error at index {}",
475 self.executed_commands
476 .last_key_value()
477 .map(|(last, _)| *last),
478 index,
479 );
480
481 let existing_opt = self.executed_commands.remove(&index);
482 let total_duration = existing_opt
483 .unwrap_or(Duration::ZERO)
484 .saturating_add(duration);
485 self.error_command = Some((index, total_duration));
486 }
487
488 fn into_coalesced(self) -> Vec<ExecutionTiming> {
493 let max_allowed_index = self.max_allowed_index();
494 let Self {
495 original_command_len,
496 executed_commands,
497 error_command,
498 } = self;
499
500 if original_command_len == 0 {
503 return vec![];
504 }
505
506 let max_executed_index = executed_commands.keys().last().copied();
507 let error_index = error_command.as_ref().map(|(idx, _)| *idx);
508 let max_used_index = match (max_executed_index, error_index) {
509 (Some(exec), Some(err)) => exec.max(err),
510 (Some(idx), None) | (None, Some(idx)) => idx,
511 (None, None) => return vec![],
512 };
513 debug_assert!(
514 max_used_index <= max_allowed_index,
515 "max used index {} exceeds max allowed index {}",
516 max_used_index,
517 max_allowed_index
518 );
519 let size = max_used_index.saturating_add(1);
520 debug_assert!(
521 size <= original_command_len,
522 "coalesced timings length {} exceeds original command length {}",
523 size,
524 original_command_len
525 );
526
527 let mut coalesced = vec![ExecutionTiming::Success(Duration::ZERO); size];
532 for (index, duration) in executed_commands {
533 let Some(entry) = coalesced.get_mut(index) else {
534 debug_assert!(
535 false,
536 "failed to initialize coalesced timings at index {}",
537 index
538 );
539 continue;
540 };
541 debug_assert!(matches!(entry, ExecutionTiming::Success(d) if d.is_zero()));
542 *entry = ExecutionTiming::Success(duration);
543 }
544
545 if let Some((index, error_duration)) = error_command {
546 debug_assert!(
547 index == coalesced.len().saturating_sub(1),
548 "error index should be last"
549 );
550 if let Some(entry) = coalesced.get_mut(index) {
551 debug_assert!(matches!(entry, ExecutionTiming::Success(d) if d.is_zero()));
552 *entry = ExecutionTiming::Abort(error_duration);
553 } else {
554 debug_assert!(
555 false,
556 "failed to initialize coalesced timings at index {}",
557 index
558 );
559 };
560 }
561
562 coalesced
563 }
564}