sui_adapter_v3/programmable_transactions/
trace_utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module implements support for tracing related to PTB execution. IMPORTANT:
5//! Bodies of all public functions in this module should be enclosed in a large if statement checking if
6//! tracing is enabled or not to make sure that any errors coming from these functions only manifest itself
7//! when tracing is enabled.
8
9use 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
36/// Inserts Move call start event into the trace. As is the case for all other public functions in this module,
37/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
38pub 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
46/// Inserts Move call end event into the trace. As is the case for all other public functions in this module,
47/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
48pub 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
56/// Inserts transfer event into the trace. As is the case for all other public functions in this module,
57/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
58pub 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
83/// Inserts PTB summary event into the trace. As is the case for all other public functions in this module,
84/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
85pub 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                    // Not ideal but it only runs when tracing is enabled so overhead
117                    // should be insignificant
118                    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
157/// Inserts split coins event into the trace. As is the case for all other public functions in this module,
158/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
159pub 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
213/// Inserts merge coins event into the trace. As is the case for all other public functions in this module,
214/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
215pub 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
269/// Inserts make move vec event into the trace. As is the case for all other public functions in this module,
270/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
271pub 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
297/// Inserts publish event into the trace. As is the case for all other public functions in this module,
298/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
299pub 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
314/// Inserts upgrade event into the trace. As is the case for all other public functions in this module,
315/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
316pub 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
331/// Inserts execution error event into the trace. As is the case for all other public functions in this module,
332/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
333pub 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
339/// Adds `ExtMoveValueInfo` to the mutable vector passed as an argument.
340/// As is the case for all other public functions in this module,
341/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
342pub 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
357/// Adds `ExtMoveValueInfo` to the mutable vector passed as an argument.
358/// As is the case for all other public functions in this module,
359/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
360pub 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
373/// Adds coin object info to the mutable vector passed as an argument.
374/// As is the case for all other public functions in this module,
375/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
376pub 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
387/// Creates `ExtMoveValueInfo` from raw bytes.
388fn 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
408/// Creates `ExtMoveValueInfo` from `Value`.
409fn 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
421/// Creates `ExtMoveValueInfo` from `ObjectValue`.
422fn 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
437/// Creates `ExtMoveValueInfo` for a coin.
438fn 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    // object.ID
448    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    // object.UID
461    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    // coin.Balance
473    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    // coin.Coin
486    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
499/// Converts a type to type tag format used in tracing.
500fn 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}