1use crate::{
10 execution_mode::ExecutionMode,
11 execution_value::{ObjectContents, ObjectValue, Value},
12 programmable_transactions::context::*,
13};
14use move_core_types::{
15 identifier::Identifier,
16 language_storage::{StructTag, TypeTag},
17};
18use move_trace_format::{
19 format::{Effect, MoveTraceBuilder, RefType, TraceEvent, TypeTagWithRefs},
20 value::{SerializableMoveValue, SimplifiedMoveStruct},
21};
22use move_vm_types::loaded_data::runtime_types::Type;
23use sui_types::{
24 base_types::ObjectID,
25 coin::Coin,
26 error::ExecutionError,
27 execution_status::ExecutionErrorKind,
28 object::bounded_visitor::BoundedVisitor,
29 ptb_trace::{
30 ExtMoveValue, ExtMoveValueInfo, ExternalEvent, PTBCommandInfo, PTBEvent, SummaryEvent,
31 },
32 transaction::Command,
33};
34use sui_verifier::INIT_FN_NAME;
35
36pub fn trace_move_call_start(trace_builder_opt: &mut Option<MoveTraceBuilder>) {
39 if let Some(trace_builder) = trace_builder_opt {
40 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
41 PTBEvent::MoveCallStart
42 ))));
43 }
44}
45
46pub fn trace_move_call_end(trace_builder_opt: &mut Option<MoveTraceBuilder>) {
49 if let Some(trace_builder) = trace_builder_opt {
50 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
51 PTBEvent::MoveCallEnd
52 ))));
53 }
54}
55
56pub fn trace_transfer(
59 context: &mut ExecutionContext<'_, '_, '_>,
60 trace_builder_opt: &mut Option<MoveTraceBuilder>,
61 obj_values: &[ObjectValue],
62) -> Result<(), ExecutionError> {
63 if let Some(trace_builder) = trace_builder_opt {
64 let mut to_transfer = vec![];
65 for (idx, v) in obj_values.iter().enumerate() {
66 let obj_info = move_value_info_from_obj_value(context, v)?;
67 to_transfer.push(ExtMoveValue::Single {
68 name: format!("obj{idx}"),
69 info: obj_info,
70 });
71 }
72 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
73 PTBEvent::ExternalEvent(ExternalEvent {
74 description: "TransferObjects: obj0...objN => ()".to_string(),
75 name: "Transfer".to_string(),
76 values: to_transfer,
77 })
78 ))));
79 }
80 Ok(())
81}
82
83pub fn trace_ptb_summary<Mode: ExecutionMode>(
86 context: &mut ExecutionContext<'_, '_, '_>,
87 trace_builder_opt: &mut Option<MoveTraceBuilder>,
88 commands: &[Command],
89) -> Result<(), ExecutionError> {
90 if let Some(trace_builder) = trace_builder_opt {
91 let events = commands
92 .iter()
93 .map(|c| match c {
94 Command::MoveCall(move_call) => {
95 let pkg = move_call.package.to_string();
96 let module = move_call.module.clone();
97 let function = move_call.function.clone();
98 Ok(vec![PTBCommandInfo::MoveCall {
99 pkg,
100 module,
101 function,
102 }])
103 }
104 Command::TransferObjects(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
105 "TransferObjects".to_string(),
106 )]),
107 Command::SplitCoins(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
108 "SplitCoins".to_string(),
109 )]),
110 Command::MergeCoins(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
111 "MergeCoins".to_string(),
112 )]),
113 Command::Publish(module_bytes, _) => {
114 let mut events = vec![];
115 events.push(PTBCommandInfo::ExternalEvent("Publish".to_string()));
116 let modules = context.deserialize_modules(module_bytes)?;
119 events.extend(modules.into_iter().find_map(|m| {
120 for fdef in &m.function_defs {
121 let fhandle = m.function_handle_at(fdef.function);
122 let fname = m.identifier_at(fhandle.name);
123 if fname == INIT_FN_NAME {
124 return Some(PTBCommandInfo::MoveCall {
125 pkg: m.address().to_string(),
126 module: m.name().to_string(),
127 function: INIT_FN_NAME.to_string(),
128 });
129 }
130 }
131 None
132 }));
133 Ok(events)
134 }
135 Command::MakeMoveVec(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
136 "MakeMoveVec".to_string(),
137 )]),
138 Command::Upgrade(..) => {
139 Ok(vec![PTBCommandInfo::ExternalEvent("Upgrade".to_string())])
140 }
141 })
142 .collect::<Result<Vec<Vec<PTBCommandInfo>>, ExecutionError>>()?
143 .into_iter()
144 .flatten()
145 .collect();
146 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
147 PTBEvent::Summary(SummaryEvent {
148 name: "PTBSummary".to_string(),
149 events,
150 })
151 ))));
152 }
153
154 Ok(())
155}
156
157pub fn trace_split_coins(
160 context: &mut ExecutionContext<'_, '_, '_>,
161 trace_builder_opt: &mut Option<MoveTraceBuilder>,
162 coin_type: &Type,
163 input_coin: &Coin,
164 split_coin_values: &[Value],
165) -> Result<(), ExecutionError> {
166 if let Some(trace_builder) = trace_builder_opt {
167 let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, coin_type)?;
168 let mut split_coin_move_values = vec![];
169 for coin_val in split_coin_values {
170 let Value::Object(ObjectValue {
171 contents: ObjectContents::Coin(coin),
172 ..
173 }) = coin_val
174 else {
175 invariant_violation!("Expected result of split coins PTB command to be a coin");
176 };
177 split_coin_move_values.push(
178 coin_move_value_info(
179 type_tag_with_refs.clone(),
180 *coin.id.object_id(),
181 coin.balance.value(),
182 )?
183 .value,
184 );
185 }
186
187 let input = coin_move_value_info(
188 type_tag_with_refs.clone(),
189 *input_coin.id.object_id(),
190 input_coin.value(),
191 )?;
192 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
193 PTBEvent::ExternalEvent(ExternalEvent {
194 description: "SplitCoins: input => result".to_string(),
195 name: "SplitCoins".to_string(),
196 values: vec![
197 ExtMoveValue::Single {
198 name: "input".to_string(),
199 info: input
200 },
201 ExtMoveValue::Vector {
202 name: "result".to_string(),
203 type_: type_tag_with_refs.clone(),
204 value: split_coin_move_values
205 },
206 ],
207 })
208 ))));
209 }
210 Ok(())
211}
212
213pub fn trace_merge_coins(
216 context: &mut ExecutionContext<'_, '_, '_>,
217 trace_builder_opt: &mut Option<MoveTraceBuilder>,
218 coin_type: &Type,
219 input_infos: &[(u64, ObjectID)],
220 target_coin: &Coin,
221) -> Result<(), ExecutionError> {
222 if let Some(trace_builder) = trace_builder_opt {
223 let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, coin_type)?;
224 let mut input_coin_move_values = vec![];
225 let mut to_merge = 0;
226 for (balance, id) in input_infos {
227 input_coin_move_values.push(coin_move_value_info(
228 type_tag_with_refs.clone(),
229 *id,
230 *balance,
231 )?);
232 to_merge += balance;
233 }
234 let merge_target = coin_move_value_info(
235 type_tag_with_refs.clone(),
236 *target_coin.id.object_id(),
237 target_coin.value() - to_merge,
238 )?;
239 let mut values = vec![ExtMoveValue::Single {
240 name: "merge_target".to_string(),
241 info: merge_target,
242 }];
243 for (idx, input_value) in input_coin_move_values.into_iter().enumerate() {
244 values.push(ExtMoveValue::Single {
245 name: format!("coin{idx}"),
246 info: input_value,
247 });
248 }
249 let merge_result = coin_move_value_info(
250 type_tag_with_refs.clone(),
251 *target_coin.id.object_id(),
252 target_coin.value(),
253 )?;
254 values.push(ExtMoveValue::Single {
255 name: "merge_result".to_string(),
256 info: merge_result,
257 });
258 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
259 PTBEvent::ExternalEvent(ExternalEvent {
260 description: "MergeCoins: merge_target, coin0...coinN => mergeresult".to_string(),
261 name: "MergeCoins".to_string(),
262 values,
263 })
264 ))));
265 }
266 Ok(())
267}
268
269pub fn trace_make_move_vec(
272 context: &mut ExecutionContext<'_, '_, '_>,
273 trace_builder_opt: &mut Option<MoveTraceBuilder>,
274 move_values: Vec<ExtMoveValueInfo>,
275 type_: &Type,
276) -> Result<(), ExecutionError> {
277 if let Some(trace_builder) = trace_builder_opt {
278 let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, type_)?;
279 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
280 PTBEvent::ExternalEvent(ExternalEvent {
281 description: "MakeMoveVec: vector".to_string(),
282 name: "MakeMoveVec".to_string(),
283 values: vec![ExtMoveValue::Vector {
284 name: "vector".to_string(),
285 type_: type_tag_with_refs,
286 value: move_values
287 .into_iter()
288 .map(|move_value| move_value.value)
289 .collect(),
290 }],
291 })
292 ))));
293 }
294 Ok(())
295}
296
297pub fn trace_publish_event(
300 trace_builder_opt: &mut Option<MoveTraceBuilder>,
301) -> Result<(), ExecutionError> {
302 if let Some(trace_builder) = trace_builder_opt {
303 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
304 PTBEvent::ExternalEvent(ExternalEvent {
305 description: "Publish: ()".to_string(),
306 name: "Publish".to_string(),
307 values: vec![],
308 })
309 ))));
310 }
311 Ok(())
312}
313
314pub fn trace_upgrade_event(
317 trace_builder_opt: &mut Option<MoveTraceBuilder>,
318) -> Result<(), ExecutionError> {
319 if let Some(trace_builder) = trace_builder_opt {
320 trace_builder.push_event(TraceEvent::External(Box::new(serde_json::json!(
321 PTBEvent::ExternalEvent(ExternalEvent {
322 description: "Upgrade: ()".to_string(),
323 name: "Upgrade".to_string(),
324 values: vec![],
325 })
326 ))));
327 }
328 Ok(())
329}
330
331pub fn trace_execution_error(trace_builder_opt: &mut Option<MoveTraceBuilder>, msg: String) {
334 if let Some(trace_builder) = trace_builder_opt {
335 trace_builder.push_event(TraceEvent::Effect(Box::new(Effect::ExecutionError(msg))));
336 }
337}
338
339pub fn add_move_value_info_from_value(
343 context: &mut ExecutionContext<'_, '_, '_>,
344 trace_builder_opt: &mut Option<MoveTraceBuilder>,
345 move_values: &mut Vec<ExtMoveValueInfo>,
346 type_: &Type,
347 value: &Value,
348) -> Result<(), ExecutionError> {
349 if trace_builder_opt.is_some()
350 && let Some(move_value_info) = move_value_info_from_value(context, type_, value)?
351 {
352 move_values.push(move_value_info);
353 }
354 Ok(())
355}
356
357pub fn add_move_value_info_from_obj_value(
361 context: &mut ExecutionContext<'_, '_, '_>,
362 trace_builder_opt: &mut Option<MoveTraceBuilder>,
363 move_values: &mut Vec<ExtMoveValueInfo>,
364 obj_val: &ObjectValue,
365) -> Result<(), ExecutionError> {
366 if trace_builder_opt.is_some() {
367 let move_value_info = move_value_info_from_obj_value(context, obj_val)?;
368 move_values.push(move_value_info);
369 }
370 Ok(())
371}
372
373pub fn add_coin_obj_info(
377 trace_builder_opt: &mut Option<MoveTraceBuilder>,
378 coin_infos: &mut Vec<(u64, ObjectID)>,
379 balance: u64,
380 id: ObjectID,
381) {
382 if trace_builder_opt.is_some() {
383 coin_infos.push((balance, id));
384 }
385}
386
387fn move_value_info_from_raw_bytes(
389 context: &mut ExecutionContext<'_, '_, '_>,
390 type_: &Type,
391 bytes: &[u8],
392) -> Result<ExtMoveValueInfo, ExecutionError> {
393 let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, type_)?;
394 let layout = context
395 .vm
396 .get_runtime()
397 .type_to_fully_annotated_layout(type_)
398 .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
399 let move_value = BoundedVisitor::deserialize_value(bytes, &layout)
400 .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
401 let serialized_move_value = SerializableMoveValue::from(move_value);
402 Ok(ExtMoveValueInfo {
403 type_: type_tag_with_refs,
404 value: serialized_move_value,
405 })
406}
407
408fn move_value_info_from_value(
410 context: &mut ExecutionContext<'_, '_, '_>,
411 type_: &Type,
412 value: &Value,
413) -> Result<Option<ExtMoveValueInfo>, ExecutionError> {
414 match value {
415 Value::Object(obj_val) => Ok(Some(move_value_info_from_obj_value(context, obj_val)?)),
416 Value::Raw(_, bytes) => Ok(Some(move_value_info_from_raw_bytes(context, type_, bytes)?)),
417 Value::Receiving(_, _, _) => Ok(None),
418 }
419}
420
421fn move_value_info_from_obj_value(
423 context: &mut ExecutionContext<'_, '_, '_>,
424 obj_val: &ObjectValue,
425) -> Result<ExtMoveValueInfo, ExecutionError> {
426 let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, &obj_val.type_)?;
427 match &obj_val.contents {
428 ObjectContents::Coin(coin) => {
429 coin_move_value_info(type_tag_with_refs, *coin.id.object_id(), coin.value())
430 }
431 ObjectContents::Raw(bytes) => {
432 move_value_info_from_raw_bytes(context, &obj_val.type_, bytes)
433 }
434 }
435}
436
437fn coin_move_value_info(
439 type_tag_with_refs: TypeTagWithRefs,
440 object_id: ObjectID,
441 balance: u64,
442) -> Result<ExtMoveValueInfo, ExecutionError> {
443 let coin_type_tag = match type_tag_with_refs.type_.clone() {
444 TypeTag::Struct(tag) => tag,
445 _ => invariant_violation!("Expected a struct type tag when creating a Move coin value"),
446 };
447 let object_id = SerializableMoveValue::Address(object_id.into());
449 let object_id_struct_tag = StructTag {
450 address: coin_type_tag.address,
451 module: Identifier::new("object").unwrap(),
452 name: Identifier::new("ID").unwrap(),
453 type_params: vec![],
454 };
455 let object_id_struct = SimplifiedMoveStruct {
456 type_: object_id_struct_tag,
457 fields: vec![(Identifier::new("value").unwrap(), object_id)],
458 };
459 let serializable_object_id = SerializableMoveValue::Struct(object_id_struct);
460 let object_uid_struct_tag = StructTag {
462 address: coin_type_tag.address,
463 module: Identifier::new("object").unwrap(),
464 name: Identifier::new("UID").unwrap(),
465 type_params: vec![],
466 };
467 let object_uid_struct = SimplifiedMoveStruct {
468 type_: object_uid_struct_tag,
469 fields: vec![(Identifier::new("id").unwrap(), serializable_object_id)],
470 };
471 let serializable_object_uid = SerializableMoveValue::Struct(object_uid_struct);
472 let serializable_value = SerializableMoveValue::U64(balance);
474 let balance_struct_tag = StructTag {
475 address: coin_type_tag.address,
476 module: Identifier::new("balance").unwrap(),
477 name: Identifier::new("Balance").unwrap(),
478 type_params: coin_type_tag.type_params.clone(),
479 };
480 let balance_struct = SimplifiedMoveStruct {
481 type_: balance_struct_tag,
482 fields: vec![(Identifier::new("value").unwrap(), serializable_value)],
483 };
484 let serializable_balance = SerializableMoveValue::Struct(balance_struct);
485 let coin_obj = SimplifiedMoveStruct {
487 type_: *coin_type_tag,
488 fields: vec![
489 (Identifier::new("id").unwrap(), serializable_object_uid),
490 (Identifier::new("balance").unwrap(), serializable_balance),
491 ],
492 };
493 Ok(ExtMoveValueInfo {
494 type_: type_tag_with_refs,
495 value: SerializableMoveValue::Struct(coin_obj),
496 })
497}
498
499fn trace_type_to_type_tag_with_refs(
501 context: &mut ExecutionContext<'_, '_, '_>,
502 type_: &Type,
503) -> Result<TypeTagWithRefs, ExecutionError> {
504 let (deref_type, ref_type) = match type_ {
505 Type::Reference(t) => (t.as_ref(), Some(RefType::Imm)),
506 Type::MutableReference(t) => (t.as_ref(), Some(RefType::Mut)),
507 t => (t, None),
508 };
509 let type_ = context
510 .vm
511 .get_runtime()
512 .get_type_tag(deref_type)
513 .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
514 Ok(TypeTagWithRefs { type_, ref_type })
515}