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