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 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 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
109 drop(context);
111 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 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
126 let wrapped_object_containers = object_runtime!(context)?.wrapped_object_containers();
129 let generated_object_ids = object_runtime!(context)?.generated_object_ids();
131
132 let finished = context.finish::<Mode>();
134 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#[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 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(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 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 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 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 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, 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 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 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 let modules = context.deserialize_modules(&module_bytes, true)?;
376
377 let computed_digest = MovePackage::compute_digest_for_modules_and_deps(
378 &module_bytes,
379 &dep_ids,
380 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}